11package main
22
33import (
4+ "context"
45 "fmt"
6+ "os/exec"
7+ "regexp"
8+ "runtime"
59 "runtime/debug"
10+ "strings"
11+ "time"
612
713 "github.com/spf13/cobra"
814)
@@ -13,32 +19,166 @@ var (
1319 date = "unknown"
1420)
1521
22+ const defaultTimeout = 2 * time .Second
23+
1624func init () {
1725 if version == "dev" {
1826 if buildCommit , buildTime := getBuildInfoFromBinary (); buildCommit != "unknown" {
1927 commit = buildCommit
2028 date = buildTime
2129 }
2230 }
31+
32+ versionCmd .Flags ().BoolP ("system" , "s" , false , "Show system information" )
33+ rootCmd .AddCommand (versionCmd )
2334}
2435
2536var versionCmd = & cobra.Command {
2637 Use : "version" ,
2738 Short : "Print version information" ,
2839 Long : `Print the version, commit hash, and build date of the container-use binary.` ,
29- Run : func (cmd * cobra.Command , args []string ) {
30- fmt .Printf ("container-use version %s\n " , version )
40+ RunE : func (cmd * cobra.Command , args []string ) error {
41+ showSystem , _ := cmd .Flags ().GetBool ("system" )
42+
43+ // Always show basic version info
44+ cmd .Printf ("container-use version %s\n " , version )
3145 if commit != "unknown" {
32- fmt .Printf ("commit: %s\n " , commit )
46+ cmd .Printf ("commit: %s\n " , commit )
3347 }
3448 if date != "unknown" {
35- fmt .Printf ("built: %s\n " , date )
49+ cmd .Printf ("built: %s\n " , date )
50+ }
51+
52+ if showSystem {
53+ cmd .Printf ("\n System:\n " )
54+ cmd .Printf (" OS/Arch: %s/%s\n " , runtime .GOOS , runtime .GOARCH )
55+
56+ // Check container runtime
57+ if runtime := detectContainerRuntime (cmd .Context ()); runtime != nil {
58+ cmd .Printf (" Container Runtime: %s\n " , runtime )
59+ } else {
60+ cmd .Printf (" Container Runtime: not found\n " )
61+ }
62+
63+ // Check Git
64+ if version := getToolVersion (cmd .Context (), "git" , "--version" ); version != "" {
65+ cmd .Printf (" Git: %s\n " , version )
66+ } else {
67+ cmd .Printf (" Git: not found\n " )
68+ }
69+
70+ // Check Dagger CLI
71+ if version := getToolVersion (cmd .Context (), "dagger" , "version" ); version != "" {
72+ cmd .Printf (" Dagger CLI: %s\n " , version )
73+ } else {
74+ cmd .Printf (" Dagger CLI: not found (needed for 'terminal' command)\n " )
75+ }
3676 }
77+
78+ return nil
3779 },
3880}
3981
40- func init () {
41- rootCmd .AddCommand (versionCmd )
82+ // runtimeInfo holds container runtime information
83+ type runtimeInfo struct {
84+ Name string
85+ Version string
86+ Running bool
87+ }
88+
89+ func (r * runtimeInfo ) String () string {
90+ if ! r .Running {
91+ return fmt .Sprintf ("%s %s (daemon not running)" , r .Name , r .Version )
92+ }
93+ return fmt .Sprintf ("%s %s" , r .Name , r .Version )
94+ }
95+
96+ // detectContainerRuntime finds the first available container runtime
97+ func detectContainerRuntime (ctx context.Context ) * runtimeInfo {
98+ // Check in the same order as Dagger
99+ runtimes := []struct {
100+ command string
101+ name string
102+ }{
103+ {"docker" , "Docker" },
104+ {"podman" , "Podman" },
105+ {"nerdctl" , "nerdctl" },
106+ {"finch" , "finch" },
107+ }
108+
109+ for _ , rt := range runtimes {
110+ if info := checkRuntime (ctx , rt .command , rt .name ); info != nil {
111+ return info
112+ }
113+ }
114+ return nil
115+ }
116+
117+ // checkRuntime checks if a specific runtime is available
118+ func checkRuntime (ctx context.Context , command , name string ) * runtimeInfo {
119+ // Check if command exists
120+ if _ , err := exec .LookPath (command ); err != nil {
121+ return nil
122+ }
123+
124+ info := & runtimeInfo {
125+ Name : name ,
126+ Version : "unknown" ,
127+ }
128+
129+ // Get version
130+ ctx , cancel := context .WithTimeout (ctx , defaultTimeout )
131+ defer cancel ()
132+
133+ if out , err := exec .CommandContext (ctx , command , "--version" ).Output (); err == nil {
134+ info .Version = extractVersion (string (out ))
135+ }
136+
137+ // Check if daemon is running
138+ cmd := exec .CommandContext (ctx , command , "info" )
139+ cmd .Stdout = nil // discard output
140+ cmd .Stderr = nil
141+ info .Running = cmd .Run () == nil
142+
143+ return info
144+ }
145+
146+ var versionRegex = regexp .MustCompile (`v?(\d+\.\d+(?:\.\d+)?)` )
147+
148+ // extractVersion finds a version number in the output
149+ func extractVersion (output string ) string {
150+ if matches := versionRegex .FindStringSubmatch (output ); len (matches ) > 1 {
151+ return matches [1 ]
152+ }
153+ return "unknown"
154+ }
155+
156+ // getToolVersion runs a command and returns its version output
157+ func getToolVersion (ctx context.Context , tool string , args ... string ) string {
158+ ctx , cancel := context .WithTimeout (ctx , defaultTimeout )
159+ defer cancel ()
160+
161+ out , err := exec .CommandContext (ctx , tool , args ... ).Output ()
162+ if err != nil {
163+ return ""
164+ }
165+
166+ output := strings .TrimSpace (string (out ))
167+
168+ // Handle specific tools
169+ switch tool {
170+ case "git" :
171+ // "git version 2.39.3" -> "2.39.3"
172+ return strings .TrimPrefix (output , "git version " )
173+ case "dagger" :
174+ // "dagger vX.Y.Z (...)" -> "vX.Y.Z"
175+ fields := strings .Fields (output )
176+ if len (fields ) > 1 {
177+ return fields [1 ]
178+ }
179+ }
180+
181+ return output
42182}
43183
44184func getBuildInfoFromBinary () (string , string ) {
@@ -48,7 +188,6 @@ func getBuildInfoFromBinary() (string, string) {
48188 }
49189
50190 var revision , buildTime , modified string
51-
52191 for _ , setting := range buildInfo .Settings {
53192 switch setting .Key {
54193 case "vcs.revision" :
@@ -60,7 +199,7 @@ func getBuildInfoFromBinary() (string, string) {
60199 }
61200 }
62201
63- // Format commit hash (use short version)
202+ // Format commit hash
64203 if len (revision ) > 7 {
65204 revision = revision [:7 ]
66205 }
0 commit comments