@@ -51,18 +51,29 @@ func (t *Toolbox) Download() error {
5151 t .TempDir = tempDir
5252
5353 // Download the file
54- resp , err := http .Get (t .URL )
55- if err != nil {
56- return fmt .Errorf ("failed to download file: %w" , err )
57- }
58- defer resp .Body .Close ()
59-
60- if resp .StatusCode != http .StatusOK {
61- return fmt .Errorf ("bad status: %s" , resp .Status )
54+ var rc io.ReadCloser
55+ if strings .HasPrefix (t .URL , "file://" ) {
56+ localPath := strings .TrimPrefix (t .URL , "file://" )
57+ file , err := os .Open (localPath )
58+ if err != nil {
59+ return fmt .Errorf ("failed to open local file: %w" , err )
60+ }
61+ rc = file
62+ } else {
63+ resp , err := http .Get (t .URL )
64+ if err != nil {
65+ return fmt .Errorf ("failed to download file: %w" , err )
66+ }
67+ if resp .StatusCode != http .StatusOK {
68+ resp .Body .Close ()
69+ return fmt .Errorf ("bad status: %s" , resp .Status )
70+ }
71+ rc = resp .Body
6272 }
73+ defer rc .Close ()
6374
6475 // Create XZ reader
65- xzReader , err := xz .NewReader (resp . Body )
76+ xzReader , err := xz .NewReader (rc )
6677 if err != nil {
6778 return fmt .Errorf ("failed to create XZ reader: %w" , err )
6879 }
@@ -140,29 +151,69 @@ func (t *Toolbox) Cleanup() error {
140151// PlaybookConfig is defined in playbook.go
141152
142153// GetDiagnosticCommands returns the predefined diagnostic commands with actual toolbox paths
143- func (t * Toolbox ) GetDiagnosticCommands () []DiagnosticCommand {
154+ func (t * Toolbox ) GetDiagnosticCommands () ( []DiagnosticCommand , error ) {
144155 if t .TempDir == "" {
145156 // Return empty slice if toolbox not downloaded yet
146- return []DiagnosticCommand {}
157+ return []DiagnosticCommand {}, nil
147158 }
148159
149160 cfg , err := loadDiagnosticsConfigEmbedded ()
150161 if err != nil {
151- return []DiagnosticCommand {}
162+ return []DiagnosticCommand {}, err
152163 }
153164 // Store playbook on toolbox for later use (e.g., system prompt)
154165 t .Playbook = cfg
155166
156167 toolboxPath := path .Join (t .TempDir , "toolbox" )
157- prefix := fmt .Sprintf ("%s/proot -b %s/nix:/nix %s/nix/store/" , toolboxPath , toolboxPath , toolboxPath )
168+ storeDir := filepath .Join (toolboxPath , "nix" , "store" )
169+ prootPath := filepath .Join (toolboxPath , "proot" )
170+ prootPrefix := fmt .Sprintf ("%s -b %s/nix:/nix" , prootPath , toolboxPath )
158171
159172 var result []DiagnosticCommand
160173 for i := range cfg .Commands {
161174 c := cfg .Commands [i ]
162- cmdStr := prefix + c .Command
175+ parts := strings .Fields (c .Command )
176+ if len (parts ) == 0 {
177+ return nil , fmt .Errorf ("command '%s' is empty" , c .Command )
178+ }
179+ binName := parts [0 ]
180+ args := parts [1 :]
181+
182+ // Try to locate the binary inside toolbox nix store under */bin or */sbin
183+ resolved := ""
184+ if entries , err := os .ReadDir (storeDir ); err == nil {
185+ for _ , e := range entries {
186+ if ! e .IsDir () {
187+ continue
188+ }
189+ candidate := filepath .Join (storeDir , e .Name (), "bin" , binName )
190+ if st , err := os .Stat (candidate ); err == nil && ! st .IsDir () && (st .Mode ()& 0o111 != 0 ) {
191+ resolved = candidate
192+ break
193+ }
194+ candidate = filepath .Join (storeDir , e .Name (), "sbin" , binName )
195+ if st , err := os .Stat (candidate ); err == nil && ! st .IsDir () && (st .Mode ()& 0o111 != 0 ) {
196+ resolved = candidate
197+ break
198+ }
199+ }
200+ }
201+
202+ var cmdStr string
203+ if resolved != "" {
204+ if len (args ) > 0 {
205+ cmdStr = prootPrefix + " " + resolved + " " + strings .Join (args , " " )
206+ } else {
207+ cmdStr = prootPrefix + " " + resolved
208+ }
209+ } else {
210+ return nil , fmt .Errorf ("binary for command '%s' not found in toolbox nix store" , binName )
211+ }
212+
163213 if c .Sudo {
164214 cmdStr = "sudo " + cmdStr
165215 }
216+
166217 timeout := 5 * time .Second
167218 if c .TimeoutSeconds > 0 {
168219 timeout = time .Duration (c .TimeoutSeconds ) * time .Second
@@ -174,7 +225,7 @@ func (t *Toolbox) GetDiagnosticCommands() []DiagnosticCommand {
174225 Timeout : timeout ,
175226 })
176227 }
177- return result
228+ return result , nil
178229}
179230
180231// loadDiagnosticsConfig reads diagnostics.yaml from the working directory or alongside the executable.
@@ -219,6 +270,14 @@ func (t *Toolbox) ExecuteDiagnosticCommand(cmd DiagnosticCommand) (string, error
219270 return "" , fmt .Errorf ("toolbox not downloaded yet" )
220271 }
221272
273+ // If command resolution failed earlier, return a descriptive error now.
274+ if strings .TrimSpace (cmd .Command ) == "" {
275+ if cmd .Spec != nil && cmd .Spec .Command != "" {
276+ return "" , fmt .Errorf ("binary for command '%s' not found in toolbox nix store" , cmd .Spec .Command )
277+ }
278+ return "" , fmt .Errorf ("command binary not found in toolbox nix store" )
279+ }
280+
222281 // Split the command into parts for exec.Command
223282 parts := strings .Fields (cmd .Command )
224283 if len (parts ) == 0 {
@@ -253,7 +312,10 @@ func (t *Toolbox) ExecuteDiagnosticCommand(cmd DiagnosticCommand) (string, error
253312
254313// RunSpecificDiagnosticCommand runs a specific diagnostic command by its display name
255314func (t * Toolbox ) RunSpecificDiagnosticCommand (displayName string ) (string , error ) {
256- commands := t .GetDiagnosticCommands ()
315+ commands , err := t .GetDiagnosticCommands ()
316+ if err != nil {
317+ return "" , err
318+ }
257319
258320 for _ , cmd := range commands {
259321 if cmd .Display == displayName {
0 commit comments