cli + daemon: move test seams off package globals onto injected structs
CLI: introduce internal/cli.deps which owns every RPC/SSH/host-command seam the tree used to reach through mutable package vars. Command builders, orchestrators, and the completion helpers become methods on *deps. Tests construct their own deps per case, so fakes no longer leak across cases and tests are free to run in parallel. Daemon: move workspaceInspectRepoFunc + workspaceImportFunc onto the Daemon struct (workspaceInspectRepo / workspaceImport), mirroring the existing guestWaitForSSH / guestDial pattern. Workspace-prepare tests drop t.Parallel() guards now that they no longer mutate process-wide state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d38f580e00
commit
c42fcbe012
19 changed files with 664 additions and 733 deletions
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newVMSessionCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "session",
|
||||
Short: "Manage long-lived guest commands inside a VM",
|
||||
|
|
@ -23,19 +23,19 @@ func newVMSessionCommand() *cobra.Command {
|
|||
RunE: helpNoArgs,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newVMSessionStartCommand(),
|
||||
newVMSessionListCommand(),
|
||||
newVMSessionShowCommand(),
|
||||
newVMSessionLogsCommand(),
|
||||
newVMSessionStopCommand(),
|
||||
newVMSessionKillCommand(),
|
||||
newVMSessionAttachCommand(),
|
||||
newVMSessionSendCommand(),
|
||||
d.newVMSessionStartCommand(),
|
||||
d.newVMSessionListCommand(),
|
||||
d.newVMSessionShowCommand(),
|
||||
d.newVMSessionLogsCommand(),
|
||||
d.newVMSessionStopCommand(),
|
||||
d.newVMSessionKillCommand(),
|
||||
d.newVMSessionAttachCommand(),
|
||||
d.newVMSessionSendCommand(),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newVMSessionStartCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionStartCommand() *cobra.Command {
|
||||
var name string
|
||||
var cwd string
|
||||
var stdinMode string
|
||||
|
|
@ -47,13 +47,13 @@ func newVMSessionStartCommand() *cobra.Command {
|
|||
Short: "Start a managed guest command",
|
||||
Long: "Start a daemon-managed guest command. The daemon verifies that the guest working directory exists and that the requested command is present in guest PATH before launch. Use --stdin-mode pipe when you need live attach.",
|
||||
Args: minArgsUsage(2, "usage: banger vm session start <id-or-name> [flags] -- <command> [args...]"),
|
||||
ValidArgsFunction: completeVMNameOnlyAtPos0,
|
||||
ValidArgsFunction: d.completeVMNameOnlyAtPos0,
|
||||
Example: strings.TrimSpace(`
|
||||
banger vm session start devbox --name planner --cwd /root/repo --stdin-mode pipe --require-command git -- pi --mode rpc --no-session
|
||||
banger vm session start devbox --name shell --stdin-mode pipe -- bash -lc 'exec bash'
|
||||
`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
layout, _, err := ensureDaemon(cmd.Context())
|
||||
layout, _, err := d.ensureDaemon(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ func newVMSessionStartCommand() *cobra.Command {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := guestSessionStartFunc(cmd.Context(), layout.SocketPath, api.GuestSessionStartParams{
|
||||
result, err := d.guestSessionStart(cmd.Context(), layout.SocketPath, api.GuestSessionStartParams{
|
||||
VMIDOrName: args[0],
|
||||
Name: name,
|
||||
Command: args[1],
|
||||
|
|
@ -97,19 +97,19 @@ func newVMSessionStartCommand() *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func newVMSessionListCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionListCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list <id-or-name>",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List managed guest commands for a VM",
|
||||
Args: exactArgsUsage(1, "usage: banger vm session list <id-or-name>"),
|
||||
ValidArgsFunction: completeVMNameOnlyAtPos0,
|
||||
ValidArgsFunction: d.completeVMNameOnlyAtPos0,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
layout, _, err := ensureDaemon(cmd.Context())
|
||||
layout, _, err := d.ensureDaemon(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := guestSessionListFunc(cmd.Context(), layout.SocketPath, args[0])
|
||||
result, err := d.guestSessionList(cmd.Context(), layout.SocketPath, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -118,18 +118,18 @@ func newVMSessionListCommand() *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func newVMSessionShowCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionShowCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "show <id-or-name> <session>",
|
||||
Short: "Show managed guest command details",
|
||||
Args: exactArgsUsage(2, "usage: banger vm session show <id-or-name> <session>"),
|
||||
ValidArgsFunction: completeSessionNames,
|
||||
ValidArgsFunction: d.completeSessionNames,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
layout, _, err := ensureDaemon(cmd.Context())
|
||||
layout, _, err := d.ensureDaemon(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := guestSessionGetFunc(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
|
||||
result, err := d.guestSessionGet(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -138,20 +138,20 @@ func newVMSessionShowCommand() *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func newVMSessionLogsCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionLogsCommand() *cobra.Command {
|
||||
var stream string
|
||||
var tailLines int
|
||||
cmd := &cobra.Command{
|
||||
Use: "logs <id-or-name> <session>",
|
||||
Short: "Show stdout or stderr for a guest session",
|
||||
Args: exactArgsUsage(2, "usage: banger vm session logs [--stream stdout|stderr] [-n LINES] <id-or-name> <session>"),
|
||||
ValidArgsFunction: completeSessionNames,
|
||||
ValidArgsFunction: d.completeSessionNames,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
layout, _, err := ensureDaemon(cmd.Context())
|
||||
layout, _, err := d.ensureDaemon(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := guestSessionLogsFunc(cmd.Context(), layout.SocketPath, api.GuestSessionLogsParams{VMIDOrName: args[0], SessionIDOrName: args[1], Stream: stream, TailLines: tailLines})
|
||||
result, err := d.guestSessionLogs(cmd.Context(), layout.SocketPath, api.GuestSessionLogsParams{VMIDOrName: args[0], SessionIDOrName: args[1], Stream: stream, TailLines: tailLines})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -164,18 +164,18 @@ func newVMSessionLogsCommand() *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func newVMSessionStopCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionStopCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "stop <id-or-name> <session>",
|
||||
Short: "Send SIGTERM to a guest session",
|
||||
Args: exactArgsUsage(2, "usage: banger vm session stop <id-or-name> <session>"),
|
||||
ValidArgsFunction: completeSessionNames,
|
||||
ValidArgsFunction: d.completeSessionNames,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
layout, _, err := ensureDaemon(cmd.Context())
|
||||
layout, _, err := d.ensureDaemon(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := guestSessionStopFunc(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
|
||||
result, err := d.guestSessionStop(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -184,18 +184,18 @@ func newVMSessionStopCommand() *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func newVMSessionKillCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionKillCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "kill <id-or-name> <session>",
|
||||
Short: "Send SIGKILL to a guest session",
|
||||
Args: exactArgsUsage(2, "usage: banger vm session kill <id-or-name> <session>"),
|
||||
ValidArgsFunction: completeSessionNames,
|
||||
ValidArgsFunction: d.completeSessionNames,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
layout, _, err := ensureDaemon(cmd.Context())
|
||||
layout, _, err := d.ensureDaemon(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := guestSessionKillFunc(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
|
||||
result, err := d.guestSessionKill(cmd.Context(), layout.SocketPath, api.GuestSessionRefParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -204,19 +204,19 @@ func newVMSessionKillCommand() *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func newVMSessionAttachCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionAttachCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "attach <id-or-name> <session>",
|
||||
Short: "Attach local stdio to an attachable guest session",
|
||||
Long: "Attach local stdio to a pipe-mode session through a daemon-created local Unix socket bridge. Only one active attach is allowed at a time, and the client must run on the same host as the daemon.",
|
||||
Args: exactArgsUsage(2, "usage: banger vm session attach <id-or-name> <session>"),
|
||||
ValidArgsFunction: completeSessionNames,
|
||||
ValidArgsFunction: d.completeSessionNames,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
layout, _, err := ensureDaemon(cmd.Context())
|
||||
layout, _, err := d.ensureDaemon(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := guestSessionAttachBeginFunc(cmd.Context(), layout.SocketPath, api.GuestSessionAttachBeginParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
|
||||
result, err := d.guestSessionAttachBegin(cmd.Context(), layout.SocketPath, api.GuestSessionAttachBeginParams{VMIDOrName: args[0], SessionIDOrName: args[1]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -229,21 +229,21 @@ func newVMSessionAttachCommand() *cobra.Command {
|
|||
}
|
||||
}
|
||||
|
||||
func newVMSessionSendCommand() *cobra.Command {
|
||||
func (d *deps) newVMSessionSendCommand() *cobra.Command {
|
||||
var message string
|
||||
cmd := &cobra.Command{
|
||||
Use: "send <id-or-name> <session>",
|
||||
Short: "Write bytes to a running guest session's stdin pipe",
|
||||
Long: "Write a payload to the stdin pipe of a running pipe-mode guest session without holding the exclusive attach. Use --message for an inline JSONL string, or pipe bytes via stdin when --message is omitted. A trailing newline is appended to --message values that lack one.",
|
||||
Args: exactArgsUsage(2, "usage: banger vm session send <id-or-name> <session> [--message '<json>']"),
|
||||
ValidArgsFunction: completeSessionNames,
|
||||
ValidArgsFunction: d.completeSessionNames,
|
||||
Example: strings.TrimSpace(`
|
||||
banger vm session send devbox planner --message '{"type":"abort"}'
|
||||
banger vm session send devbox planner --message '{"type":"steer","message":"Focus on src/"}'
|
||||
echo '{"type":"prompt","prompt":"Summarize."}' | banger vm session send devbox planner
|
||||
`),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
layout, _, err := ensureDaemon(cmd.Context())
|
||||
layout, _, err := d.ensureDaemon(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ func newVMSessionSendCommand() *cobra.Command {
|
|||
return fmt.Errorf("read stdin: %w", err)
|
||||
}
|
||||
}
|
||||
result, err := guestSessionSendFunc(cmd.Context(), layout.SocketPath, api.GuestSessionSendParams{
|
||||
result, err := d.guestSessionSend(cmd.Context(), layout.SocketPath, api.GuestSessionSendParams{
|
||||
VMIDOrName: args[0],
|
||||
SessionIDOrName: args[1],
|
||||
Payload: payload,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue