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/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
+}