Commit 2ffecac8bc37

Vincent Demeester <vincent@sbr.pm>
2026-01-15 10:05:47
feat(shpool-remote): add CLI tool for managing remote shpool sessions
Add shpool-remote, a small Go utility to manage shpool sessions on remote hosts: - list: List all sessions on a remote host - detach: Detach a session (keeps it running) - kill: Kill a session (terminates it) - attach: Attach to a session (uses SSH shpool-ssh-wrapper integration) The tool is a thin wrapper around shpool commands executed over SSH, making it easier to manage remote sessions without manually constructing SSH commands. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent e4bd4c8
Changed files (6)
pkgs/default.nix
@@ -13,6 +13,7 @@ in
   nixfmt-plus = pkgs.callPackage ./nixfmt-plus.nix { };
   scripts = pkgs.callPackage ./my/scripts { };
   shpool-ssh-wrapper = pkgs.callPackage ../tools/shpool-ssh-wrapper { };
+  shpool-remote = pkgs.callPackage ../tools/shpool-remote { };
   vrsync = pkgs.callPackage ./my/vrsync { };
   vde-thinkpad = pkgs.callPackage ./my/vde-thinkpad { };
   battery-monitor = pkgs.callPackage ../tools/battery-monitor { };
systems/kyushu/home.nix
@@ -73,6 +73,7 @@ in
     arr
     claude-hooks
     toggle-color-scheme
+    shpool-remote
   ];
 
   # Automatic color scheme switching
tools/shpool-remote/default.nix
@@ -0,0 +1,16 @@
+{ pkgs }:
+
+pkgs.buildGoModule {
+  pname = "shpool-remote";
+  version = "0.1.0";
+
+  src = ./.;
+
+  vendorHash = null;
+
+  meta = with pkgs.lib; {
+    description = "Manage shpool sessions on remote hosts";
+    license = licenses.asl20;
+    maintainers = [ ];
+  };
+}
tools/shpool-remote/go.mod
@@ -0,0 +1,3 @@
+module shpool-remote
+
+go 1.23
tools/shpool-remote/main.go
@@ -0,0 +1,120 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"strings"
+)
+
+const usage = `shpool-remote - Manage shpool sessions on remote hosts
+
+Usage:
+  shpool-remote <host> list                 List all sessions on host
+  shpool-remote <host> detach <session>     Detach a session
+  shpool-remote <host> kill <session>       Kill a session
+  shpool-remote <host> attach <session>     Attach to a session
+
+Examples:
+  shpool-remote rhea.home list
+  shpool-remote rhea.home detach mywork
+  shpool-remote rhea.home kill old-session
+  shpool-remote rhea.home attach dev
+`
+
+func main() {
+	if len(os.Args) < 3 {
+		fmt.Fprint(os.Stderr, usage)
+		os.Exit(1)
+	}
+
+	host := os.Args[1]
+	command := os.Args[2]
+
+	switch command {
+	case "list":
+		if err := runList(host); err != nil {
+			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+			os.Exit(1)
+		}
+	case "detach":
+		if len(os.Args) < 4 {
+			fmt.Fprintln(os.Stderr, "Error: session name required for detach")
+			os.Exit(1)
+		}
+		if err := runDetach(host, os.Args[3]); err != nil {
+			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+			os.Exit(1)
+		}
+	case "kill":
+		if len(os.Args) < 4 {
+			fmt.Fprintln(os.Stderr, "Error: session name required for kill")
+			os.Exit(1)
+		}
+		if err := runKill(host, os.Args[3]); err != nil {
+			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+			os.Exit(1)
+		}
+	case "attach":
+		if len(os.Args) < 4 {
+			fmt.Fprintln(os.Stderr, "Error: session name required for attach")
+			os.Exit(1)
+		}
+		if err := runAttach(host, os.Args[3]); err != nil {
+			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+			os.Exit(1)
+		}
+	default:
+		fmt.Fprintf(os.Stderr, "Error: unknown command '%s'\n\n", command)
+		fmt.Fprint(os.Stderr, usage)
+		os.Exit(1)
+	}
+}
+
+func runList(host string) error {
+	cmd := exec.Command("ssh", host, "shpool", "list")
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	return cmd.Run()
+}
+
+func runDetach(host, session string) error {
+	cmd := exec.Command("ssh", host, "shpool", "detach", session)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		return err
+	}
+	fmt.Printf("Session '%s' detached on %s\n", session, host)
+	return nil
+}
+
+func runKill(host, session string) error {
+	cmd := exec.Command("ssh", host, "shpool", "kill", session)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if err := cmd.Run(); err != nil {
+		return err
+	}
+	fmt.Printf("Session '%s' killed on %s\n", session, host)
+	return nil
+}
+
+func runAttach(host, session string) error {
+	// For attach, we need an interactive SSH session
+	// Use ssh host/session pattern which uses the shpool-ssh-wrapper
+	fullSession := fmt.Sprintf("%s/%s", host, session)
+	cmd := exec.Command("ssh", fullSession)
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	return cmd.Run()
+}
+
+// runCommand executes a remote shpool command and returns the output
+func runCommand(host string, args ...string) (string, error) {
+	cmdArgs := append([]string{host, "shpool"}, args...)
+	cmd := exec.Command("ssh", cmdArgs...)
+	output, err := cmd.CombinedOutput()
+	return strings.TrimSpace(string(output)), err
+}
tools/shpool-remote/README.md
@@ -0,0 +1,62 @@
+# shpool-remote
+
+Manage shpool sessions on remote hosts.
+
+## Usage
+
+```bash
+# List all sessions on a host
+shpool-remote <host> list
+
+# Detach a session (keeps it running)
+shpool-remote <host> detach <session>
+
+# Kill a session (terminates it)
+shpool-remote <host> kill <session>
+
+# Attach to a session (interactive)
+shpool-remote <host> attach <session>
+```
+
+## Examples
+
+```bash
+# List all sessions on rhea
+shpool-remote rhea.home list
+
+# Detach the "dev" session on rhea
+shpool-remote rhea.home detach dev
+
+# Kill an old session
+shpool-remote rhea.home kill old-work
+
+# Attach to a session (uses SSH shpool-ssh-wrapper integration)
+shpool-remote rhea.home attach myproject
+```
+
+## How it works
+
+This tool is a thin wrapper around `shpool` commands executed over SSH. It simplifies managing remote shpool sessions without having to manually construct SSH commands.
+
+For the `attach` command, it uses the SSH host/session pattern which integrates with `shpool-ssh-wrapper` from your SSH config.
+
+## Building
+
+```bash
+# Build with Nix
+nix build .#shpool-remote
+
+# Or build with Go
+cd tools/shpool-remote
+go build
+```
+
+## Installation
+
+The package is available in the flake outputs:
+
+```nix
+home.packages = with pkgs; [
+  shpool-remote
+];
+```