93 lines
3.1 KiB
Go
93 lines
3.1 KiB
Go
package agents
|
|
|
|
import (
|
|
"context"
|
|
"os/exec"
|
|
"time"
|
|
)
|
|
|
|
const commandCheckTimeout = 5 * time.Second
|
|
|
|
// DetectOption is a detection strategy. Returns (found, matchedPath, err).
|
|
type DetectOption func(ctx context.Context) (bool, string, error)
|
|
|
|
// WithCommand checks if a command is in PATH (exec.LookPath).
|
|
func WithCommand(name string) DetectOption {
|
|
return func(ctx context.Context) (bool, string, error) {
|
|
path, err := exec.LookPath(name)
|
|
if err != nil {
|
|
return false, "", nil
|
|
}
|
|
return true, path, nil
|
|
}
|
|
}
|
|
|
|
// WithCommandCheck checks that a command is on PATH and that its
|
|
// non-interactive capability check exits successfully. A failed check means
|
|
// the optional capability is unavailable; a cancelled context remains an
|
|
// actual discovery error.
|
|
func WithCommandCheck(name string, args ...string) DetectOption {
|
|
return func(ctx context.Context) (bool, string, error) {
|
|
path, err := exec.LookPath(name)
|
|
if err != nil {
|
|
return false, "", nil
|
|
}
|
|
checkCtx, cancel := context.WithTimeout(ctx, commandCheckTimeout)
|
|
defer cancel()
|
|
if err := exec.CommandContext(checkCtx, path, args...).Run(); err != nil {
|
|
if ctx.Err() != nil {
|
|
return false, "", ctx.Err()
|
|
}
|
|
return false, "", nil
|
|
}
|
|
return true, path, nil
|
|
}
|
|
}
|
|
|
|
// WithNpxRunnable reports the agent as runnable via `npx -y <pkg>` whenever
|
|
// npx is on PATH. MatchedPath is tagged "npx <pkg>" so the settings page
|
|
// renders "Detected at npx <pkg>", giving users an honest hint that the
|
|
// package isn't globally installed but will be fetched on launch.
|
|
//
|
|
// Only `npx` is checked, not `node`: every npm-distributed agent's launch
|
|
// command is `npx -y <pkg>`, so a node-only setup would pass detection but
|
|
// fail at session start with "exec: npx: executable file not found in PATH".
|
|
//
|
|
// Use this AFTER a WithCommand check for the global binary — Detect is
|
|
// first-match-wins, so the global install (if present) is preferred and
|
|
// reports its real path.
|
|
//
|
|
// NOTE: not currently wired into any agent's IsInstalled. The host-utility
|
|
// manager treats Available=true as a green light to spawn and probe the
|
|
// agent at boot, so claiming availability via npx alone caused unwanted
|
|
// downloads and misleading auth_required/failed states for agents the user
|
|
// never installed. The helper is parked here until the host-utility manager
|
|
// gains a "skip probe for npx-fallback detections" path; at that point this
|
|
// can be re-wired into the npm-distributed agents' IsInstalled.
|
|
func WithNpxRunnable(pkg string) DetectOption {
|
|
return func(ctx context.Context) (bool, string, error) {
|
|
if _, err := exec.LookPath("npx"); err == nil {
|
|
return true, "npx " + pkg, nil
|
|
}
|
|
return false, "", nil
|
|
}
|
|
}
|
|
|
|
// Detect runs options in order, returns first match.
|
|
// If none match, returns DiscoveryResult{Available: false}.
|
|
func Detect(ctx context.Context, opts ...DetectOption) (*DiscoveryResult, error) {
|
|
for _, opt := range opts {
|
|
found, matched, err := opt(ctx)
|
|
if err != nil {
|
|
return &DiscoveryResult{Available: false}, err
|
|
}
|
|
if found {
|
|
return &DiscoveryResult{
|
|
Available: true,
|
|
MatchedPath: matched,
|
|
}, nil
|
|
}
|
|
}
|
|
return &DiscoveryResult{Available: false}, nil
|
|
}
|