featherlog-cli
Featherlog CLI — team rules + my work, from git history. Connects Claude Code to Featherlog, and does two local things on the side (today's commits as markdown, transcript backups). MIT, Node 20+, zero dependencies.
Published on npm as
@nonscale/featherlog(the bare namefeatherlogbelongs to an unrelated logging SDK).
npx @nonscale/featherlog connect # show what it would add to Claude Code (dry-run)
npx @nonscale/featherlog connect --write # connect: merge into ~/.claude/settings.json (with a backup)
npx @nonscale/featherlog disconnect --write # disconnect: remove only what Featherlog added
npx @nonscale/featherlog today # today's commits as Korean markdown (since 05:00 KST), local git only
npx @nonscale/featherlog backup # gzip agent transcripts into ~/.featherlog/archive/<repo>/
init and uninstall still work as hidden aliases of connect and disconnect.
What it does
| Command | What happens | Network |
|---|---|---|
connect [--write] | Connect Claude Code. Detects ~/.claude, ~/.cursor, ~/.codex, ~/.gemini by directory presence and prints the two Claude Code hooks it would add (below): at session start you get the handoff note, at session end one session line is recorded. --write merges them into ~/.claude/settings.json, keeps every existing hook, and writes settings.json.featherlog-backup-<timestamp> first. | none (the hooks themselves post later, see below) |
disconnect [--write] | Disconnect Claude Code. Removes only hooks carrying the X-Featherlog marker (header or in the curl command). Other hooks are untouched. | none |
today [--since <git date>] [--repo <dir>] | My work today, from local git and nothing else. Reads git log and prints ## YYYY-MM-DD — 요약, a commit table (SHA · scope · message), and file / added / deleted line counts. No LLM. | none |
backup [--repo <dir>] [--dry-run] | Transcript backup (extra, unrelated to the server). Copies ~/.claude/projects/<this repo>/**/*.jsonl, today's ~/.codex/sessions/**/*.jsonl, and ~/.cursor/projects/<this repo>/agent-transcripts/**/*.jsonl into ~/.featherlog/archive/<repo>/<agent>/….gz. Idempotent: same size + mtime is skipped. | none |
The hook contract (verified against the Claude Code hooks reference, 2026-09-13)
| Event | Hook type | What happens |
|---|---|---|
SessionEnd | http → POST <INGEST_URL>/hook | One session line. Claude Code POSTs its hook input JSON as-is (session_id, cwd, reason, transcript_path string, …). The server keeps session_id, the cwd basename, reason and the time — it drops transcript_path and every key named transcript/prompt/content/diff/patch. One events row per session (upsert). |
SessionStart | command → curl … <INGEST_URL>/handoff --data-binary @- | The handoff note. SessionStart does not support http hooks (only command/mcp_tool), so the CLI installs a curl one-liner. It pipes the hook input to the handoff endpoint and prints the response — {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"…"}} — which Claude Code injects as context: the last 2 decisions, open blockers, the next 3 items, and the last session on your other device. Empty note → {} → nothing injected. -f … || true keeps it silent when offline. |
FEATHERLOG_INGEST_URL is the base URL (default placeholder https://featherlog.app/api/ingest; a legacy value ending in /hook is accepted). The connect key travels as Authorization: Bearer $FEATHERLOG_TOKEN and the device name as X-Featherlog-Device: $FEATHERLOG_DEVICE; both are resolved by Claude Code at hook time via allowedEnvVars (http) or by the shell (command), and neither value is ever written into settings.json. Both hooks set timeout: 10, which also raises Claude Code's default 1.5 s SessionEnd budget.
Stop and PostToolUse hooks are intentionally not installed: turns are not stored, and commits come from the GitHub connection, not from your shell.
What it never sends
- Transcript bodies never leave your machine.
backupwrites to your own home directory only. - The
SessionEndhook thatconnectinstalls posts Claude Code's hook input:session_id,cwd,reason, andtranscript_pathas a string (which the server discards). Not the transcript, not your code, not diffs, not your prompts. todayreads git locally and prints to stdout. Nothing is uploaded.- The CLI itself makes no network calls. Only the hooks (executed by Claude Code, not by this CLI) do.
Keeping transcripts longer
Claude Code deletes local transcripts after cleanupPeriodDays (30 by default). featherlog backup is a gz copy for the day it is run; it is not a scheduler. If you want the originals kept longer, raise the value in ~/.claude/settings.json:
{ "cleanupPeriodDays": 90 }
Other agents (templates, unverified)
hooks/templates/ holds curl-based templates that post the generic envelope for Cursor, Codex CLI, Gemini CLI and Copilot CLI via featherlog-envelope.sh. They follow each vendor's public docs and are marked unverified against live agent; see hooks/templates/README.md for locations, events and what each one sends.
Connect agent, per agent (에이전트 연결 — 에이전트별)
One repository, four manifests. Every agent talks to the same Featherlog MCP (https://featherlog.app/api/mcp, 8 tools:
log_decision · log_blocker · log_next · log_correction write a one-line note, get_handoff · get_rules · get_rule_history · get_my_work read)
with your team's connect key (fl_…, Featherlog → Settings → Connect agent → Create connect key). Only the Claude Code CLI path
(connect --write) is verified against official docs; every plugin/extension manifest below was written from the vendor's public
docs on 2026-09-13 and is unverified against a live agent — each file says so in its own description / _unverified line.
| Agent | One-liner | Manifest in this repo |
|---|---|---|
| Claude Code | /plugin marketplace add nonscaleA/featherlog-cli → /plugin install featherlog@featherlog (asks for the connect key via userConfig) — or MCP only: claude mcp add --transport http featherlog https://featherlog.app/api/mcp --header 'Authorization: Bearer fl_…' — or hooks only: npx @nonscale/featherlog connect --write | .claude-plugin/plugin.json + .mcp.json + hooks/hooks.json |
| Cursor 2.5 | /add-plugin nonscaleA/featherlog-cli (or cursor.com/marketplace once listed), then set FEATHERLOG_TOKEN under Plugins → Configure | .cursor-plugin/plugin.json + mcp.json + hooks/cursor.json |
| Codex CLI | paste [mcp_servers.featherlog] into ~/.codex/config.toml (+ optional experimental hooks) — see codex/README.md | codex/README.md + hooks/templates/codex/hooks.json |
| Gemini CLI | gemini extensions install https://github.com/nonscaleA/featherlog-cli (prompts for FEATHERLOG_TOKEN) | gemini-extension.json + hooks/hooks.json |
| Copilot CLI | copilot plugin marketplace add nonscaleA/featherlog-cli → copilot plugin install featherlog@featherlog (Copilot CLI reads .claude-plugin/ as-is) | .claude-plugin/plugin.json + .mcp.json |
| Agent Plugins 1.0 clients (VS Code, Kiro, …) | point the client at this repository | plugin.json + mcp.json (spec: agent-plugins.org, type: "streamable-http") |
Connect-key substitution differs per ecosystem and is the least-verified part: Claude Code ${user_config.TOKEN} / $CLAUDE_PLUGIN_OPTION_TOKEN,
Cursor ${FEATHERLOG_TOKEN} via variables, Gemini settings[].envVar, Codex bearer_token_env_var, Agent Plugins 1.0 leaves
${FEATHERLOG_TOKEN} in mcp.json headers to the client. Prefer the CLI (connect --write) or claude mcp add when a manifest misbehaves.
hooks/hooks.json is one file for Claude Code, Gemini CLI and Copilot CLI: command hooks only (Gemini supports nothing else),
no timeout key (Gemini counts ms, Claude Code seconds — curl -m bounds each call instead), SessionStart → /handoff,
SessionEnd → /hook. Inside a plugin the SessionEnd budget stays at Claude Code's default 1.5 s, hence curl -m 1.
What leaves your machine
| Path | Sent | Never sent |
|---|---|---|
connect, disconnect, today, backup (this CLI) | nothing — no network calls | — |
SessionEnd hook (CLI-installed http, or plugin command) | Claude Code's hook input: session_id, cwd (server keeps the basename), reason, timestamps, transcript_path as a string (server discards it) | transcript body, prompts, code, diffs, file contents |
SessionStart hook | {hook_event_name, session_id, cwd} → receives the handoff note | anything else |
Other-agent templates (featherlog-envelope.sh) | source, event, session_id, device, occurred_at, project.{repo,cwd,branch} (from git in cwd) | transcript body, prompts, code, diffs |
Featherlog MCP (/api/mcp) | only what you type into a tool call (a decision, a blocker, a next item, a correction) and the tool name | your files — the server has no tool that reads them |
| Headers | Authorization: Bearer <connect key>, X-Featherlog: <installer>/<version>, X-Featherlog-Device (optional device name) | — |
Development
npm ci
npm test # node --test
npm run lint # node --check on every file
npm pack --dry-run
Set FEATHERLOG_TEST_HOME=/some/dir to point backup / connect at a fake home; tests use this so they never touch your real ~/.claude.
한국어
깃털로그 CLI — git 로그를 사람이 읽는 팀 규칙과 내가 한 일로. Claude Code 를 깃털로그에 연결하고, 곁다리로 로컬 일 두 가지(오늘 커밋 마크다운, 대화 기록 백업)를 합니다. MIT, Node 20+, 의존성 0.
connect— Claude Code 연결. 홈 폴더에서 에이전트를 찾고~/.claude/settings.json에 넣을 내용을 보여 줍니다(기본 dry-run).--write면 병합하고 이전 설정은 백업 파일로 남깁니다. 연결하면:- 세션이 끝날 때 메타 한 줄이 남습니다 —
SessionEnd→ http 훅으로POST <INGEST_URL>/hook. Claude Code 가 훅 입력 JSON 을 그대로 보내고, 서버는 세션 ID·cwd 이름·종료 이유·시각만 남깁니다(transcript_path는 서버가 버림). Claude Code 세션 1건 = 1행. - 시작할 때 이어하기 메모를 받습니다 —
SessionStart→ command 훅(curl). SessionStart 는 http 훅을 지원하지 않아(공식 문서 2026-09-13 확인) curl 한 줄이/handoff를 호출하고 응답{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":…}}을 stdout 으로 흘려 이어하기 메모(최근 정한 것 2·아직 막힌 것·다음 할 일 3·다른 기기 마지막 세션)를 컨텍스트로 넣습니다. 기록이 없으면{}. - 환경변수: 연결 키
FEATHERLOG_TOKEN(필수, settings.json 에 쓰이지 않음) ·FEATHERLOG_INGEST_URL(서버 기본 URL) · 기기 이름FEATHERLOG_DEVICE(선택, 내가 붙인 별명, 예:macB). init은connect의 숨김 별칭입니다(예전 문서·스크립트 호환).
- 세션이 끝날 때 메타 한 줄이 남습니다 —
disconnect— Claude Code 연결 해제.X-Featherlog마커가 붙은 훅만 지웁니다.uninstall은 숨김 별칭.today— 오늘 내가 한 일 (로컬 git, 서버 없이). 오늘(05:00 KST 기준) 커밋을## YYYY-MM-DD — 요약/ 커밋 목록(SHA·scope·메시지) / 파일 수·추가·삭제 줄로 출력. LLM 없음.backup— 대화 기록 압축 (부가, 서버와 무관). Claude Code·Codex·Cursor 대화 기록(.jsonl)을~/.featherlog/archive/<리포>/에 gz 로 복사. 같은 크기+수정시각이면 건너뜀.hooks/templates/— Cursor·Codex·Gemini CLI·Copilot CLI 용 curl 템플릿(범용 봉투). 라이브 에이전트 미검증.
보내지 않는 것. 대화 기록 본문은 이 기기를 떠나지 않습니다. 훅은 세션 ID·cwd 이름·종료 이유·시각만 보냅니다. 코드·diff·프롬프트·본문은 보내지 않습니다. Stop·PostToolUse 훅은 설치하지 않습니다(턴은 저장하지 않고, 커밋은 GitHub 연결이 정본).
참고. Claude Code 는 cleanupPeriodDays(기본 30일) 뒤에 로컬 대화 기록을 지웁니다. 더 오래 보관하려면 ~/.claude/settings.json 에서 값을 올리세요. backup 은 실행한 날의 복사본일 뿐 스케줄러가 아닙니다.
에이전트 연결(에이전트별). 위 "Connect agent, per agent" 표. 모든 에이전트가 같은 깃털로그 MCP(도구 8개: log_* 로 한 줄 메모를 남기고 get_* 로 읽음)에 팀의 연결 키(fl_…, 깃털로그 → 설정 → 에이전트 연결 → 연결 키 만들기)로 붙습니다. Claude Code 는 /plugin marketplace add nonscaleA/featherlog-cli → /plugin install featherlog@featherlog, Cursor 는 /add-plugin nonscaleA/featherlog-cli, Gemini CLI 는 gemini extensions install https://github.com/nonscaleA/featherlog-cli, Codex 는 codex/README.md, Copilot CLI 는 .claude-plugin/ 을 그대로 읽습니다. 매니페스트 4개는 공식 문서 기준(2026-09-13)이며 라이브 에이전트 미검증입니다.
설치: npx @nonscale/featherlog connect (npm 이름 featherlog 는 다른 패키지가 쓰고 있어 @nonscale/featherlog 로 배포합니다).