├── .editorconfig ├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .tool-versions ├── .vscode └── settings.json ├── Dockerfile ├── README.md ├── cmd └── mcp-launcher │ └── main.go ├── cspell.config.yaml ├── go.mod ├── go.sum ├── internal └── contexts │ ├── logger.go │ └── metadata.go ├── manifests └── mcp-servers │ ├── browserbase.github.io │ └── mc-server-browserbase.yaml │ └── fatwang2.github.io │ └── search1api-mcp.yaml └── pkg ├── jsonpatch ├── jsonpatch.go └── jsonpatch_test.go ├── manifests └── manifests.go └── utils └── mo.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.proto] 4 | indent_size = 2 5 | indent_style = space 6 | insert_final_newline = true 7 | 8 | [*.yaml] 9 | indent_size = 2 10 | indent_style = space 11 | insert_final_newline = true 12 | 13 | [*.go] 14 | charset = utf-8 15 | indent_style = tab 16 | indent_size = 4 17 | end_of_line = lf 18 | insert_final_newline = true 19 | trim_trailing_whitespace = true 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | STORE_PATH: "" 13 | 14 | jobs: 15 | build-test: 16 | name: Build Test 17 | runs-on: "ubuntu-latest" 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version: "stable" 26 | cache: true 27 | 28 | - name: Test Build 29 | run: go build ./... 30 | 31 | lint: 32 | name: Lint 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - uses: actions/setup-go@v5 38 | with: 39 | go-version: "stable" 40 | cache: true 41 | 42 | - name: golangci-lint 43 | uses: golangci/golangci-lint-action@v3.4.0 44 | with: 45 | # Optional: golangci-lint command line arguments. 46 | args: "--timeout=10m" 47 | 48 | unittest: 49 | name: Unit Test 50 | runs-on: ubuntu-latest 51 | 52 | steps: 53 | - uses: actions/checkout@v4 54 | 55 | - name: Setup Go 56 | uses: actions/setup-go@v5 57 | with: 58 | go-version: "stable" 59 | cache: true 60 | 61 | - name: Unit tests 62 | run: | 63 | go test ./... -coverprofile=coverage.out -covermode=atomic -p=1 64 | go tool cover -func coverage.out 65 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | workflow_dispatch: 8 | 9 | concurrency: ${{ github.workflow }}-${{ github.ref }} 10 | 11 | jobs: 12 | publish: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: stable 21 | # https://goreleaser.com/ci/actions/#workflow 22 | - uses: goreleaser/goreleaser-action@v6 23 | with: 24 | distribution: goreleaser 25 | version: latest 26 | args: release --clean 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | docker: 30 | runs-on: ubuntu-latest 31 | needs: 32 | - publish 33 | permissions: 34 | packages: write 35 | contents: read 36 | attestations: write 37 | id-token: write 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: docker/metadata-action@v5 41 | id: meta 42 | with: 43 | images: ghcr.io/${{ github.repository }} 44 | flavor: | 45 | latest=true 46 | tags: | 47 | type=semver,pattern={{version}} 48 | type=semver,pattern={{major}}.{{minor}} 49 | type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} 50 | - uses: docker/setup-qemu-action@v3 51 | - uses: docker/setup-buildx-action@v3 52 | - uses: docker/login-action@v3 53 | with: 54 | registry: ghcr.io 55 | username: ${{ github.repository_owner }} 56 | password: ${{ secrets.GITHUB_TOKEN }} 57 | - uses: docker/build-push-action@v6 58 | with: 59 | platforms: linux/amd64,linux/arm64 60 | push: true 61 | tags: ${{ steps.meta.outputs.tags }} 62 | labels: ${{ steps.meta.outputs.labels }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | # .idea file 28 | /.idea/* 29 | !/.idea/.gitignore 30 | !/.idea/codeStyles 31 | 32 | # build result 33 | /result 34 | 35 | node_modules 36 | .eslintcache 37 | sdk/typescript/pkg/ 38 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: all 4 | disable: 5 | - containedctx 6 | - contextcheck 7 | - cyclop 8 | - depguard 9 | - err113 10 | - exhaustruct 11 | - funlen 12 | - gochecknoglobals 13 | - gochecknoinits 14 | - gocognit 15 | - gocyclo 16 | - godot 17 | - godox 18 | - ireturn 19 | - lll 20 | - maintidx 21 | - nilnil 22 | - nlreturn 23 | - paralleltest 24 | - tagalign 25 | - tagliatelle 26 | - testpackage 27 | - varnamelen 28 | - wrapcheck 29 | settings: 30 | dupl: 31 | threshold: 600 32 | gocritic: 33 | disabled-checks: 34 | - ifElseChain 35 | gosec: 36 | excludes: 37 | - G115 38 | mnd: 39 | ignored-files: 40 | - examples/.* 41 | ignored-functions: 42 | - context.WithTimeout 43 | - strconv.ParseComplex 44 | nestif: 45 | min-complexity: 15 46 | revive: 47 | rules: 48 | - name: blank-imports 49 | disabled: true 50 | wsl: 51 | strict-append: false 52 | allow-assign-and-call: false 53 | allow-trailing-comment: true 54 | allow-separated-leading-comment: true 55 | allow-cuddle-declarations: true 56 | exclusions: 57 | generated: lax 58 | presets: 59 | - comments 60 | - common-false-positives 61 | - legacy 62 | - std-error-handling 63 | rules: 64 | - linters: 65 | - perfsprint 66 | path: _test\.go 67 | - path: (.+)\.go$ 68 | text: if statements should only be cuddled with assignments 69 | - path: (.+)\.go$ 70 | text: if statements should only be cuddled with assignments used in the if statement itself 71 | - path: (.+)\.go$ 72 | text: assignments should only be cuddled with other assignments 73 | paths: 74 | - third_party$ 75 | - builtin$ 76 | - examples$ 77 | formatters: 78 | enable: 79 | - gofmt 80 | - goimports 81 | exclusions: 82 | generated: lax 83 | paths: 84 | - third_party$ 85 | - builtin$ 86 | - examples$ 87 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: kollama 2 | release: 3 | github: 4 | owner: nekomeowww 5 | name: ollama-operator 6 | builds: 7 | - id: kollama 8 | goos: 9 | - linux 10 | - windows 11 | - darwin 12 | goarch: 13 | - arm64 14 | - amd64 15 | - "386" 16 | env: 17 | - CGO_ENABLED=0 18 | - GO111MODULE=on 19 | main: cmd/kollama/main.go 20 | archives: 21 | - id: kollama 22 | builds: 23 | - kollama 24 | name_template: "{{ .ProjectName }}_{{ .Tag }}_{{ .Os }}_{{ .Arch }}" 25 | format_overrides: 26 | - goos: windows 27 | format: zip 28 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.24.0 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Disable the default formatter, use eslint instead 3 | "prettier.enable": false, 4 | "editor.formatOnSave": false, 5 | // Auto fix 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll.eslint": "explicit", 8 | "source.organizeImports": "never" 9 | }, 10 | // Silent the stylistic rules in you IDE, but still auto fix them 11 | "eslint.rules.customizations": [ 12 | { 13 | "rule": "style/*", 14 | "severity": "off", 15 | "fixable": true 16 | }, 17 | { 18 | "rule": "format/*", 19 | "severity": "off", 20 | "fixable": true 21 | }, 22 | { 23 | "rule": "*-indent", 24 | "severity": "off", 25 | "fixable": true 26 | }, 27 | { 28 | "rule": "*-spacing", 29 | "severity": "off", 30 | "fixable": true 31 | }, 32 | { 33 | "rule": "*-spaces", 34 | "severity": "off", 35 | "fixable": true 36 | }, 37 | { 38 | "rule": "*-order", 39 | "severity": "off", 40 | "fixable": true 41 | }, 42 | { 43 | "rule": "*-dangle", 44 | "severity": "off", 45 | "fixable": true 46 | }, 47 | { 48 | "rule": "*-newline", 49 | "severity": "off", 50 | "fixable": true 51 | }, 52 | { 53 | "rule": "*quotes", 54 | "severity": "off", 55 | "fixable": true 56 | }, 57 | { 58 | "rule": "*semi", 59 | "severity": "off", 60 | "fixable": true 61 | } 62 | ], 63 | // Enable eslint for all supported languages 64 | "eslint.validate": [ 65 | "javascript", 66 | "javascriptreact", 67 | "typescript", 68 | "typescriptreact", 69 | "vue", 70 | "html", 71 | "markdown", 72 | "json", 73 | "json5", 74 | "jsonc", 75 | "yaml", 76 | "toml", 77 | "xml", 78 | "gql", 79 | "graphql", 80 | "astro", 81 | "css", 82 | "less", 83 | "scss", 84 | "pcss", 85 | "postcss" 86 | ], 87 | "go.useLanguageServer": true, 88 | "go.lintOnSave": "package", 89 | "go.vetOnSave": "workspace", 90 | "go.coverOnSave": false, 91 | "go.lintTool": "golangci-lint", 92 | "go.lintFlags": [ 93 | "--config=${workspaceFolder}/.golangci.yml", 94 | ], 95 | "go.inferGopath": true, 96 | "go.alternateTools": {}, 97 | "go.coverOnSingleTest": true, 98 | "go.testTimeout": "900s", 99 | "go.testFlags": [ 100 | "-v", 101 | "-count=1" 102 | ], 103 | "go.toolsManagement.autoUpdate": true, 104 | "go.coverOnSingleTestFile": true, 105 | "gopls": { 106 | "build.buildFlags": [], 107 | "ui.completion.usePlaceholders": true, 108 | "ui.semanticTokens": true 109 | }, 110 | "[go]": { 111 | "editor.insertSpaces": false, 112 | "editor.formatOnSave": true, 113 | "editor.codeActionsOnSave": { 114 | "source.organizeImports": "always" 115 | }, 116 | }, 117 | } 118 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 AS builder 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | WORKDIR /app 7 | 8 | COPY go.mod go.sum ./ 9 | 10 | RUN go mod download 11 | 12 | COPY . . 13 | 14 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o ./result/mcp-launcher ./cmd/mcp-launcher 15 | 16 | # https://github.com/GoogleContainerTools/distroless 17 | FROM gcr.io/distroless/static-debian12 AS app 18 | 19 | COPY --from=builder /app/result/mcp-launcher / 20 | 21 | ENTRYPOINT ["/mcp-launcher"] 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `mcp-launcher` 2 | 3 | ## Development 4 | 5 | ``` 6 | go run ./cmd/mcp-launcher https://github.com/browserbase/mcp-server-browserbase -d stagehand 7 | ``` 8 | -------------------------------------------------------------------------------- /cmd/mcp-launcher/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "log/slog" 10 | "os" 11 | 12 | "os/exec" 13 | "path/filepath" 14 | "strings" 15 | "time" 16 | 17 | "github.com/docker/docker/api/types/container" 18 | "github.com/docker/docker/api/types/network" 19 | dockerclient "github.com/docker/docker/client" 20 | dockerfile "github.com/flexstack/new-dockerfile" 21 | "github.com/go-git/go-git/v5" 22 | "github.com/lmittmann/tint" 23 | "github.com/moby/buildkit/client" 24 | "github.com/moeru-ai/mcp-launcher/internal/contexts" 25 | "github.com/moeru-ai/mcp-launcher/pkg/manifests" 26 | "github.com/nekomeowww/xo" 27 | v1 "github.com/opencontainers/image-spec/specs-go/v1" 28 | 29 | "github.com/samber/lo" 30 | "github.com/spf13/cobra" 31 | ) 32 | 33 | func getXDGDataHome() string { 34 | if xdg := os.Getenv("XDG_DATA_HOME"); xdg != "" { 35 | return xdg 36 | } 37 | 38 | home, err := os.UserHomeDir() 39 | if err != nil { 40 | return "" 41 | } 42 | 43 | return filepath.Join(home, ".local", "share") 44 | } 45 | 46 | func parseRepoURL(url string) (string, error) { 47 | // Handle both HTTPS and SSH URLs 48 | if strings.HasPrefix(url, "https://github.com/") { 49 | return strings.TrimPrefix(url, "https://"), nil 50 | } 51 | if strings.HasPrefix(url, "git@github.com:") { 52 | path := strings.TrimPrefix(url, "git@github.com:") 53 | return "github.com/" + strings.TrimSuffix(path, ".git"), nil 54 | } 55 | 56 | return "", fmt.Errorf("unsupported repository URL format: %s", url) 57 | } 58 | 59 | func isWorkingTreeClean(repo *git.Repository) (bool, error) { 60 | w, err := repo.Worktree() 61 | if err != nil { 62 | return false, fmt.Errorf("failed to get worktree: %w", err) 63 | } 64 | 65 | status, err := w.Status() 66 | if err != nil { 67 | return false, fmt.Errorf("failed to get worktree status: %w", err) 68 | } 69 | 70 | return status.IsClean(), nil 71 | } 72 | 73 | func resetHard(repo *git.Repository) error { 74 | w, err := repo.Worktree() 75 | if err != nil { 76 | return fmt.Errorf("failed to get worktree: %w", err) 77 | } 78 | 79 | // Get HEAD reference 80 | ref, err := repo.Head() 81 | if err != nil { 82 | return fmt.Errorf("failed to get HEAD reference: %w", err) 83 | } 84 | 85 | // Reset to HEAD 86 | err = w.Reset(&git.ResetOptions{ 87 | Commit: ref.Hash(), 88 | Mode: git.HardReset, 89 | }) 90 | if err != nil { 91 | return fmt.Errorf("failed to reset to HEAD: %w", err) 92 | } 93 | 94 | return nil 95 | } 96 | 97 | func pullLatest(repo *git.Repository) error { 98 | w, err := repo.Worktree() 99 | if err != nil { 100 | return fmt.Errorf("failed to get worktree: %w", err) 101 | } 102 | 103 | err = w.Pull(&git.PullOptions{ 104 | RemoteName: "origin", 105 | Progress: os.Stdout, 106 | Force: false, 107 | }) 108 | if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { 109 | if err.Error() != "object not found" { 110 | return fmt.Errorf("failed to pull latest changes: %w", err) 111 | } 112 | 113 | err = repo.Fetch(&git.FetchOptions{ 114 | RemoteName: "origin", 115 | Progress: os.Stdout, 116 | Force: false, 117 | }) 118 | if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { 119 | return fmt.Errorf("failed to fetch latest changes: %w", err) 120 | } 121 | 122 | return nil 123 | } 124 | 125 | return nil 126 | } 127 | 128 | func cloneRepository(ctx context.Context, repoURL string) (string, error) { 129 | log := contexts.SlogFrom(ctx) 130 | 131 | log.Debug("preparing to clone repository", slog.String("url", repoURL)) 132 | 133 | repoPath, err := parseRepoURL(repoURL) 134 | if err != nil { 135 | return "", fmt.Errorf("failed to parse repository URL: %w", err) 136 | } 137 | 138 | log.Debug("parsed repository URL", slog.String("path", repoPath)) 139 | 140 | // Create the full path for the repository 141 | targetPath := filepath.Join(getXDGDataHome(), "mcp-launcher", "servers", "source", repoPath) 142 | 143 | log.Debug("creating directory to store repository", slog.String("path", targetPath)) 144 | 145 | // Ensure the parent directory exists 146 | err = os.MkdirAll(filepath.Dir(targetPath), 0755) //nolint:mnd 147 | if err != nil { 148 | return "", fmt.Errorf("failed to create directory: %w", err) 149 | } 150 | 151 | log.Debug("directory created", slog.String("path", targetPath)) 152 | 153 | var repo *git.Repository 154 | 155 | // Check if repository already exists 156 | stat, err := os.Stat(filepath.Join(targetPath, ".git")) 157 | if err == nil { 158 | if stat.IsDir() { 159 | log.Debug("existing repository found", slog.String("path", targetPath)) 160 | 161 | // Repository exists, open it 162 | repo, err = git.PlainOpen(targetPath) 163 | if err != nil { 164 | return "", fmt.Errorf("failed to open existing repository: %w", err) 165 | } 166 | 167 | log.Debug("repository opened", slog.String("path", targetPath)) 168 | 169 | // Check if working tree is clean 170 | clean, err := isWorkingTreeClean(repo) 171 | if err != nil { 172 | return "", fmt.Errorf("failed to check if working tree is clean: %w", err) 173 | } 174 | 175 | log.Debug("checked if working tree is clean", slog.Bool("clean", clean)) 176 | 177 | if !clean { 178 | // Instead of stashing (which isn't supported by go-git), we'll reset to HEAD 179 | if err := resetHard(repo); err != nil { 180 | return "", fmt.Errorf("failed to reset to HEAD: %w", err) 181 | } 182 | } 183 | 184 | log.Debug("pulling latest changes") 185 | 186 | if err := pullLatest(repo); err != nil { 187 | return "", fmt.Errorf("failed to pull latest changes: %w", err) 188 | } 189 | 190 | log.Debug("pulled latest changes") 191 | 192 | return targetPath, nil 193 | } else { 194 | return "", fmt.Errorf("existing repository contains invalid file(s): %s, .git is a file", targetPath) 195 | } 196 | } 197 | 198 | // Clone new repository 199 | cloneOpts := &git.CloneOptions{ 200 | URL: repoURL, 201 | Progress: os.Stdout, 202 | Depth: 1, 203 | } 204 | 205 | _, err = git.PlainClone(targetPath, false, cloneOpts) 206 | if err != nil { 207 | return "", fmt.Errorf("failed to clone repository: %w", err) 208 | } 209 | 210 | return targetPath, nil 211 | } 212 | 213 | func printStatus(status client.SolveStatus) { 214 | // Process vertexes (build steps) 215 | for _, vertex := range status.Vertexes { 216 | // Skip if no name 217 | if vertex.Name == "" { 218 | continue 219 | } 220 | 221 | status := "RUNNING" 222 | if vertex.Completed != nil { 223 | status = "DONE " 224 | } else if vertex.Cached { 225 | status = "CACHED " 226 | } 227 | 228 | if vertex.Error != "" { 229 | status = "ERROR " 230 | } 231 | 232 | // Extract step number from name if available 233 | stepInfo := "" 234 | if strings.Contains(vertex.Name, "] ") { 235 | parts := strings.SplitN(vertex.Name, "] ", 2) //nolint:mnd 236 | if len(parts) == 2 { //nolint:mnd 237 | stepInfo = parts[0] + "] " 238 | vertex.Name = parts[1] 239 | } 240 | } 241 | 242 | fmt.Fprintf(os.Stdout, "\r[%s] %s%s", status, stepInfo, vertex.Name) 243 | if vertex.Error != "" { 244 | fmt.Fprintf(os.Stdout, " %s", vertex.Error) 245 | } 246 | 247 | fmt.Fprintln(os.Stdout) 248 | } 249 | } 250 | 251 | var ( 252 | directory string 253 | ) 254 | 255 | func main() { 256 | cmd := &cobra.Command{ 257 | Use: "mcp-launcher [repository-url]", 258 | Short: "Clone a repository and build its Docker image", 259 | Args: cobra.ExactArgs(1), 260 | Run: func(cobraCmd *cobra.Command, args []string) { 261 | level := new(slog.LevelVar) 262 | 263 | level.Set(slog.LevelInfo) 264 | if os.Getenv("DEBUG") != "" { 265 | level.Set(slog.LevelDebug) 266 | } 267 | 268 | handler := tint.NewHandler(os.Stderr, &tint.Options{ 269 | Level: level, 270 | TimeFormat: time.Kitchen, 271 | }) 272 | 273 | log := slog.New(handler) 274 | 275 | ctx := contexts.WithMetadata(context.Background()) 276 | ctx = contexts.WithSlog(ctx, log) 277 | 278 | err := manifests.LoadManifests(ctx) 279 | if err != nil { 280 | log.Error("Failed to load manifests", slog.Any("error", err)) 281 | os.Exit(1) 282 | } 283 | 284 | md := contexts.MetadataFrom(ctx) 285 | md.RepositoryURL = args[0] 286 | 287 | // Clone the repository 288 | repoPath, err := cloneRepository(ctx, md.RepositoryURL) 289 | if err != nil { 290 | log.Error("Failed to clone repository", slog.Any("error", err)) 291 | os.Exit(1) 292 | } 293 | 294 | log.Info("Repository cloned successfully", slog.String("path", repoPath)) 295 | 296 | // Add debug information about the repository structure 297 | if err := filepath.Walk(repoPath, func(path string, info os.FileInfo, err error) error { 298 | if err != nil { 299 | return err 300 | } 301 | relPath, err := filepath.Rel(repoPath, path) 302 | if err != nil { 303 | return err 304 | } 305 | log.Debug("Found file", 306 | slog.String("path", relPath), 307 | slog.Bool("is_dir", info.IsDir()), 308 | slog.Int64("size", info.Size())) 309 | return nil 310 | }); err != nil { 311 | log.Error("Failed to walk repository", slog.Any("error", err)) 312 | } 313 | 314 | md.RepositoryClonedPath = repoPath 315 | md.SubDirectory = directory 316 | 317 | err = manifests.ExecuteAfterClone(ctx) 318 | if err != nil { 319 | log.Error("Failed to run after clone plugins", slog.Any("error", err)) 320 | os.Exit(1) 321 | } 322 | 323 | df := dockerfile.New(log) 324 | 325 | if directory != "" { 326 | repoPath = filepath.Join(repoPath, directory) 327 | log.Info("Using subdirectory", slog.String("path", repoPath)) 328 | } 329 | 330 | // Use the cloned repository path instead of current working directory 331 | r, err := df.MatchRuntime(repoPath) 332 | if err != nil { 333 | log.Error("Failed to match runtime", slog.Any("error", err)) 334 | os.Exit(1) 335 | } 336 | 337 | contents, err := r.GenerateDockerfile(repoPath) 338 | if err != nil { 339 | log.Error("Failed to generate Dockerfile", slog.Any("error", err)) 340 | os.Exit(1) 341 | } 342 | 343 | log.Debug("Generated Dockerfile", slog.String("contents", string(contents))) 344 | 345 | tempDir, err := os.MkdirTemp("", strings.Join([]string{"mcp-launcher", "mcp-servers", "dockerfiles", "*"}, "-")) 346 | if err != nil { 347 | panic(err) 348 | } 349 | 350 | dockerfilePath := filepath.Join(tempDir, "Dockerfile") 351 | err = os.WriteFile(dockerfilePath, contents, 0600) //nolint:mnd 352 | if err != nil { 353 | panic(err) 354 | } 355 | 356 | log.Info("Dockerfile written", slog.String("path", dockerfilePath)) 357 | 358 | var imageHash string 359 | 360 | // Create a command for Docker build 361 | dockerCmd := exec.Command("docker", "build", "-t", "mcp-server-dev", "-f", dockerfilePath, repoPath, "--progress=rawjson") 362 | 363 | log.Info("Building Docker image", slog.String("command", dockerCmd.String())) 364 | 365 | stderr, err := dockerCmd.StderrPipe() 366 | if err != nil { 367 | panic(err) 368 | } 369 | if err := dockerCmd.Start(); err != nil { 370 | panic(err) 371 | } 372 | 373 | // Process and display stdout while also parsing JSON 374 | scanner := bufio.NewScanner(stderr) 375 | for scanner.Scan() { 376 | line := scanner.Text() 377 | 378 | // Try to parse as JSON for additional processing if needed 379 | var data client.SolveStatus 380 | err := json.Unmarshal([]byte(line), &data) 381 | if err == nil { 382 | printStatus(data) 383 | 384 | // Check for image hash 385 | if len(data.Statuses) > 0 { 386 | imageStatus, ok := lo.Find(data.Statuses, func(item *client.VertexStatus) bool { 387 | return strings.HasPrefix(item.ID, "writing image") 388 | }) 389 | if ok { 390 | imageHash = strings.TrimPrefix(imageStatus.ID, "writing image sha256:") 391 | } 392 | } 393 | } 394 | } 395 | 396 | if err := scanner.Err(); err != nil { 397 | log.Error("Error reading Docker build output", slog.Any("error", err)) 398 | } 399 | 400 | // Wait for the command to complete 401 | if err := dockerCmd.Wait(); err != nil { 402 | log.Error("Docker build failed", slog.Any("error", err)) 403 | panic(err) 404 | } 405 | 406 | log.Info("Docker build completed", slog.String("dockerfile", dockerfilePath), slog.String("image_hash", imageHash)) 407 | 408 | client, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv) 409 | if err != nil { 410 | panic(err) 411 | } 412 | 413 | createdContainer, err := client.ContainerCreate( 414 | ctx, 415 | &container.Config{ 416 | Tty: true, 417 | Image: imageHash, 418 | Env: []string{}, 419 | }, 420 | &container.HostConfig{}, 421 | &network.NetworkingConfig{}, 422 | &v1.Platform{}, 423 | filepath.Base(repoPath), 424 | ) 425 | if err != nil { 426 | panic(err) 427 | } 428 | 429 | md.DockerContainerHash = createdContainer.ID 430 | 431 | xo.PrintJSON(md) 432 | }, 433 | } 434 | 435 | cmd.PersistentFlags().StringVarP(&directory, "directory", "d", "", "The directory that points to the entrypoint of the mcp server") 436 | 437 | if err := cmd.Execute(); err != nil { 438 | panic(err) 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /cspell.config.yaml: -------------------------------------------------------------------------------- 1 | version: "0.2" 2 | ignorePaths: [] 3 | dictionaryDefinitions: [] 4 | dictionaries: [] 5 | words: 6 | - apierrors 7 | - Apim 8 | - bmatcuk 9 | - browserbase 10 | - buildkit 11 | - bumpp 12 | - canonicalheader 13 | - chardata 14 | - cognitiveservices 15 | - containedctx 16 | - contextcheck 17 | - cyclop 18 | - dataurl 19 | - depguard 20 | - Describedby 21 | - Detailf 22 | - dockerclient 23 | - dockerfiles 24 | - doublestar 25 | - dupl 26 | - durationcheck 27 | - eastasia 28 | - elevenlabs 29 | - errcheck 30 | - errchkjson 31 | - errname 32 | - execinquery 33 | - exhaustive 34 | - exhaustruct 35 | - exportloopref 36 | - Facemotion 37 | - flac 38 | - flexstack 39 | - forbidigo 40 | - forcetypeassert 41 | - funlen 42 | - goarch 43 | - gochecknoglobals 44 | - gochecknoinits 45 | - gocognit 46 | - goconst 47 | - gocritic 48 | - gocyclo 49 | - godox 50 | - gofmt 51 | - gofumpt 52 | - goheader 53 | - goimports 54 | - gomnd 55 | - goprintffuncname 56 | - gosec 57 | - gosimple 58 | - govet 59 | - hreflang 60 | - importantimport 61 | - ineffassign 62 | - ireturn 63 | - jsonpatch 64 | - kbitrate 65 | - kbps 66 | - koemotion 67 | - labstack 68 | - lll 69 | - lmittmann 70 | - maintidx 71 | - misspell 72 | - moby 73 | - moeru 74 | - mulaw 75 | - musttag 76 | - nakedret 77 | - nekomeowww 78 | - nestif 79 | - nilnil 80 | - nlreturn 81 | - nolint 82 | - nolintlint 83 | - nosprintfhostport 84 | - opencontainers 85 | - paralleltest 86 | - perfsprint 87 | - pluginregistry 88 | - prealloc 89 | - predeclared 90 | - rawjson 91 | - reassign 92 | - repositoryurlrules 93 | - revive 94 | - Rinna 95 | - samber 96 | - slogecho 97 | - sonarjs 98 | - ssml 99 | - stagehead 100 | - staticcheck 101 | - strconv 102 | - tagalign 103 | - tenv 104 | - testableexamples 105 | - testpackage 106 | - Traceparent 107 | - truesilk 108 | - typecheck 109 | - unconvert 110 | - unparam 111 | - unspeech 112 | - unused 113 | - varnamelen 114 | - Vercel 115 | - whitespace 116 | - wrapcheck 117 | - wsl 118 | ignoreWords: [] 119 | import: [] 120 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/moeru-ai/mcp-launcher 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/docker/docker v27.5.1+incompatible 7 | github.com/evanphx/json-patch/v5 v5.9.11 8 | github.com/flexstack/new-dockerfile v0.5.2 9 | github.com/go-git/go-git/v5 v5.16.0 10 | github.com/lmittmann/tint v1.0.7 11 | github.com/moby/buildkit v0.20.2 12 | github.com/nekomeowww/xo v1.16.0 13 | github.com/opencontainers/image-spec v1.1.1 14 | github.com/samber/lo v1.49.1 15 | github.com/samber/mo v1.13.0 16 | github.com/spf13/cobra v1.9.1 17 | github.com/stretchr/testify v1.10.0 18 | gopkg.in/yaml.v3 v3.0.1 19 | ) 20 | 21 | require ( 22 | dario.cat/mergo v1.0.1 // indirect 23 | entgo.io/ent v0.14.4 // indirect 24 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect 25 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 26 | github.com/Masterminds/semver/v3 v3.3.1 // indirect 27 | github.com/Microsoft/go-winio v0.6.2 // indirect 28 | github.com/ProtonMail/go-crypto v1.2.0 // indirect 29 | github.com/cloudflare/circl v1.6.1 // indirect 30 | github.com/containerd/containerd/api v1.8.0 // indirect 31 | github.com/containerd/containerd/v2 v2.0.5 // indirect 32 | github.com/containerd/continuity v0.4.5 // indirect 33 | github.com/containerd/errdefs v1.0.0 // indirect 34 | github.com/containerd/errdefs/pkg v0.3.0 // indirect 35 | github.com/containerd/log v0.1.0 // indirect 36 | github.com/containerd/platforms v1.0.0-rc.1 // indirect 37 | github.com/containerd/ttrpc v1.2.7 // indirect 38 | github.com/containerd/typeurl/v2 v2.2.3 // indirect 39 | github.com/cyphar/filepath-securejoin v0.4.1 // indirect 40 | github.com/davecgh/go-spew v1.1.1 // indirect 41 | github.com/distribution/reference v0.6.0 // indirect 42 | github.com/docker/go-connections v0.5.0 // indirect 43 | github.com/docker/go-units v0.5.0 // indirect 44 | github.com/emirpasic/gods v1.18.1 // indirect 45 | github.com/felixge/httpsnoop v1.0.4 // indirect 46 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 47 | github.com/go-git/go-billy/v5 v5.6.2 // indirect 48 | github.com/go-logr/logr v1.4.2 // indirect 49 | github.com/go-logr/stdr v1.2.2 // indirect 50 | github.com/gofrs/flock v0.12.1 // indirect 51 | github.com/gogo/protobuf v1.3.2 // indirect 52 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 53 | github.com/golang/protobuf v1.5.4 // indirect 54 | github.com/google/go-cmp v0.7.0 // indirect 55 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 56 | github.com/google/uuid v1.6.0 // indirect 57 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect 58 | github.com/hashicorp/errwrap v1.1.0 // indirect 59 | github.com/hashicorp/go-multierror v1.1.1 // indirect 60 | github.com/in-toto/in-toto-golang v0.9.0 // indirect 61 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 62 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 63 | github.com/kevinburke/ssh_config v1.2.0 // indirect 64 | github.com/klauspost/compress v1.18.0 // indirect 65 | github.com/moby/docker-image-spec v1.3.1 // indirect 66 | github.com/moby/locker v1.0.1 // indirect 67 | github.com/moby/patternmatcher v0.6.0 // indirect 68 | github.com/moby/sys/signal v0.7.1 // indirect 69 | github.com/opencontainers/go-digest v1.0.0 // indirect 70 | github.com/pelletier/go-toml v1.9.5 // indirect 71 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 72 | github.com/pjbgf/sha1cd v0.3.2 // indirect 73 | github.com/pkg/errors v0.9.1 // indirect 74 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect 75 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 76 | github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect 77 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect 78 | github.com/shibumi/go-pathspec v1.3.0 // indirect 79 | github.com/shopspring/decimal v1.4.0 // indirect 80 | github.com/sirupsen/logrus v1.9.3 // indirect 81 | github.com/skeema/knownhosts v1.3.1 // indirect 82 | github.com/spf13/pflag v1.0.6 // indirect 83 | github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 // indirect 84 | github.com/xanzy/ssh-agent v0.3.3 // indirect 85 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 86 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect 87 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect 88 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect 89 | go.opentelemetry.io/otel v1.35.0 // indirect 90 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect 91 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 92 | go.opentelemetry.io/otel/sdk v1.35.0 // indirect 93 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 94 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 95 | golang.org/x/crypto v0.37.0 // indirect 96 | golang.org/x/net v0.39.0 // indirect 97 | golang.org/x/sync v0.13.0 // indirect 98 | golang.org/x/sys v0.32.0 // indirect 99 | golang.org/x/text v0.24.0 // indirect 100 | google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f // indirect 101 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect 102 | google.golang.org/grpc v1.72.0 // indirect 103 | google.golang.org/protobuf v1.36.6 // indirect 104 | gopkg.in/warnings.v0 v0.1.2 // indirect 105 | gotest.tools/v3 v3.5.2 // indirect 106 | ) 107 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= 2 | dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | entgo.io/ent v0.14.4 h1:/DhDraSLXIkBhyiVoJeSshr4ZYi7femzhj6/TckzZuI= 4 | entgo.io/ent v0.14.4/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= 5 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= 6 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 7 | github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M= 8 | github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw= 9 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 10 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 11 | github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= 12 | github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 13 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 14 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 15 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 16 | github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= 17 | github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= 18 | github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs= 19 | github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= 20 | github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= 21 | github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= 22 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 23 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= 24 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 25 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 26 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 27 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 28 | github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= 29 | github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 30 | github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= 31 | github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= 32 | github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= 33 | github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= 34 | github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= 35 | github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= 36 | github.com/containerd/containerd/v2 v2.0.5 h1:2vg/TjUXnaohAxiHnthQg8K06L9I4gdYEMcOLiMc8BQ= 37 | github.com/containerd/containerd/v2 v2.0.5/go.mod h1:Qqo0UN43i2fX1FLkrSTCg6zcHNfjN7gEnx3NPRZI+N0= 38 | github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= 39 | github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= 40 | github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= 41 | github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 42 | github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 43 | github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 44 | github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= 45 | github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= 46 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 47 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 48 | github.com/containerd/nydus-snapshotter v0.15.0 h1:RqZRs1GPeM6T3wmuxJV9u+2Rg4YETVMwTmiDeX+iWC8= 49 | github.com/containerd/nydus-snapshotter v0.15.0/go.mod h1:biq0ijpeZe0I5yZFSJyHzFSjjRZQ7P7y/OuHyd7hYOw= 50 | github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= 51 | github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= 52 | github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= 53 | github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= 54 | github.com/containerd/stargz-snapshotter v0.16.3 h1:zbQMm8dRuPHEOD4OqAYGajJJUwCeUzt4j7w9Iaw58u4= 55 | github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= 56 | github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= 57 | github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= 58 | github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= 59 | github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= 60 | github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= 61 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 62 | github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= 63 | github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 64 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 65 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 66 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 67 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 68 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 69 | github.com/docker/cli v27.5.1+incompatible h1:JB9cieUT9YNiMITtIsguaN55PLOHhBSz3LKVc6cqWaY= 70 | github.com/docker/cli v27.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 71 | github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= 72 | github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 73 | github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= 74 | github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= 75 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 76 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 77 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 78 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 79 | github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= 80 | github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= 81 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 82 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 83 | github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= 84 | github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= 85 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 86 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 87 | github.com/flexstack/new-dockerfile v0.5.2 h1:ygU7kmqc23A2PTBh+NIawIOCJAffgElOLGxVyGlUQYI= 88 | github.com/flexstack/new-dockerfile v0.5.2/go.mod h1:wlTQyPrs6ozTrzBeoee3opOpevatoTsJzQg/G+XfpLo= 89 | github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 90 | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 91 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 92 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 93 | github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= 94 | github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= 95 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= 96 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= 97 | github.com/go-git/go-git/v5 v5.16.0 h1:k3kuOEpkc0DeY7xlL6NaaNg39xdgQbtH5mwCafHO9AQ= 98 | github.com/go-git/go-git/v5 v5.16.0/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= 99 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 100 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 101 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 102 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 103 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 104 | github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= 105 | github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= 106 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 107 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 108 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 109 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 110 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 111 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 112 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 113 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 114 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 115 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 116 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 117 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 118 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= 119 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= 120 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 121 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 122 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 123 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 124 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 125 | github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= 126 | github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= 127 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 128 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 129 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 130 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 131 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 132 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 133 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 134 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 135 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 136 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 137 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 138 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 139 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 140 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 141 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 142 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 143 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 144 | github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y= 145 | github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= 146 | github.com/moby/buildkit v0.20.2 h1:qIeR47eQ1tzI1rwz0on3Xx2enRw/1CKjFhoONVcTlMA= 147 | github.com/moby/buildkit v0.20.2/go.mod h1:DhaF82FjwOElTftl0JUAJpH/SUIUx4UvcFncLeOtlDI= 148 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 149 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 150 | github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= 151 | github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= 152 | github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= 153 | github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 154 | github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= 155 | github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= 156 | github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 157 | github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 158 | github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= 159 | github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= 160 | github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= 161 | github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 162 | github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= 163 | github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= 164 | github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 165 | github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 166 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 167 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 168 | github.com/nekomeowww/xo v1.16.0 h1:+iyBqTfEKRCZz5NruKeruZ0cOwgrz+h32YA/OJWQoyQ= 169 | github.com/nekomeowww/xo v1.16.0/go.mod h1:uPuIqPtie41ex0mzxpEzueQbCw6kIH/VCV/l3OdvBTw= 170 | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= 171 | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= 172 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 173 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 174 | github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= 175 | github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= 176 | github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= 177 | github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 178 | github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= 179 | github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= 180 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 181 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 182 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 183 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 184 | github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 185 | github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 186 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 187 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 188 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= 189 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 190 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 191 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 192 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 193 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 194 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 195 | github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= 196 | github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 197 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 198 | github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= 199 | github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= 200 | github.com/samber/mo v1.13.0 h1:LB1OwfJMju3a6FjghH+AIvzMG0ZPOzgTWj1qaHs1IQ4= 201 | github.com/samber/mo v1.13.0/go.mod h1:BfkrCPuYzVG3ZljnZB783WIJIGk1mcZr9c9CPf8tAxs= 202 | github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= 203 | github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= 204 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 205 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 206 | github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= 207 | github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= 208 | github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 209 | github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 210 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 211 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 212 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 213 | github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= 214 | github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= 215 | github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= 216 | github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= 217 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 218 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 219 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 220 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 221 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 222 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 223 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 224 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 225 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 226 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 227 | github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144 h1:k9tdF32oJYwtjzMx+D26M6eYiCaAPdJ7tyN7tF1oU5Q= 228 | github.com/tonistiigi/fsutil v0.0.0-20250417144416-3f76f8130144/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= 229 | github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8= 230 | github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= 231 | github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= 232 | github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= 233 | github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= 234 | github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= 235 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 236 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 237 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 238 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 239 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 240 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 241 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 242 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 243 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= 244 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= 245 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM= 246 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8= 247 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= 248 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= 249 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 250 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 251 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= 252 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= 253 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= 254 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= 255 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 256 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 257 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= 258 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= 259 | go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= 260 | go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= 261 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 262 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 263 | go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 264 | go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 265 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 266 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 267 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 268 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 269 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 270 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 271 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 272 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 273 | golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= 274 | golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= 275 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 276 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 277 | golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= 278 | golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 279 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 280 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 281 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 282 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 283 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 284 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 285 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 286 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 287 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 288 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 289 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 290 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 291 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 292 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 293 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 294 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 299 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 300 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 301 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 302 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 303 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 304 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 305 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 306 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 307 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 308 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 309 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 310 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 311 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 312 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 313 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 314 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 315 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 316 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 317 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 318 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 319 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 320 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 321 | google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f h1:tjZsroqekhC63+WMqzmWyW5Twj/ZfR5HAlpd5YQ1Vs0= 322 | google.golang.org/genproto/googleapis/api v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:Cd8IzgPo5Akum2c9R6FsXNaZbH3Jpa2gpHlW89FqlyQ= 323 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f h1:N/PrbTw4kdkqNRzVfWPrBekzLuarFREcbFOiOLkXon4= 324 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 325 | google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= 326 | google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 327 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 328 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 329 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 330 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 331 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 332 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 333 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 334 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 335 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 336 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 337 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 338 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 339 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 340 | gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 341 | gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 342 | -------------------------------------------------------------------------------- /internal/contexts/logger.go: -------------------------------------------------------------------------------- 1 | package contexts 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | ) 7 | 8 | type contextKey int 9 | 10 | const ( 11 | slogKey contextKey = iota 12 | metadataKey 13 | ) 14 | 15 | func WithSlog(ctx context.Context, logger *slog.Logger) context.Context { 16 | return context.WithValue(ctx, slogKey, logger) 17 | } 18 | 19 | func SlogFrom(ctx context.Context) *slog.Logger { 20 | logger, ok := ctx.Value(slogKey).(*slog.Logger) 21 | if !ok { 22 | return slog.Default() 23 | } 24 | 25 | return logger 26 | } 27 | -------------------------------------------------------------------------------- /internal/contexts/metadata.go: -------------------------------------------------------------------------------- 1 | package contexts 2 | 3 | import "context" 4 | 5 | type Metadata struct { 6 | RepositoryURL string 7 | RepositoryClonedPath string 8 | 9 | SubDirectory string 10 | 11 | DockerfilePath string 12 | DockerImageHash string 13 | DockerContainerHash string 14 | } 15 | 16 | func WithMetadata(ctx context.Context) context.Context { 17 | return context.WithValue(ctx, metadataKey, new(Metadata)) 18 | } 19 | 20 | func MetadataFrom(ctx context.Context) *Metadata { 21 | md, ok := ctx.Value(metadataKey).(*Metadata) 22 | if !ok { 23 | return new(Metadata) 24 | } 25 | 26 | return md 27 | } 28 | -------------------------------------------------------------------------------- /manifests/mcp-servers/browserbase.github.io/mc-server-browserbase.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: MCPServerManifest 3 | metadata: 4 | name: "mc-server-browserbase" 5 | description: "Configures browserbase/mcp-server-browserbase mcp server" 6 | spec: 7 | match: 8 | type: "RepositoryUrl" 9 | exactUrls: 10 | - "https://github.com/browserbase/mcp-server-browserbase" 11 | operations: 12 | - on: "AfterClone" 13 | type: "PatchJson" 14 | targetPath: "package.json" 15 | patches: 16 | - op: "remove" 17 | path: "/scripts/prepare" 18 | - op: "add" 19 | path: "/scripts/start" 20 | value: "node dist/index.js" 21 | -------------------------------------------------------------------------------- /manifests/mcp-servers/fatwang2.github.io/search1api-mcp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: MCPServerManifest 3 | metadata: 4 | name: "search1api-mcp" 5 | description: "Configures fatwang2/search1api mcp server" 6 | spec: 7 | match: 8 | type: "RepositoryUrl" 9 | exactUrls: 10 | - "https://github.com/fatwang2/search1api" 11 | operations: 12 | - on: "AfterClone" 13 | type: "PatchJson" 14 | targetPath: "package.json" 15 | patches: 16 | - op: "remove" 17 | path: "/scripts/prepare" 18 | - op: "add" 19 | path: "/scripts/start" 20 | -------------------------------------------------------------------------------- /pkg/jsonpatch/jsonpatch.go: -------------------------------------------------------------------------------- 1 | package jsonpatch 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | jsonpatch "github.com/evanphx/json-patch/v5" 7 | "github.com/moeru-ai/mcp-launcher/pkg/utils" 8 | "github.com/samber/lo" 9 | "github.com/samber/mo" 10 | ) 11 | 12 | type JSONPatchOperation string 13 | 14 | const ( 15 | JSONPatchOperationAdd JSONPatchOperation = "add" 16 | JSONPatchOperationRemove JSONPatchOperation = "remove" 17 | JSONPatchOperationReplace JSONPatchOperation = "replace" 18 | ) 19 | 20 | type JSONPatchOperationObject struct { 21 | Operation JSONPatchOperation `json:"op"` 22 | Path string `json:"path"` 23 | Value any `json:"value,omitempty"` 24 | } 25 | 26 | func NewPatches(operations ...mo.Option[JSONPatchOperationObject]) []byte { 27 | return lo.Must(json.Marshal(utils.MapOptionsPresent(operations))) 28 | } 29 | 30 | func NewReplace(path string, to any) mo.Option[JSONPatchOperationObject] { 31 | return mo.Some(JSONPatchOperationObject{ 32 | Operation: JSONPatchOperationReplace, 33 | Path: path, 34 | Value: to, 35 | }) 36 | } 37 | 38 | func NewAdd(path string, value any) mo.Option[JSONPatchOperationObject] { 39 | return mo.Some(JSONPatchOperationObject{ 40 | Operation: JSONPatchOperationAdd, 41 | Path: path, 42 | Value: value, 43 | }) 44 | } 45 | 46 | func NewRemove(path string) mo.Option[JSONPatchOperationObject] { 47 | return mo.Some(JSONPatchOperationObject{ 48 | Operation: JSONPatchOperationRemove, 49 | Path: path, 50 | }) 51 | } 52 | 53 | type ApplyOptions jsonpatch.ApplyOptions 54 | 55 | func ApplyPatches(bytes []byte, applyOpt mo.Option[ApplyOptions], patches ...mo.Option[JSONPatchOperationObject]) mo.Result[[]byte] { 56 | patch, err := jsonpatch.DecodePatch(NewPatches(patches...)) 57 | if err != nil { 58 | return mo.Err[[]byte](err) 59 | } 60 | 61 | patched, err := patch.Apply(bytes) 62 | if err != nil { 63 | return mo.Err[[]byte](err) 64 | } 65 | 66 | return mo.Ok[[]byte](patched) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/jsonpatch/jsonpatch_test.go: -------------------------------------------------------------------------------- 1 | package jsonpatch 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | jsonpatch "github.com/evanphx/json-patch/v5" 8 | "github.com/samber/lo" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestJSONPatchReplace(t *testing.T) { 13 | patch, err := jsonpatch.DecodePatch(NewPatches( 14 | NewReplace("/model", "gpt-3.5-turbo"), 15 | )) 16 | require.NoError(t, err) 17 | 18 | patched, err := patch.Apply(lo.Must(json.Marshal(map[string]interface{}{ 19 | "model": "gpt-3.5", 20 | }))) 21 | require.NoError(t, err) 22 | 23 | require.JSONEq(t, `{"model":"gpt-3.5-turbo"}`, string(patched)) 24 | } 25 | 26 | func TestJSONPatchAdd(t *testing.T) { 27 | patch, err := jsonpatch.DecodePatch(NewPatches( 28 | NewAdd("/stream_options", map[string]any{ 29 | "include_usage": true, 30 | }), 31 | )) 32 | require.NoError(t, err) 33 | 34 | patched, err := patch.Apply(lo.Must(json.Marshal(map[string]interface{}{ 35 | "model": "gpt-3.5", 36 | }))) 37 | require.NoError(t, err) 38 | 39 | require.JSONEq(t, `{"model":"gpt-3.5","stream_options":{"include_usage":true}}`, string(patched)) 40 | } 41 | 42 | func TestJSONPatchRemove(t *testing.T) { 43 | patch, err := jsonpatch.DecodePatch(NewPatches( 44 | NewRemove("/model"), 45 | )) 46 | require.NoError(t, err) 47 | 48 | patched, err := patch.Apply(lo.Must(json.Marshal(map[string]interface{}{ 49 | "model": "gpt-3.5", 50 | }))) 51 | require.NoError(t, err) 52 | 53 | require.JSONEq(t, `{}`, string(patched)) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/manifests/manifests.go: -------------------------------------------------------------------------------- 1 | package manifests 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/moeru-ai/mcp-launcher/internal/contexts" 10 | "github.com/moeru-ai/mcp-launcher/pkg/jsonpatch" 11 | "github.com/nekomeowww/xo" 12 | "github.com/samber/mo" 13 | "gopkg.in/yaml.v3" 14 | ) 15 | 16 | // ManifestOperation defines the type of operation to perform 17 | type OperationType string 18 | 19 | const ( 20 | OperationPatchJSON OperationType = "PatchJson" 21 | // Add more operation types as needed 22 | ) 23 | 24 | // Match represents a rule that determines if a manifest should be applied 25 | type Match struct { 26 | Type string `yaml:"type" json:"type"` 27 | Value string `yaml:"value" json:"value"` 28 | ExactURLs []string `yaml:"exactUrls,omitempty" json:"exactUrls,omitempty"` 29 | } 30 | 31 | // JSONPatchOperation defines a JSON patch operation 32 | type JSONPatchOperation struct { 33 | Op string `yaml:"op" json:"op"` 34 | Path string `yaml:"path" json:"path"` 35 | Value interface{} `yaml:"value,omitempty" json:"value,omitempty"` 36 | } 37 | 38 | // Operation represents a single transformation operation 39 | type Operation struct { 40 | On string `yaml:"on" json:"on"` 41 | Type OperationType `yaml:"type" json:"type"` 42 | TargetPath string `yaml:"targetPath" json:"targetPath"` 43 | Patches []JSONPatchOperation `yaml:"patches,omitempty" json:"patches,omitempty"` 44 | } 45 | 46 | type ManifestSpec struct { 47 | Match Match `yaml:"match" json:"match"` 48 | Operations []Operation `yaml:"operations" json:"operations"` 49 | } 50 | 51 | type ManifestDef struct { 52 | APIVersion string `yaml:"apiVersion" json:"apiVersion"` 53 | Kind string `yaml:"kind" json:"kind"` 54 | } 55 | 56 | type ManifestMeta struct { 57 | Name string `yaml:"name" json:"name"` 58 | Description string `yaml:"description" json:"description"` 59 | } 60 | 61 | // Manifest represents a configuration for repository transformations 62 | type Manifest struct { 63 | ManifestDef 64 | 65 | Metadata ManifestMeta `yaml:"metadata" json:"metadata"` 66 | Spec ManifestSpec `yaml:"spec" json:"spec"` 67 | } 68 | 69 | // ManifestRegistry manages loading and execution of manifests 70 | type ManifestRegistry struct { 71 | manifests []Manifest 72 | } 73 | 74 | // NewManifestRegistry creates a new registry 75 | func NewManifestRegistry() *ManifestRegistry { 76 | return &ManifestRegistry{ 77 | manifests: []Manifest{}, 78 | } 79 | } 80 | 81 | // LoadManifestsFromDirectory loads all manifest files from a directory 82 | func (r *ManifestRegistry) LoadManifestsFromDirectory(ctx context.Context, dirPath string) error { 83 | files, err := os.ReadDir(dirPath) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | for _, file := range files { 89 | if file.IsDir() { 90 | return r.LoadManifestsFromDirectory(ctx, filepath.Join(dirPath, file.Name())) 91 | } 92 | 93 | ext := filepath.Ext(file.Name()) 94 | if ext != ".yaml" && ext != ".yml" && ext != ".json" && ext != ".toml" { 95 | continue 96 | } 97 | 98 | filePath := filepath.Join(dirPath, file.Name()) 99 | 100 | manifest, err := LoadManifestFromFile(filePath) 101 | if err != nil { 102 | contexts.SlogFrom(ctx).Error("Failed to load manifest", "file", filePath, "error", err) 103 | continue 104 | } 105 | 106 | r.manifests = append(r.manifests, manifest) 107 | 108 | contexts.SlogFrom(ctx).Info("Loaded manifest", "name", manifest.Metadata.Name) 109 | } 110 | 111 | return nil 112 | } 113 | 114 | // LoadManifestFromFile loads a manifest from a file 115 | func LoadManifestFromFile(filePath string) (Manifest, error) { 116 | var manifest Manifest 117 | 118 | data, err := os.ReadFile(filePath) 119 | if err != nil { 120 | return manifest, err 121 | } 122 | 123 | // Here you'd add logic to detect file type (yaml/json/toml) and parse accordingly 124 | // For simplicity, this example assumes YAML 125 | err = yaml.Unmarshal(data, &manifest) 126 | if err != nil { 127 | return manifest, err 128 | } 129 | 130 | return manifest, nil 131 | } 132 | 133 | // ExecuteManifests runs all applicable manifests in the current context 134 | func (r *ManifestRegistry) ExecuteManifests(ctx context.Context, phase string) error { 135 | md := contexts.MetadataFrom(ctx) 136 | 137 | for _, manifest := range r.manifests { 138 | if shouldApplyManifest(manifest.Spec.Match, md) { 139 | contexts.SlogFrom(ctx).Info("Executing manifest", "name", manifest.Metadata.Name, "phase", phase) 140 | 141 | err := executeManifest(ctx, manifest, phase) 142 | if err != nil { 143 | return err 144 | } 145 | } 146 | } 147 | 148 | return nil 149 | } 150 | 151 | // shouldApplyManifest checks if a manifest should be applied based on conditions 152 | func shouldApplyManifest(condition Match, md *contexts.Metadata) bool { 153 | if condition.Type == "RepositoryUrl" { 154 | for _, url := range condition.ExactURLs { 155 | if url == md.RepositoryURL { 156 | return true 157 | } 158 | } 159 | } 160 | 161 | return false 162 | } 163 | 164 | // executeManifest runs the operations defined in a manifest 165 | func executeManifest(ctx context.Context, manifest Manifest, on string) error { 166 | md := contexts.MetadataFrom(ctx) 167 | 168 | for _, op := range manifest.Spec.Operations { 169 | if op.On != on { 170 | continue 171 | } 172 | 173 | switch op.Type { 174 | case OperationPatchJSON: 175 | targetPath := filepath.Join(md.RepositoryClonedPath, md.SubDirectory, op.TargetPath) 176 | 177 | content, err := os.ReadFile(targetPath) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | // Convert manifest patch operations to jsonpatch operations 183 | patches := make([]mo.Option[jsonpatch.JSONPatchOperationObject], 0, len(op.Patches)) 184 | 185 | for _, p := range op.Patches { 186 | switch p.Op { 187 | case "add": 188 | patches = append(patches, jsonpatch.NewAdd(p.Path, p.Value)) 189 | case "remove": 190 | patches = append(patches, jsonpatch.NewRemove(p.Path)) 191 | } 192 | } 193 | 194 | patchedContent := jsonpatch.ApplyPatches( 195 | content, 196 | mo.Some(jsonpatch.ApplyOptions{AllowMissingPathOnRemove: true}), 197 | patches..., 198 | ) 199 | 200 | if patchedContent.IsError() { 201 | return patchedContent.Error() 202 | } 203 | 204 | contexts.SlogFrom(ctx).Info("Patching file", "path", targetPath) 205 | 206 | err = os.WriteFile(targetPath, patchedContent.MustGet(), 0600) //nolint:mnd 207 | if err != nil { 208 | return err 209 | } 210 | default: 211 | return fmt.Errorf("unknown operation type: %s", op.Type) 212 | } 213 | } 214 | 215 | return nil 216 | } 217 | 218 | var DefaultRegistry *ManifestRegistry 219 | 220 | func init() { 221 | DefaultRegistry = NewManifestRegistry() 222 | } 223 | 224 | func LoadManifests(ctx context.Context) error { 225 | err := DefaultRegistry.LoadManifestsFromDirectory(ctx, xo.RelativePathBasedOnPwdOf("manifests")) 226 | if err != nil { 227 | contexts.SlogFrom(ctx).Warn("Failed to load manifests from default directory", "error", err) 228 | } 229 | 230 | if envPath := os.Getenv("MCP_MANIFESTS_PATH"); envPath != "" { 231 | err := DefaultRegistry.LoadManifestsFromDirectory(ctx, envPath) 232 | if err != nil { 233 | contexts.SlogFrom(ctx).Warn("Failed to load manifests from environment path", "path", envPath, "error", err) 234 | } 235 | } 236 | 237 | return nil 238 | } 239 | 240 | func ExecuteAfterClone(ctx context.Context) error { 241 | return DefaultRegistry.ExecuteManifests(ctx, "AfterClone") 242 | } 243 | 244 | func ExecuteBeforeBuild(ctx context.Context) error { 245 | return DefaultRegistry.ExecuteManifests(ctx, "BeforeBuild") 246 | } 247 | -------------------------------------------------------------------------------- /pkg/utils/mo.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/samber/lo" 5 | "github.com/samber/mo" 6 | ) 7 | 8 | func FilterOptionPresent[T any](item mo.Option[T], _ int) bool { 9 | return item.IsPresent() 10 | } 11 | 12 | func FilterOptionAbsent[T any](item mo.Option[T], _ int) bool { 13 | return item.IsAbsent() 14 | } 15 | 16 | func MapOptionOrEmpty[T any](item mo.Option[T], _ int) T { 17 | return item.OrEmpty() 18 | } 19 | 20 | func MapOptionMust(item mo.Option[error], _ int) error { 21 | return item.MustGet() 22 | } 23 | 24 | func MapOptionsPresent[T any](items []mo.Option[T]) []T { 25 | filtered := lo.Filter(items, FilterOptionPresent) 26 | return lo.Map(filtered, MapOptionOrEmpty) 27 | } 28 | 29 | func ResultToOption[T any](item mo.Result[T]) mo.Option[T] { 30 | if item.IsError() { 31 | return mo.None[T]() 32 | } 33 | 34 | return mo.Some(item.MustGet()) 35 | } 36 | --------------------------------------------------------------------------------