Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
BINARY_NAME=agent-deck
BUILD_DIR=./build
VERSION=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
LDFLAGS=-ldflags "-X main.Version=$(VERSION)"
COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null)
LDFLAGS=-ldflags "-X main.Version=$(VERSION) -X main.Commit=$(COMMIT)"

# Build the binary
build:
Expand Down
75 changes: 65 additions & 10 deletions cmd/agent-deck/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ import (

const Version = "0.27.5"

// Commit is the git commit hash, injected at build time via ldflags.
var Commit string

// VersionString returns the version with commit hash if available.
func VersionString() string {
if Commit != "" {
return Version + " (" + Commit + ")"
}
return Version
}

// Table column widths for list command output
const (
tableColTitle = 20
Expand Down Expand Up @@ -79,19 +90,32 @@ func promptForUpdate() bool {
}

info, err := update.CheckForUpdate(Version, false)
if err != nil || info == nil || !info.Available {
if err != nil || info == nil {
return false
}

sourceRebuild, sourceHead := sourceRebuildNeeded()
if !info.Available && !sourceRebuild {
return false
}

// If auto_update is disabled, just show notification (don't prompt)
if !settings.AutoUpdate {
fmt.Fprintf(os.Stderr, "\n💡 Update available: v%s → v%s (run: agent-deck update)\n",
info.CurrentVersion, info.LatestVersion)
if sourceRebuild {
fmt.Fprintf(os.Stderr, "\n💡 Rebuild available: binary %s vs source %s (run: agent-deck update)\n", Commit, sourceHead)
} else {
fmt.Fprintf(os.Stderr, "\n💡 Update available: v%s → v%s (run: agent-deck update)\n",
info.CurrentVersion, info.LatestVersion)
}
return false
}

// auto_update is enabled - prompt user
fmt.Printf("\n⬆ Update available: v%s → v%s\n", info.CurrentVersion, info.LatestVersion)
if sourceRebuild {
fmt.Printf("\n⬆ Rebuild available: binary %s vs source %s\n", Commit, sourceHead)
} else {
fmt.Printf("\n⬆ Update available: v%s → v%s\n", info.CurrentVersion, info.LatestVersion)
}
fmt.Print("Update now? [Y/n]: ")

var response string
Expand Down Expand Up @@ -198,7 +222,7 @@ func main() {
if len(args) > 0 {
switch args[0] {
case "version", "--version", "-v":
fmt.Printf("Agent Deck v%s\n", Version)
fmt.Printf("Agent Deck v%s\n", VersionString())
return
case "help", "--help", "-h":
printHelp()
Expand Down Expand Up @@ -313,6 +337,7 @@ func main() {

// Set version for UI update checking
ui.SetVersion(Version)
ui.SetCommit(Commit)

// Initialize theme from config (resolves "system" to actual dark/light)
theme := session.ResolveTheme()
Expand Down Expand Up @@ -2101,7 +2126,7 @@ func handleUpdate(args []string) {
os.Exit(1)
}

fmt.Printf("Agent Deck v%s\n", Version)
fmt.Printf("Agent Deck v%s\n", VersionString())
fmt.Println("Checking for updates...")

// Always force check when user explicitly runs 'update' command
Expand All @@ -2111,14 +2136,21 @@ func handleUpdate(args []string) {
fmt.Printf("Error checking for updates: %v\n", err)
os.Exit(1)
}
sourceRebuild, sourceHead := sourceRebuildNeeded()

if !info.Available {
if !info.Available && !sourceRebuild {
fmt.Println("✓ You're running the latest version!")
return
}

fmt.Printf("\n⬆ Update available: v%s → v%s\n", info.CurrentVersion, info.LatestVersion)
fmt.Printf(" Release: %s\n", info.ReleaseURL)
if sourceRebuild && !info.Available {
fmt.Printf("\n⬆ Rebuild available: binary %s vs source %s\n", Commit, sourceHead)
} else {
fmt.Printf("\n⬆ Update available: v%s → v%s\n", info.CurrentVersion, info.LatestVersion)
if info.ReleaseURL != "" {
fmt.Printf(" %s\n", info.ReleaseURL)
}
}

// Fetch and display changelog
displayChangelog(info.CurrentVersion, info.LatestVersion)
Expand Down Expand Up @@ -2190,6 +2222,29 @@ func handleUpdate(args []string) {
updateRemotesAfterLocalUpdate(info.LatestVersion)
}

// sourceRebuildNeeded reports whether source-mode should rebuild the binary even
// when the source checkout is not behind source_ref (e.g. binary commit differs).
func sourceRebuildNeeded() (bool, string) {
settings := session.GetUpdateSettings()
if settings.SourceDir == "" || Commit == "" {
return false, ""
}

cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
cmd.Dir = settings.SourceDir
out, err := cmd.Output()
if err != nil {
return false, ""
}

head := strings.TrimSpace(string(out))
if head == "" || head == Commit {
return false, head
}

return true, head
}

func runHomebrewUpgradeWithRefresh(homebrewUpgradeCmd string) error {
cmdParts := strings.Fields(homebrewUpgradeCmd)
if len(cmdParts) == 0 {
Expand Down Expand Up @@ -2261,7 +2316,7 @@ func drainStdin() {
}

func printHelp() {
fmt.Printf("Agent Deck v%s\n", Version)
fmt.Printf("Agent Deck v%s\n", VersionString())
fmt.Println("Terminal session manager for AI coding agents")
fmt.Println()
fmt.Println("Usage: agent-deck [-p profile] [command]")
Expand Down
12 changes: 12 additions & 0 deletions internal/session/userconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,15 @@ type UpdateSettings struct {
// NotifyInCLI shows update notification in CLI commands (not just TUI)
// Default: true
NotifyInCLI bool `toml:"notify_in_cli"`

// SourceDir is the absolute path to a local git checkout. When set,
// updates pull and build from source instead of downloading release
// binaries. Leave empty to use the default release-based updates.
SourceDir string `toml:"source_dir"`

// SourceRef is the remote tracking ref to follow (e.g. "origin/main").
// Default: "origin/main"
SourceRef string `toml:"source_ref"`
}

// PreviewSettings defines preview pane configuration
Expand Down Expand Up @@ -1423,6 +1432,9 @@ func GetUpdateSettings() UpdateSettings {
if settings.CheckIntervalHours <= 0 {
settings.CheckIntervalHours = 24
}
if settings.SourceRef == "" {
settings.SourceRef = "origin/main"
}

return settings
}
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func (h *HelpOverlay) View() string {
}
lines = append(lines, "")
lines = append(lines, separatorStyle.Render(strings.Repeat("─", separatorWidth)))
lines = append(lines, versionStyle.Render("Agent Deck v"+Version))
lines = append(lines, versionStyle.Render("Agent Deck v"+DisplayVersion()))

totalLines := len(lines)

Expand Down
18 changes: 17 additions & 1 deletion internal/ui/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,27 @@ import (
// Version is set by main.go for update checking
var Version = "0.0.0"

// Commit is the git commit hash, set by main.go at startup.
var Commit string

// SetVersion sets the current version for update checking
func SetVersion(v string) {
Version = v
}

// SetCommit sets the git commit hash for display in the UI.
func SetCommit(c string) {
Commit = c
}

// DisplayVersion returns the version string with commit hash if available.
func DisplayVersion() string {
if Commit != "" {
return Version + " (" + Commit + ")"
}
return Version
}

// Structured loggers for UI components
var (
uiLog = logging.ForComponent(logging.CompUI)
Expand Down Expand Up @@ -7540,7 +7556,7 @@ func (h *Home) View() string {
versionStyle := lipgloss.NewStyle().
Foreground(ColorComment).
Faint(true)
versionBadge := versionStyle.Render("v" + Version)
versionBadge := versionStyle.Render("v" + DisplayVersion())

// Fill remaining header space
headerLeft := lipgloss.JoinHorizontal(lipgloss.Left, logo, " ", title, " ", stats)
Expand Down
Loading