This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ShellTime CLI is a Go-based command-line tool for tracking DevOps work. It consists of two main binaries:
shelltime: The main CLI tool for command tracking and managementshelltime-daemon: A background service for asynchronous command tracking, synchronization, and OTEL data collection
Requires Go 1.27.1+
# Build the CLI binary
go build -o shelltime ./cmd/cli/main.go
# Build the daemon binary
go build -o shelltime-daemon ./cmd/daemon/main.go
# Build with all ldflags (version, commit, AI service config)
go build -ldflags "-X main.version=v0.1.0 -X main.commit=$(git rev-parse HEAD) -X main.date=$(date -u +%Y-%m-%d) -X main.ppEndpoint=<endpoint> -X main.ppToken=<token>" -o shelltime ./cmd/cli/main.go# Run all tests with coverage
go test -timeout 3m -coverprofile=coverage.txt -covermode=atomic ./...
# Run tests for a specific package
go test ./commands/...
go test ./daemon/...
go test ./model/...
# Run a single test
go test -run TestHandlerName ./daemon/Tests use testify (assertions + suites). Suite-based tests use suite.Suite with SetupTest/TearDownTest lifecycle hooks (see daemon/cc_info_handler_test.go for example). Simple functions use table-driven tests.
Tests must never reach the daemon running on the machine (yours, or a Claude Code cloud session's): it uploads to a real account. commands/main_test.go points track's fast-path socket (trackSocketPath) at a path that doesn't exist, and every other test uses a temp socketPath. The commands and daemon TestMains clear CLAUDE_CODE_REMOTE*, so tests behave the same inside a cloud session as on CI.
perf/ is a separate Go module (root ./... skips it). It benchmarks the real binary the way the shell hooks run it: daemon and direct paths, pre and post, plus a sync. See perf/README.md.
# Benchmark the current tree
go -C perf test -run '^$' -bench . -benchtime 50x -count 5
# Compare a base commit with the working tree, as CI does
perf/compare.sh origin/mainThe Track Perf workflow (.github/workflows/perf.yaml) runs this on every PR and push to main. It posts the report as a PR comment and in the job summary. A scenario that gets more than 10% slower (and more than 0.25 ms, with p < 0.05) raises a warning, never a failure. Track/* scenarios skip while a real daemon owns /tmp/shelltime.sock.
# Generate mocks (uses .mockery.yml configuration, Mockery v3)
mockery
# Generate PromptPal types (requires pp CLI and API token)
pp gCI runs both mockery and pp g before tests. Generated files: model/pp.types.g.go (PromptPal types), model/mock_*.go (testify mocks for service interfaces).
go vet ./...
go fmt ./...- cmd/cli/: CLI entry point - registers all commands, initializes services via dependency injection
- cmd/daemon/: Daemon entry point - sets up pub/sub, socket handler, and optional AICodeOtel gRPC server
- commands/: CLI command implementations - each command in its own file,
base.goholds injected services - daemon/: Daemon internals - socket handler, Watermill pub/sub channel, AICodeOtel gRPC server/processor
- model/: Business logic - API clients, config, crypto, shell hooks, service installers, dotfile handlers
Three key interfaces with dependency injection:
ConfigService: Reads and merges config fromconfig.yaml/config.toml(YAML preferred) plus the matchingconfig.local.*overridesAIService: PromptPal integration for AI-powered command suggestions (shelltime q)CommandService: Executable lookup with fallback paths (handles daemon's limited PATH)
Injection happens in cmd/*/main.go via commands.InjectVar() and commands.InjectAIService().
- SocketHandler: Unix domain socket server accepting JSON messages from CLI
- GoChannel: Watermill pub/sub for decoupled message processing
- SocketTopicProcessor: Consumes messages and routes to appropriate handlers
Optional daemon services (feature-gated via config):
- CCInfoTimerService: Lazy-fetch background timer for Claude Code statusline data (cost, quota, git info), plus an always-on Anthropic usage sync (10-min interval, only while a
claudeprocess is running) that pushes quota to/api/v1/anthropic-usagewithout needing statusline activity - CodexUsageSyncService: Syncs Codex usage to
/api/v1/codex-usageevery 10 min, only while acodex/chatgptprocess is running (both intervals share theusageSyncIntervalconst indaemon/usage_sync.go) - SyncCircuitBreakerService: Retry failed syncs with file-based persistence (
sync_pending.log) and hourly recovery timer - AICodeOtelServer: gRPC OTEL collector for AI coding CLI metrics, logs and traces (Claude Code, Codex). Spans (
claude_code.interaction,llm_request,tool, ...) go to the same/api/v1/cc/otelendpoint inspans; a span withoutsession.idtakes it from another span of its trace in the same export.shelltime cc installturns traces on (CLAUDE_CODE_ENHANCED_TELEMETRY_BETA,OTEL_TRACES_EXPORTER=otlp). In a Claude Code cloud session (CLAUDE_CODE_REMOTE=true, where the SessionStart hook starts the daemon) it adds the VM'sCLAUDE_CODE_REMOTE_SESSION_IDas theccr.session.idattribute to Claude Code records that lack it; the server marks those sessions as cloud sessions.daemon/main_test.goclears both variables so tests behave the same inside a cloud session - HeartbeatResyncService: Periodic resync of failed heartbeats (30-min interval)
- CleanupTimerService: Periodic log file cleanup (24-hour interval)
Services initialize in cmd/daemon/main.go: check enabled flag → create → start → defer stop. All run concurrently with graceful shutdown on SIGINT/SIGTERM.
- Shell hooks capture commands → CLI stores locally (file-based buffer)
- CLI sends sync message to daemon via Unix socket
- Daemon's pub/sub routes to sync handler
- Batch sync to shelltime.xyz API with optional encryption
- Main config:
$HOME/.shelltime/config.yaml(YAML preferred;config.ymlandconfig.tomlare also accepted) - Local overrides:
$HOME/.shelltime/config.local.yaml(merged over the base, gitignored) - Daemon socket:
/tmp/shelltime.sock(configurable viasocketPath) - AICodeOtel gRPC port: configurable via
aiCodeOtel.grpcPort(default: 54027) - Outbound proxy:
proxy.url/proxy.noProxy(http, https, socks5, socks5h). Applied once at startup viamodel.ConfigureProxy; every outbound HTTP client must usemodel.NewHTTPClientormodel.HTTPTransport()so the proxy applies - Token
proxy-injected(model.ProxyInjectedToken): a Claude Code cloud environment keeps the real token as a network secret, and its agent proxy addsAuthorization: CLI <token>to requests forapi.shelltime.xyz. Requests to the API then carry noAuthorizationheader of their own (setCLIAuthorization); set it through that helper, never by hand.cloud.bashin the installation repo writes it - AI cloud only (
aiCloudOnly,model.IsAICloudOnly): collect only AI coding agent telemetry and nothing about the machine, so it never becomes a host. Unset, it is on whereCLAUDE_CODE_REMOTE=true(a Claude Code cloud VM); an explicit value wins. The daemon's socket dropstrack_pre/track_post/syncand heartbeats, andcmd/daemon/main.goskips the bolt store, sync circuit breaker and heartbeat resync; OTEL,cc prand session-project pass through. In the CLI,trackwithout a daemon stores nothing, andsync,alias import,dotfiles pushandhooks installprint why and return nil (skippedInAICloudOnly).doctorreports it under Configuration and skips Shell Hooks
Follow Conventional Commits with scope: fix(daemon): ..., feat(cli): ..., refactor(model): ...
- CLI Framework:
urfave/cli/v2 - Message Queue:
ThreeDotsLabs/watermill(in-process pub/sub for daemon) - AI Integration:
PromptPal/go-sdkwith generated types frompromptpal.yml - Telemetry:
uptrace-gofor OTEL - Config:
pelletier/go-toml/v2 - Git:
go-git/v5for branch/dirty detection - gRPC: OTEL collector protos for AICodeOtel server
Releases use Release-Please (always-bump-patch) + Goreleaser. macOS builds include code signing/notarization via Quill when credentials are present. CI config in .github/workflows/.
- Daemon is optional but recommended (<8ms latency vs ~100ms+ direct)
- Encryption requires daemon mode and a token with encryption capability
- Shell hooks are platform-specific (bash, zsh, fish) - test on target shells
- CC statusline quota display is macOS-only (requires Keychain access to Claude Code OAuth token)
- AICodeOtel feature enables AI coding CLI metrics/logs/traces passthrough via gRPC (port 54027) - supports Claude Code, Codex, and other OTEL-compatible CLIs