config(git,claude,ssh): templated gitconfig; claude settings/commands/hooks/skills; ssh config

gitconfig: name/email from chezmoi data, gh credential helper by PATH
lookup (portable to darwin). claude: no credentials, history, projects,
sessions, or plugin caches. ssh config as private_ (0600).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Prad NukalaandClaude Fable 5 committed 2026-08-09 14:53:46 -04:00
1 parent 3c691e8dc5
commit 20891f3910
476 files changed
+59086 -7

No files matched your search

@@ -0,0 +1,203 @@
#!/usr/bin/env python3
# Reads a Claude Code SessionEnd hook JSON on stdin and exports that session's
# claude-mem learnings to a single Obsidian note. Stdlib only. Fails soft:
# any error exits 0 without blocking the session.
#
# Data interface (discovered live, not guessed):
# - SQLite DB: ~/.claude-mem/claude-mem.db (read-only)
# - sdk_sessions.content_session_id == Claude Code session_id
# - sdk_sessions.memory_session_id -> observations / session_summaries
# - observation types seen: discovery, change, feature, decision, bugfix, refactor
# - no private/excluded column exists: claude-mem drops <private> content at
# capture time so it never reaches the DB. We still scan defensively.
# - viewer deep link that resolves: http://localhost:<port>/?observation=<id>
import sys, os, re, json, sqlite3, datetime
from pathlib import Path
HOME = Path.home()
DB_PATH = Path(os.environ.get("CLAUDE_MEM_DB", HOME / ".claude-mem" / "claude-mem.db"))
VAULT_DIR = Path(os.environ.get("CMEM_OBSIDIAN_DIR", HOME / "Documents" / "Vault" / "90 - Archive" / "Learnings"))
WORKER_PORT = os.environ.get("CLAUDE_MEM_WORKER_PORT", "37700")
LOG_PATH = HOME / ".claude" / "hooks" / "cmem-to-obsidian.log"
# observation type -> note section
SECTIONS = [
("Decisions", {"decision"}),
("Fixes & gotchas", {"bugfix"}),
("Insights", {"discovery", "feature", "change", "refactor"}),
]
def log(msg):
try:
with open(LOG_PATH, "a") as f:
f.write(f"{datetime.datetime.now().isoformat()} {msg}\n")
except Exception:
pass
def slug(s):
return re.sub(r"[^a-z0-9]+", "-", (s or "").lower()).strip("-") or "unknown"
def is_private(obs):
blob = " ".join(str(obs.get(k) or "") for k in ("title", "subtitle", "narrative", "text", "facts"))
return "<private>" in blob.lower()
def connect_ro(path):
# read-only so we never lock or mutate the live DB / WAL
return sqlite3.connect(f"file:{path}?mode=ro", uri=True, timeout=5)
def fetch(session_id):
con = connect_ro(DB_PATH)
con.row_factory = sqlite3.Row
cur = con.cursor()
row = cur.execute(
"SELECT memory_session_id, project, started_at FROM sdk_sessions "
"WHERE content_session_id = ? LIMIT 1", (session_id,)
).fetchone()
if not row or not row["memory_session_id"]:
con.close()
return None
mem_id, project, started_at = row["memory_session_id"], row["project"], row["started_at"]
obs = [dict(r) for r in cur.execute(
"SELECT id, type, title, subtitle, narrative FROM observations "
"WHERE memory_session_id = ? ORDER BY created_at_epoch ASC", (mem_id,)
).fetchall()]
summ = cur.execute(
"SELECT learned, completed, investigated, request FROM session_summaries "
"WHERE memory_session_id = ? ORDER BY created_at_epoch DESC LIMIT 1", (mem_id,)
).fetchone()
con.close()
return {
"mem_id": mem_id,
"project": project or "unknown",
"started_at": started_at,
"observations": [o for o in obs if not is_private(o)],
"summary": dict(summ) if summ else {},
}
def obs_link(oid):
return f"http://localhost:{WORKER_PORT}/?observation={oid}"
def one_line(obs):
title = (obs.get("title") or "").strip()
sub = (obs.get("subtitle") or "").strip()
text = f"{title} — {sub}" if title and sub else (title or sub or "(untitled observation)")
return f"- {text} [cmem #{obs['id']}]({obs_link(obs['id'])})"
def render_note(data, session_id):
project = data["project"]
pslug = slug(project)
# date from the session's own start time -> stable across re-runs (idempotent)
date = (data["started_at"] or datetime.datetime.now().isoformat())[:10]
short = session_id[:8]
summary = data["summary"]
abstract = (summary.get("learned") or summary.get("completed")
or summary.get("investigated") or summary.get("request") or "").strip()
abstract = re.sub(r"\s+", " ", abstract) or "No session summary recorded."
fm = [
"---",
f"date: {date}",
f"project: {project}",
f"session: {short}",
"source: claude-mem",
"tags:",
" - claude-mem",
f" - project/{pslug}",
"---",
"",
f"# {project} — Session Learnings ({date})",
"",
"> [!abstract] Session summary",
f"> {abstract}",
"",
f"Project: [[{project}]]",
"",
]
body = []
by_id = {o["id"]: o for o in data["observations"]}
placed = set()
for heading, types in SECTIONS:
items = [o for o in data["observations"] if o["type"] in types]
if not items:
continue
body.append(f"## {heading}")
for o in items:
placed.add(o["id"])
if heading == "Decisions":
t = (o.get("title") or "").strip()
s = (o.get("subtitle") or "").strip()
body.append(f"- > [!note] {t}" + (f" — {s}" if s else "")
+ f" [cmem #{o['id']}]({obs_link(o['id'])})")
else:
body.append(one_line(o))
body.append("")
# any type we didn't map -> Insights catch-all
leftover = [o for o in data["observations"] if o["id"] not in placed]
if leftover:
body.append("## Insights")
body.extend(one_line(o) for o in leftover)
body.append("")
return "\n".join(fm + body).rstrip() + "\n", project, pslug, date, short
def ensure_project_moc(project):
# so [[Project]] wikilink resolves
moc = VAULT_DIR / f"{project}.md"
try:
if not moc.exists():
moc.write_text(
f"---\ntags:\n - moc\n - project/{slug(project)}\n---\n\n# {project}\n\n"
"Map of content for claude-mem session learnings.\n"
)
except Exception as e:
log(f"moc write failed: {e}")
def write_note(data, session_id):
text, project, pslug, date, short = render_note(data, session_id)
folder = VAULT_DIR / project
folder.mkdir(parents=True, exist_ok=True)
ensure_project_moc(project)
# deterministic filename keyed to session -> idempotent upsert
path = folder / f"{date} {project} {short}.md"
path.write_text(text)
log(f"wrote {path} ({len(data['observations'])} observations)")
return path
def main():
try:
payload = json.load(sys.stdin)
except Exception:
return
session_id = payload.get("session_id")
if not session_id:
return
try:
if not DB_PATH.exists():
return
data = fetch(session_id)
if not data or not data["observations"]:
return
write_note(data, session_id)
except Exception as e:
log(f"export failed for session {session_id}: {e}")
return
if __name__ == "__main__":
main()
@@ -0,0 +1,101 @@
#!/bin/sh
# installed by herdr
# managed by herdr; reinstalling or updating the integration overwrites this file.
# add custom hooks beside this file instead of editing it.
# HERDR_INTEGRATION_ID=claude
# HERDR_INTEGRATION_VERSION=7
set -eu
action="${1:-}"
hook_input_file="$(mktemp "${TMPDIR:-/tmp}/herdr-claude-hook.XXXXXX")" || exit 0
trap 'rm -f "$hook_input_file"' EXIT HUP INT TERM
cat >"$hook_input_file" 2>/dev/null || true
case "$action" in
session) ;;
*) exit 0 ;;
esac
[ "${HERDR_ENV:-}" = "1" ] || exit 0
[ -n "${HERDR_SOCKET_PATH:-}" ] || exit 0
[ -n "${HERDR_PANE_ID:-}" ] || exit 0
command -v python3 >/dev/null 2>&1 || exit 0
HERDR_ACTION="$action" HERDR_HOOK_INPUT_FILE="$hook_input_file" python3 - <<'PY'
import json
import os
import random
import socket
import time
source = "herdr:claude"
action = os.environ.get("HERDR_ACTION", "")
pane_id = os.environ.get("HERDR_PANE_ID")
socket_path = os.environ.get("HERDR_SOCKET_PATH")
hook_input_file = os.environ.get("HERDR_HOOK_INPUT_FILE")
if not pane_id or not socket_path:
raise SystemExit(0)
hook_input = {}
if hook_input_file:
try:
with open(hook_input_file, encoding="utf-8") as handle:
content = handle.read()
if content.strip():
hook_input = json.loads(content)
except Exception:
hook_input = {}
hook_event_name = str(hook_input.get("hook_event_name") or "")
is_subagent = bool(hook_input.get("agent_id"))
if is_subagent:
raise SystemExit(0)
if hook_event_name == "SubagentStop":
# SubagentStop is a completion event. Older Herdr integrations mapped it
# to durable working, but Claude recap/away-summary can emit it after the
# main turn has already stopped. Never let it revive an idle pane.
raise SystemExit(0)
request_id = f"{source}:{int(time.time() * 1000)}:{random.randrange(1_000_000):06d}"
report_seq = time.time_ns()
session_id = hook_input.get("session_id")
agent_session_id = session_id if isinstance(session_id, str) and session_id else None
transcript_path = hook_input.get("transcript_path")
agent_session_path = transcript_path if isinstance(transcript_path, str) and transcript_path else None
session_start_source = hook_input.get("source") if hook_event_name == "SessionStart" else None
if not isinstance(session_start_source, str) or not session_start_source:
session_start_source = None
if agent_session_id:
params = {
"pane_id": pane_id,
"source": source,
"agent": "claude",
"seq": report_seq,
"agent_session_id": agent_session_id,
}
if agent_session_path:
params["agent_session_path"] = agent_session_path
if session_start_source:
params["session_start_source"] = session_start_source
request = {
"id": request_id,
"method": "pane.report_agent_session",
"params": params,
}
else:
raise SystemExit(0)
try:
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.settimeout(0.5)
client.connect(socket_path)
client.sendall((json.dumps(request) + "\n").encode())
try:
client.recv(4096)
except Exception:
pass
client.close()
except Exception:
pass
PY
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env node
/**
* GitNexus Claude Code Hook
*
* PreToolUse handler — intercepts Grep/Glob/Bash searches
* and augments with graph context from the GitNexus index.
*
* NOTE: SessionStart hooks are broken on Windows (Claude Code bug).
* Session context is injected via CLAUDE.md / skills instead.
*/
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
/**
* Read JSON input from stdin synchronously.
*/
function readInput() {
try {
const data = fs.readFileSync(0, 'utf-8');
return JSON.parse(data);
} catch {
return {};
}
}
/**
* Check if a directory (or ancestor) has a .gitnexus index.
*/
function findGitNexusIndex(startDir) {
let dir = startDir || process.cwd();
for (let i = 0; i < 5; i++) {
if (fs.existsSync(path.join(dir, '.gitnexus'))) {
return true;
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return false;
}
/**
* Extract search pattern from tool input.
*/
function extractPattern(toolName, toolInput) {
if (toolName === 'Grep') {
return toolInput.pattern || null;
}
if (toolName === 'Glob') {
const raw = toolInput.pattern || '';
const match = raw.match(/[*\/]([a-zA-Z][a-zA-Z0-9_-]{2,})/);
return match ? match[1] : null;
}
if (toolName === 'Bash') {
const cmd = toolInput.command || '';
if (!/\brg\b|\bgrep\b/.test(cmd)) return null;
const tokens = cmd.split(/\s+/);
let foundCmd = false;
let skipNext = false;
const flagsWithValues = new Set(['-e', '-f', '-m', '-A', '-B', '-C', '-g', '--glob', '-t', '--type', '--include', '--exclude']);
for (const token of tokens) {
if (skipNext) { skipNext = false; continue; }
if (!foundCmd) {
if (/\brg$|\bgrep$/.test(token)) foundCmd = true;
continue;
}
if (token.startsWith('-')) {
if (flagsWithValues.has(token)) skipNext = true;
continue;
}
const cleaned = token.replace(/['"]/g, '');
return cleaned.length >= 3 ? cleaned : null;
}
return null;
}
return null;
}
function main() {
try {
const input = readInput();
const hookEvent = input.hook_event_name || '';
if (hookEvent !== 'PreToolUse') return;
const cwd = input.cwd || process.cwd();
if (!findGitNexusIndex(cwd)) return;
const toolName = input.tool_name || '';
const toolInput = input.tool_input || {};
if (toolName !== 'Grep' && toolName !== 'Glob' && toolName !== 'Bash') return;
const pattern = extractPattern(toolName, toolInput);
if (!pattern || pattern.length < 3) return;
// Resolve CLI path — try multiple strategies:
// 1. Relative path (works when script is inside npm package)
// 2. require.resolve (works when gitnexus is globally installed)
// 3. Fall back to npx (works when neither is available)
let cliPath = '/home/prad/.bun/install/global/node_modules/gitnexus/dist/cli/index.js';
if (!fs.existsSync(cliPath)) {
try {
cliPath = require.resolve('gitnexus/dist/cli/index.js');
} catch {
cliPath = ''; // will use npx fallback
}
}
// augment CLI writes result to stderr (KuzuDB's native module captures
// stdout fd at OS level, making it unusable in subprocess contexts).
const { spawnSync } = require('child_process');
let result = '';
try {
let child;
if (cliPath) {
child = spawnSync(
process.execPath,
[cliPath, 'augment', pattern],
{ encoding: 'utf-8', timeout: 8000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
);
} else {
// npx fallback
const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
child = spawnSync(
cmd,
['-y', 'gitnexus', 'augment', pattern],
{ encoding: 'utf-8', timeout: 15000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
);
}
result = child.stderr || '';
} catch { /* graceful failure */ }
if (result && result.trim()) {
console.log(JSON.stringify({
hookSpecificOutput: {
hookEventName: 'PreToolUse',
additionalContext: result.trim()
}
}));
}
} catch (err) {
// Graceful failure — log to stderr for debugging
console.error('GitNexus hook error:', err.message);
}
}
main();