Architecture
Module Layout
Section titled “Module Layout”cmd/klim/main.go Entry point → cli.Execute()internal/ build/ Version/Commit/Date (ldflags) + Info(), VersionOnly() catalog/ Fetch marketplace.yaml from GitHub, cache locally cli/ Cobra commands: list, export, import, open, share, update, etc. config/ config.yaml: logging, marketplace, performance, UI prefs custompacks/ User-created pack definitions detector/ Fallback version detection (Go buildinfo, Windows PE) favorites/ Favorites list persistence fileutil/ Shared file I/O: AtomicWrite, EnsureDir, ReadYAML, WriteYAML finder/ PATH scanning, install source detection logging/ slog structured logging + lumberjack file rotation manifest/ YAML schema for export/import manifests paths/ Single source of truth for all file paths pkgmgr/ Package manager queries (installed + latest versions) registry/ Tool, Instance, Pack structs; version comparison scancache/ Per-host scan cache for installed tools selfupdate/ Self-update from GitHub Releases service/ ToolService: composition root share/ Compact token encode/decode tui/ Bubbletea TUI: model, commands, view, stylesmarketplace/ tools/*.yaml One file per tool definition packs/*.yaml One file per pack definitionCore Architecture
Section titled “Core Architecture”ToolService ├── ToolCatalog catalog.LoadOrFetch() → fetch/cache marketplace.yaml ├── ToolFinder PATH scan → detect install source per binary └── VersionResolver pkgmgr queries → installed + latest versionsToolService is the composition root that wires together the catalog, finder, and version resolver. It provides two main entry points:
LoadAndResolve()— Full scan + version resolution (used by CLI)LoadCached()/LoadAndResolveCached()— Cache-first with optional--refreshflag
Data Flow
Section titled “Data Flow”TUI Flow
Section titled “TUI Flow”Init → findToolsCmd (PATH scan) → scanResultMsg (found tools) → resolveToolVersionCmd (×N, concurrent with semaphore) → toolVersionMsg (×N, version results) → doneThe TUI uses Bubbletea v2’s message-passing architecture. Each async operation is a Cmd that returns a Msg. The Update function processes messages and triggers state transitions.
CLI Flow
Section titled “CLI Flow”svc.LoadAndResolve() → catalog.LoadOrFetch() (fetch/cache marketplace) → finder.FindTools() (PATH scan) → pkgmgr.Resolve() (worker pool, concurrent version queries) → return toolsCLI commands call svc.LoadAndResolve() (or the cached variant) which handles everything internally with a worker pool.
Concurrency Model
Section titled “Concurrency Model”- TUI version resolution:
resolveSemchannel (capacity 4) limits concurrent subprocess calls scanGencounter: Invalidates stale messages when a rescan is triggered- Package manager calls:
context.WithTimeout(configurable, default 30s) - npm/dpkg caching:
sync.Onceprevents redundant global queries
Key Conventions
Section titled “Key Conventions”Error Handling
Section titled “Error Handling”- Return
errorlast - Wrap with
%wfor chain - Use
errors.New()for static messages - Never panic on user paths
Naming
Section titled “Naming”- Bubbletea messages end in
Msg - Command factories end in
Cmd - Cobra runners:
run<Command>
Imports
Section titled “Imports”- stdlib → third-party → internal, separated by blank lines
Shared Utilities
Section titled “Shared Utilities”internal/paths — Single source for all ~/.klim/* paths:
paths.Config() // config/config.yamlpaths.Favorites() // favorites/favorites.yamlpaths.ScanCache() // cache/scan-cache.yamlpaths.CatalogCache() // marketplace/marketplace-cache.yamlinternal/fileutil — Atomic writes and YAML I/O:
fileutil.AtomicWrite(path, data, 0o644)fileutil.WriteYAML(path, &obj, "# header\n")fileutil.ReadYAML(path, &obj)internal/registry — Tool collection helpers:
registry.SortByName(tools)registry.ToolMap(tools)registry.InstalledSet(tools)Adding a Package Manager
Section titled “Adding a Package Manager”- Add
InstallSourceconstant + command templates inregistry/tool.go - Add to
sourcePriority(),SourcesForOS(),AllPMStatusForOS() - Implement
xxxInstalledVersion()/xxxLatestVersion()inpkgmgr/pkgmgr.go - Wire into
installedVersion()/latestVersion()switches - Add field to
PackageIDsstruct + YAML tag - Add finder source detection in
finder/finder.go