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 |
helpers.go |
Shared command helpers for authenticated HTTP clients and session API error translation |
hook.go |
Parent command for hook handlers (confab hook <type>) |
hook_sessionstart.go |
session-start hook: spawns sync daemon. Provider-agnostic — selects via --provider flag and routes through provider.Provider. |
hook_sessionend.go |
session-end hook: stops sync daemon (Claude only; Codex shutdown is parent-PID driven and explicitly rejects this command) |
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 --provider <name> — install/uninstall hooks for the selected provider via p.InstallHooks() |
sync.go |
confab sync start/stop/status — daemon management |
spawn.go |
Generic maybeSpawnDaemon(p, *daemonLaunchInput) — single dispatch for Claude and Codex daemon spawn. daemonLaunchInput is the canonical wire format between the hook and the freshly-spawned daemon process. |
login.go |
Device code auth flow and API key login |
logout.go |
Clear stored credentials |
setup.go |
One-command setup: auth + hooks + bundled skills. Bare confab setup --backend-url ... auto-detects every provider CLI on PATH (via provider.DetectInstalled) and installs hooks/skills for each. --provider X overrides to single-provider mode. Best-effort across providers: per-provider failure is reported in a summary but doesn't abort the loop. |
status.go |
Show backend auth + per-provider hook/skill state for every supported provider. No --provider flag — output always covers all providers, with orphan-hook detection (hooks installed but CLI missing) and a remediation footer. |
list.go |
List local sessions (dispatches through provider.Provider.ScanSessions) |
list_utils.go |
Duration parsing, session filtering — fully provider-agnostic |
save.go |
Manual session upload by ID (dispatches through provider.Provider.FindSessionByID + DefaultCWD) |
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). Accepts --provider to pick the daemon-state namespace and normalizes Codex subagent thread IDs to the root thread before loading state. |
retro.go |
confab retro — fetch session transcript for retrospective (invoked by /retro skill) |
session.go |
Parent command for session subcommands (confab session <cmd>) |
session_get_summary.go |
confab session get-summary — fetch condensed session transcript from backend |
session_download.go |
confab session download — download raw JSONL transcript files from backend |
session_list_files.go |
confab session list-files — list transcript file metadata for a session |
skills.go |
confab skills add/remove — install/uninstall bundled skills for supported providers. add defaults to detected providers; remove defaults to all supported provider dirs. |
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
├── session
│ ├── get-summary
│ ├── download
│ └── list-files
├── til
├── retro
├── 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 via p.ParseSessionHook(r), do work, write the response via p.WriteHookResponse(w, ...).
pkg/hookconfig/{claude,codex}.go — Add Install<Name>Hook(), Uninstall<Name>Hook(), Is<Name>HookInstalled(). Wire them into the provider's InstallHooks / UninstallHooks / IsHooksInstalled in pkg/provider/{claude,codex}.go.
cmd/hooks.go — No change needed; p.InstallHooks() covers it.
cmd/status.go — No change needed; p.IsHooksInstalled() covers it.
cmd/hook.go — Register the new hook command under hookCmd.
Adding a new skill
pkg/config/skill_<name>.go — Add provider-rendered template constants/snippets.
pkg/config/bundled_skills.go — Add the skill name to bundledSkillNames and bundledSkillTemplate.
cmd/announce.go — Add an Announcement entry for Claude auto-rollout on update if the skill should be announced.
- Provider methods —
Provider.InstallSkills() / UninstallSkills() / IsSkillInstalled() automatically pick up the bundled registry when they call pkg/config.
Invariants
- All
io.ReadAll calls must be bounded. login.go and other commands that read HTTP responses or stdin use io.LimitReader to prevent memory exhaustion. Never use unbounded io.ReadAll on external input.
- Environment variable duration overrides are capped.
hook_sessionstart.go caps env var durations (e.g., sync interval) to prevent abuse via unreasonable values.
- Tar extraction in
update.go has size and path limits. Extracted files are bounded to prevent zip-bomb attacks, and paths are validated to prevent directory traversal.
- 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
ClaudeHookResponse JSON. Claude Code parses stdout as the hook response. Use stderr for status messages.
- Hook commands parse stdin via
p.ParseSessionHook(r). Returns the provider-agnostic provider.HookInput view. Session hooks also validate transcript_path.
- 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 exec.Command 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.
maybeSpawnDaemon(p, *daemonLaunchInput) is generic over the provider. Both session-start and user-prompt-submit call it. The function asks the provider's ShouldSpawnForInput gate, checks for an already-running daemon via daemon.LoadStateForProvider, fills in ParentPID via p.FindParentPID(), and spawns. The launchAsHookInput internal adapter bridges the HookInput interface signature to the mutable daemonLaunchInput so WalkUpToRoot rewrites can land on the spawn-side struct.
SessionStart routes every firing through p.WalkUpToRoot. Identity for Claude; thread-edge walk for Codex. For Codex, every subagent SessionStart that lands in an already-running root tree becomes a no-op via state-file dedup. confab save --provider codex <subagent-uuid> performs the same walk-up so manual saves of any UUID in a tree always sync the whole tree.
SessionStart keeps bundled skills aligned with hooks. Claude runs announcements, which install missing skills and return a visible system message. Codex silently ensures bundled skills under ~/.codex/skills/ so users who installed hooks get the same Confab skills without extra setup.
list, save, til route discovery through the Provider interface (CF-398). Adding a new provider requires only pkg/provider/<name>.go + <name>_discovery.go — no changes in cmd/. The remaining provider.NameClaudeCode / provider.NameCodex references in cmd/ are flag defaults (entry-point handling) and a couple of user-facing copy gates in cmd/list.go for the Codex-specific "save" hint.
Hook handlers (hook_userpromptsubmit.go, hook_pretooluse.go) stay hard-bound to provider.ClaudeCode{}. UserPromptSubmit and PreToolUse are Claude-only hook events; Codex doesn't install them. CF-398 deferred adding a p.SupportsCommitLinking() interface gate to a follow-up — see the comments in those files.
Backend session commands share auth/client setup. helpers.go owns the repeated EnsureAuthenticated + pkg/http.NewClient path and the common "session not found" translation for session fetch/list/download commands. Keep endpoint-specific behavior in the command files, not in the helper.
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())