cmd/
CLI command layer built on Cobra. Each file defines one or more commands and registers them via init().
Files
| File |
Role |
root.go |
Root command, persistent pre/post hooks, logger init |
hook.go |
Parent command for hook handlers (confab hook <type>) |
hook_sessionstart.go |
session-start hook: spawns sync daemon |
hook_sessionend.go |
session-end hook: stops sync daemon |
hook_pretooluse.go |
pre-tool-use hook: injects Confab links into git commits and PRs |
hook_posttooluse.go |
post-tool-use hook: links GitHub artifacts to Confab sessions |
hook_userpromptsubmit.go |
user-prompt-submit hook: ensures daemon is running |
hooks.go |
confab hooks add/remove — install/uninstall hooks in Claude Code settings |
sync.go |
confab sync start/stop/status — daemon management |
spawn.go |
Daemon spawning utilities, Claude PID detection |
login.go |
Device code auth flow and API key login |
logout.go |
Clear stored credentials |
setup.go |
One-command setup: auth + hooks |
status.go |
Show hook and auth status |
list.go |
List local sessions |
list_utils.go |
Duration parsing, session filtering |
save.go |
Manual session upload by ID |
install.go |
Copy binary to ~/.local/bin/ |
update.go |
Check/install updates from GitHub Releases |
til.go |
confab til — save a TIL to the backend (invoked by /til skill) |
skills.go |
confab skills add/remove — install/uninstall Claude Code skills |
announce.go |
General announcement system for post-update feature notifications |
autoupdate.go |
Enable/disable auto-update |
version.go |
Print version info |
redaction.go |
Test redaction rules against a file |
Command Tree
confab
├── hook
│ ├── session-start (also: sync start)
│ ├── session-end (also: sync stop)
│ ├── pre-tool-use
│ ├── post-tool-use
│ └── user-prompt-submit
├── sync
│ ├── start / stop
│ └── status
├── hooks
│ ├── add
│ └── remove
├── skills
│ ├── add
│ └── remove
├── til
├── login / logout
├── setup
├── status
├── list
├── save
├── install
├── update
├── autoupdate [enable|disable]
├── version
└── redaction-test
How to Extend
Adding a new command
- Create
cmd/<name>.go
- Define a
cobra.Command with Use, Short, RunE
- In
init(), call rootCmd.AddCommand(<name>Cmd) (or attach to a parent command)
- Register flags in
init() via <name>Cmd.Flags()
- Follow existing patterns — look at
save.go for a simple example, login.go for a complex one
Adding a new hook type
This is a cross-cutting change spanning multiple packages:
cmd/hook_<name>.go — Create hook handler. Read JSON from stdin, do work, write HookResponse JSON to stdout
pkg/config/config.go — Add Install<Name>Hook(), Uninstall<Name>Hook(), Is<Name>HookInstalled()
cmd/hooks.go — Add install/uninstall calls in hooksAddCmd and hooksRemoveCmd
cmd/status.go — Add status check for the new hook
cmd/setup.go — Add to the setup flow
cmd/hook.go — Register the new hook command under hookCmd
Adding a new skill
pkg/config/skill_<name>.go — Add template constant, Install<Name>Skill(), Uninstall<Name>Skill(), Is<Name>SkillInstalled(), Ensure<Name>Skill()
cmd/skills.go — Add install/uninstall calls in skillsAddCmd and skillsRemoveCmd
cmd/announce.go — Add an Announcement entry for auto-rollout on update
cmd/status.go — Add status check for the new skill
cmd/setup.go — Add to the setup flow
Invariants
- Hook commands must read JSON from stdin and complete quickly. Claude Code blocks waiting for hook responses. Long-running work must be delegated (e.g., daemon spawn).
- Hook commands must not write to stdout except for
HookResponse JSON. Claude Code parses stdout as the hook response. Use stderr for status messages.
- All hooks use
pkg/types.HookInput. Parsed via types.ReadHookInput(os.Stdin) (base validation) or discovery.ReadHookInputFrom(os.Stdin) (adds transcript_path validation for session hooks).
- Hook handlers must always output valid JSON, even on error. An error should produce a response with
continue: true rather than crashing with no output.
- Commands use
RunE (not Run) to return errors. Cobra handles error display.
Design Decisions
Hooks are thin wrappers. Hook command files read stdin, call into pkg/ packages, and write the response. Business logic lives in the packages, not in command handlers. This keeps hooks testable and the command layer simple.
hook.go dispatches vs. separate binaries. All hooks go through a single confab hook <type> command rather than separate binaries. This simplifies installation (one binary) and hook management (consistent command pattern).
spawn.go uses os.StartProcess with Setpgid. The daemon must outlive the hook command. Setpgid: true creates a new process group so the daemon isn't killed when the hook exits. exec.Command with Start() would work too, but os.StartProcess gives more control over the process attributes.
maybeSpawnDaemon is called from both session-start and user-prompt-submit. The user-prompt-submit hook handles the "teleport" case where Claude Code resumes a session without firing SessionStart. If the daemon isn't running, it spawns one.
Testable function pattern. Hook handlers extract core logic into functions that take io.Reader/io.Writer parameters (e.g., sessionStartFromReader(r io.Reader, w io.Writer)). Tests call these directly without needing stdin/stdout. Some functions use overridable function variables (e.g., spawnDaemonFunc) for test injection.
Testing
go test ./cmd/...
Tests use the io.Reader/io.Writer pattern and function variable overrides to test hook behavior without actual process spawning or stdin/stdout.
Dependencies
Uses: all pkg/ packages
Used by: main.go (calls cmd.Execute())