feat: add subtrace-token flag for development tracing

Add internal --subtrace-token flag to enable running sketch under
subtrace.dev for development purposes. Run with --skaband-addr= for max
happiness.

Subtrace manages to trace both incoming and outgoing traffic (e.g., to
Anthropic), so it can be used.

Beware that using this may leak your anthropic keys to subtrace.dev.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s3521ac3b805c857fk
diff --git a/cmd/sketch/main.go b/cmd/sketch/main.go
index 9d8023c..a2c9f52 100644
--- a/cmd/sketch/main.go
+++ b/cmd/sketch/main.go
@@ -217,6 +217,7 @@
 	outsideHTTP         string
 	branchPrefix        string
 	sshConnectionString string
+	subtraceToken       string
 }
 
 // parseCLIFlags parses all command-line flags and returns a CLIFlags struct
@@ -276,6 +277,7 @@
 
 	// Internal flags for development/debugging
 	internalFlags.StringVar(&flags.dumpDist, "dump-dist", "", "(internal) dump embedded /dist/ filesystem to specified directory and exit")
+	internalFlags.StringVar(&flags.subtraceToken, "subtrace-token", "", "(development) run sketch under subtrace.dev with the provided token")
 
 	// Custom usage function that shows only user-visible flags by default
 	userFlags.Usage = func() {
@@ -415,6 +417,7 @@
 		MaxDollars:     flags.maxDollars,
 		BranchPrefix:   flags.branchPrefix,
 		LinkToGitHub:   flags.linkToGitHub,
+		SubtraceToken:  flags.subtraceToken,
 	}
 
 	if err := dockerimg.LaunchContainer(ctx, config); err != nil {
diff --git a/dockerimg/dockerimg.go b/dockerimg/dockerimg.go
index e708745..e207904 100644
--- a/dockerimg/dockerimg.go
+++ b/dockerimg/dockerimg.go
@@ -124,6 +124,9 @@
 
 	// LinkToGitHub enables GitHub branch linking in UI
 	LinkToGitHub bool
+
+	// SubtraceToken enables running sketch under subtrace.dev (development only)
+	SubtraceToken string
 }
 
 // LaunchContainer creates a docker container for a project, installs sketch and opens a connection to it.
@@ -254,6 +257,14 @@
 
 	fmt.Printf("📦 running in container %s\n", cntrName)
 
+	// Setup subtrace if token is provided (development only) - after container creation, before start
+	if config.SubtraceToken != "" {
+		fmt.Println("🔍 Setting up subtrace (development only)")
+		if err := setupSubtraceBeforeStart(ctx, cntrName, config.SubtraceToken); err != nil {
+			return fmt.Errorf("failed to setup subtrace: %w", err)
+		}
+	}
+
 	// Start the sketch container
 	if out, err := combinedOutput(ctx, "docker", "start", cntrName); err != nil {
 		return fmt.Errorf("docker start: %s, %w", out, err)
@@ -543,16 +554,29 @@
 	}
 	cmdArgs = append(cmdArgs, "--security-opt", "seccomp="+seccompPath)
 
+	// Add subtrace environment variable if token is provided
+	if config.SubtraceToken != "" {
+		cmdArgs = append(cmdArgs, "-e", "SUBTRACE_TOKEN="+config.SubtraceToken)
+		cmdArgs = append(cmdArgs, "-e", "SUBTRACE_HTTP2=1")
+	}
+
 	// Add volume mounts if specified
 	for _, mount := range config.Mounts {
 		if mount != "" {
 			cmdArgs = append(cmdArgs, "-v", mount)
 		}
 	}
-	cmdArgs = append(
-		cmdArgs,
-		imgName,
-		"/bin/sketch",
+	cmdArgs = append(cmdArgs, imgName)
+
+	// Add command: either [sketch] or [subtrace run -- sketch]
+	if config.SubtraceToken != "" {
+		cmdArgs = append(cmdArgs, "/usr/local/bin/subtrace", "run", "--", "/bin/sketch")
+	} else {
+		cmdArgs = append(cmdArgs, "/bin/sketch")
+	}
+
+	// Add all sketch arguments
+	cmdArgs = append(cmdArgs,
 		"-unsafe",
 		"-addr=:80",
 		"-session-id="+config.SessionID,
diff --git a/dockerimg/subtrace.go b/dockerimg/subtrace.go
new file mode 100644
index 0000000..c6433b0
--- /dev/null
+++ b/dockerimg/subtrace.go
@@ -0,0 +1,88 @@
+package dockerimg
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"path/filepath"
+	"runtime"
+)
+
+// downloadSubtrace downloads the subtrace binary for the given architecture
+// and caches it in ~/.cache/sketch/subtrace-{platform}-{arch}
+func downloadSubtrace(ctx context.Context, platform, arch string) (string, error) {
+	_ = ctx // ctx is reserved for future timeout/cancellation support
+	homeDir, err := os.UserHomeDir()
+	if err != nil {
+		return "", fmt.Errorf("failed to get home directory: %w", err)
+	}
+
+	cacheDir := filepath.Join(homeDir, ".cache", "sketch")
+	if err := os.MkdirAll(cacheDir, 0o755); err != nil {
+		return "", fmt.Errorf("failed to create cache directory: %w", err)
+	}
+
+	binaryName := fmt.Sprintf("subtrace-%s-%s", platform, arch)
+	binaryPath := filepath.Join(cacheDir, binaryName)
+
+	// Check if the binary already exists
+	if _, err := os.Stat(binaryPath); err == nil {
+		// Binary exists, return the path
+		return binaryPath, nil
+	}
+
+	// Download the binary
+	downloadURL := fmt.Sprintf("https://subtrace.dev/download/latest/%s/%s/subtrace", platform, arch)
+
+	resp, err := http.Get(downloadURL)
+	if err != nil {
+		return "", fmt.Errorf("failed to download subtrace from %s: %w", downloadURL, err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("failed to download subtrace: HTTP %d", resp.StatusCode)
+	}
+
+	// Create the binary file
+	file, err := os.Create(binaryPath)
+	if err != nil {
+		return "", fmt.Errorf("failed to create subtrace binary file: %w", err)
+	}
+	defer file.Close()
+
+	// Copy the downloaded content to the file
+	_, err = io.Copy(file, resp.Body)
+	if err != nil {
+		return "", fmt.Errorf("failed to write subtrace binary: %w", err)
+	}
+
+	// Make the binary executable
+	if err := os.Chmod(binaryPath, 0o755); err != nil {
+		return "", fmt.Errorf("failed to make subtrace binary executable: %w", err)
+	}
+
+	return binaryPath, nil
+}
+
+// setupSubtraceBeforeStart downloads subtrace and uploads it to the container before it starts
+func setupSubtraceBeforeStart(ctx context.Context, cntrName, subtraceToken string) error {
+	if subtraceToken == "" {
+		return nil
+	}
+
+	// Download subtrace binary
+	subtracePath, err := downloadSubtrace(ctx, "linux", runtime.GOARCH)
+	if err != nil {
+		return fmt.Errorf("failed to download subtrace: %w", err)
+	}
+
+	// Copy subtrace binary to the container (container exists but isn't started)
+	if out, err := combinedOutput(ctx, "docker", "cp", subtracePath, cntrName+":/usr/local/bin/subtrace"); err != nil {
+		return fmt.Errorf("failed to copy subtrace to container: %s: %w", out, err)
+	}
+
+	return nil
+}