Skip to content

Latest commit

 

History

History
144 lines (108 loc) · 9.09 KB

CLAUDE.md

File metadata and controls

144 lines (108 loc) · 9.09 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

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 management
  • shelltime-daemon: A background service for asynchronous command tracking, synchronization, and OTEL data collection

Development Commands

Requires Go 1.27.1+

Building

# 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

Testing

# 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.

Performance (shelltime track latency)

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/main

The 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.

Code Generation

# Generate mocks (uses .mockery.yml configuration, Mockery v3)
mockery

# Generate PromptPal types (requires pp CLI and API token)
pp g

CI 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).

Linting

go vet ./...
go fmt ./...

Architecture

Package Structure

  • 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.go holds 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

Service Interfaces (model package)

Three key interfaces with dependency injection:

  • ConfigService: Reads and merges config from config.yaml/config.toml (YAML preferred) plus the matching config.local.* overrides
  • AIService: 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().

Daemon Architecture

  1. SocketHandler: Unix domain socket server accepting JSON messages from CLI
  2. GoChannel: Watermill pub/sub for decoupled message processing
  3. 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 claude process is running) that pushes quota to /api/v1/anthropic-usage without needing statusline activity
  • CodexUsageSyncService: Syncs Codex usage to /api/v1/codex-usage every 10 min, only while a codex/chatgpt process is running (both intervals share the usageSyncInterval const in daemon/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/otel endpoint in spans; a span without session.id takes it from another span of its trace in the same export. shelltime cc install turns 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's CLAUDE_CODE_REMOTE_SESSION_ID as the ccr.session.id attribute to Claude Code records that lack it; the server marks those sessions as cloud sessions. daemon/main_test.go clears 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.

Data Flow

  1. Shell hooks capture commands → CLI stores locally (file-based buffer)
  2. CLI sends sync message to daemon via Unix socket
  3. Daemon's pub/sub routes to sync handler
  4. Batch sync to shelltime.xyz API with optional encryption

Configuration

  • Main config: $HOME/.shelltime/config.yaml (YAML preferred; config.yml and config.toml are also accepted)
  • Local overrides: $HOME/.shelltime/config.local.yaml (merged over the base, gitignored)
  • Daemon socket: /tmp/shelltime.sock (configurable via socketPath)
  • AICodeOtel gRPC port: configurable via aiCodeOtel.grpcPort (default: 54027)
  • Outbound proxy: proxy.url / proxy.noProxy (http, https, socks5, socks5h). Applied once at startup via model.ConfigureProxy; every outbound HTTP client must use model.NewHTTPClient or model.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 adds Authorization: CLI <token> to requests for api.shelltime.xyz. Requests to the API then carry no Authorization header of their own (setCLIAuthorization); set it through that helper, never by hand. cloud.bash in 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 where CLAUDE_CODE_REMOTE=true (a Claude Code cloud VM); an explicit value wins. The daemon's socket drops track_pre/track_post/sync and heartbeats, and cmd/daemon/main.go skips the bolt store, sync circuit breaker and heartbeat resync; OTEL, cc pr and session-project pass through. In the CLI, track without a daemon stores nothing, and sync, alias import, dotfiles push and hooks install print why and return nil (skippedInAICloudOnly). doctor reports it under Configuration and skips Shell Hooks

Commit Rules

Follow Conventional Commits with scope: fix(daemon): ..., feat(cli): ..., refactor(model): ...

Key Dependencies

  • CLI Framework: urfave/cli/v2
  • Message Queue: ThreeDotsLabs/watermill (in-process pub/sub for daemon)
  • AI Integration: PromptPal/go-sdk with generated types from promptpal.yml
  • Telemetry: uptrace-go for OTEL
  • Config: pelletier/go-toml/v2
  • Git: go-git/v5 for branch/dirty detection
  • gRPC: OTEL collector protos for AICodeOtel server

Release

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/.

Important Notes

  • 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