High-level map of the tmh codebase for contributors and reviewers. Kept intentionally short — the code itself is the source of truth and files referenced here are all under 800 lines.
cmd/tmh Cobra CLI entry points — thin wrappers, no business logic
cmd/tmh-gen Build-time generator (schema, man pages, completions)
internal/actions Stateful side effects: attach, sync, reload, freeze, init,
kill, import, export, snapshot, undo, hooks, tmux_audit
internal/config YAML parsing + validation + resolver (profiles, templates,
inheritance), JSON schema, comment-preserving writer,
discover rules (glob + optional zoxide)
internal/tmux Runner interface + CLI implementation (spawning `tmux`).
`tmuxtest` is a pure-Go MockRunner used across tests.
internal/state SQLite store: history, snapshots, marks, trust-hashes,
reload queue. Pure-Go driver (modernc.org/sqlite) — no CGO.
internal/ui Bubble Tea TUI: dashboard, palette, picker, settings,
diff/confirm/history screens, theming, i18n strings.
internal/i18n go-i18n v2 bundle + English + Russian locales.
internal/errors Typed sentinel errors (tmux-layer, config-layer, hooks).
internal/shell $SHELL → rc-file resolution.
internal/xdg XDG config/state/cache paths.
┌──────────────┐
cmd/tmh/cmd/sync │ newSyncCmd │
└───────┬──────┘
│ loadConfig() → *config.Config
▼
actions.Push(ctx, Runner, cfg, opts)
│
├── config.Resolve(cfg, profile) // inherit defaults,
│ // apply templates
│
├── for each ResolvedSession:
│ Runner.HasSession / NewSession / NewWindow
│ applyWindowLayout
│
└── SyncReport (created / updated / skipped)
┌────────────────┐
│ Runner.ListSessions, ListPanes, ListWindows
└────────┬───────┘
▼
config.LiveSnapshot (in-memory)
│
▼
config.Diff(resolved, live) → []config.Drift
│
┌──────────┼──────────┐
▼ ▼
sync --pull tmh freeze
(destructive, (non-destructive,
applies via applies via
PathSet/PathDelete) PathSet only)
│ │
└──────────┬───────────┘
▼
config.Write(cfg, path, opts)
│
yaml.Node tree → file
(comments preserved)
See internal/state/db.go for the authoritative DDL. Tables:
| Table | Purpose |
|---|---|
events |
Append-only audit log keyed by unix-ts |
snapshots |
Serialised session trees for tmh undo |
trust |
(config_path, config_hash) — hook trust decisions |
reload_queue |
Panes queued for shell re-source while busy |
marks |
Named target bookmarks for TUI |
Both CLI and TUI open the same DB. WAL + busy_timeout=5s + foreign_keys=on
are applied on every connect.
-
Non-destructive by default. Writes through
config.PathSet/config.PathDeletekeep the yaml.Node tree intact — comments and template references never rot. Destructive changes require explicit--all/--apply-all. -
Runner interface is the only tmux seam. Production uses
tmux.CLIRunner; tests usetmux.tmuxtest.MockRunner. No package outsideinternal/tmuxforkstmuxdirectly. -
config.Configis parsed once, passed by reference. Mutations go throughPathSet/PathDelete; callers write back viaconfig.Write. -
TUI sub-models receive messages, own their state, and return updated copies per the Bubble Tea contract. The root model routes messages to the active screen only.
-
Hooks are imperative — excluded from drift.
config/diff.gocompares dirs, commands, and layouts; Hooks fields are ignored. -
i18n JSON output is English-stable.
--jsonon ls/diff/audit emits EnglishReasonCodestrings for script consumers regardless of active locale.
- Create
cmd/tmh/cmd/<name>.gowith anewFooCmd() *cobra.Command. - Register it inside
root.AddCommand(...)incmd/tmh/cmd/root.go. - Put side-effect logic in
internal/actions/<name>.goso the CLI stays thin and the logic is callable from the TUI. - Add i18n keys for
cli.<name>.shortand any flag descriptions to bothinternal/i18n/locales/en.jsonandru.json. - Run
make docsto refresh man pages, completions, and JSON schema. - Add tests in
internal/actions/<name>_test.go— use MockRunner.
- Add the field to the relevant struct in
internal/config/types.gowithyaml:"..."tag (used by both parser and schema reflector). - If the field participates in inheritance/resolution, update
internal/config/resolver.goto propagate it into the Resolved types. - If the field affects drift semantics, update
internal/config/diff.go. - Run
make docs— the JSON schema regenerates from tags. - Add tests.