Repository: crhuber/kelp Branch: master Commit: abde55811893 Files: 24 Total size: 164.1 KB Directory structure: gitextract_f2uvh5_n/ ├── .github/ │ └── workflows/ │ ├── build.yaml │ ├── codeql.yml │ └── release.yaml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── .tool-versions ├── README.md ├── TODO.txt ├── Taskfile.yaml ├── go.mod ├── go.sum ├── main.go ├── pkg/ │ ├── config/ │ │ └── config.go │ ├── install/ │ │ ├── install.go │ │ └── install_test.go │ ├── logging/ │ │ └── logging.go │ ├── rm/ │ │ └── rm.go │ ├── types/ │ │ ├── github.go │ │ ├── github_test.go │ │ └── os.go │ └── utils/ │ ├── utils.go │ └── utils_test.go └── testdata/ └── helm-latest.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yaml ================================================ name: Build and Test permissions: contents: read on: pull_request: branches: [ master ] push: branches: [ master ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 with: go-version: '1.23' - name: Cache Go modules uses: actions/cache@v3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Download dependencies run: go mod download - name: Verify dependencies run: go mod verify - name: Install Task uses: arduino/setup-task@v2 with: version: 3.x repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Lint, Test, and Build run: | task ci lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 with: go-version: '1.23' - name: golangci-lint uses: golangci/golangci-lint-action@v7 with: version: latest args: --timeout=5m ================================================ FILE: .github/workflows/codeql.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL Advanced" on: push: branches: [ "master" ] pull_request: branches: [ "master" ] schedule: - cron: '44 20 * * 3' jobs: analyze: name: Analyze (${{ matrix.language }}) # Runner size impacts CodeQL analysis time. To learn more, please see: # - https://gh.io/recommended-hardware-resources-for-running-codeql # - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/using-larger-runners (GitHub.com only) # Consider using larger runners or machines with greater resources for possible analysis time improvements. runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} permissions: # required for all workflows security-events: write # required to fetch internal or private CodeQL packs packages: read # only required for workflows in private repositories actions: read contents: read strategy: fail-fast: false matrix: include: - language: actions build-mode: none - language: go build-mode: autobuild # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' # Use `c-cpp` to analyze code written in C, C++ or both # Use 'java-kotlin' to analyze code written in Java, Kotlin or both # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository uses: actions/checkout@v4 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` # or others). This is typically only required for manual builds. # - name: Setup runtime (example) # uses: actions/setup-example@v1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality # If the analyze step fails for one of the languages you are analyzing with # "We were unable to automatically build your code", modify the matrix above # to set the build mode to "manual" for that language. Then modify this step # to build your code. # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - if: matrix.build-mode == 'manual' shell: bash run: | echo 'If you are using a "manual" build mode for one or more of the' \ 'languages you are analyzing, replace this with the commands to build' \ 'your code, for example:' echo ' make bootstrap' echo ' make release' exit 1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" ================================================ FILE: .github/workflows/release.yaml ================================================ name: goreleaser on: push: tags: - '*' jobs: goreleaser: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Set up Go uses: actions/setup-go@v4 with: go-version: '1.24' - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 with: distribution: goreleaser version: '~> v2' args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ kelp build/ dist/ # llm files .cursor .claude ================================================ FILE: .golangci.yml ================================================ version: "2" run: go: "1.23" linters: enable: - gocritic - gosec - misspell - revive disable: - errcheck settings: gosec: excludes: - G404 revive: rules: - name: exported disabled: false - name: unreachable-code disabled: false - name: unused-parameter disabled: false exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - gocritic - gosec path: _test\.go paths: - third_party$ - builtin$ - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 formatters: enable: - gofmt - goimports exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ ================================================ FILE: .goreleaser.yaml ================================================ # GoReleaser configuration for kelp version: 2 project_name: kelp before: hooks: - go mod tidy - go generate ./... builds: - env: - CGO_ENABLED=0 goos: - linux - darwin goarch: - amd64 - arm64 main: . binary: kelp ldflags: - -s -w - -X main.version={{.Version}} - -X main.commit={{.Commit}} - -X main.date={{.Date}} gcflags: - all=-l - ./dontoptimizeme=-N archives: - formats: [tar.gz] name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" files: - README.md - LICENSE* checksum: name_template: "checksums.txt" changelog: sort: asc use: github filters: exclude: - "^docs:" - "^test:" - "^ci:" - "^chore:" - merge conflict - Merge pull request - Merge remote-tracking branch - Merge branch groups: - title: "🚀 New Features" regexp: "^.*feat[(\\w)]*:+.*$" order: 0 - title: "🐛 Bug Fixes" regexp: "^.*fix[(\\w)]*:+.*$" order: 1 - title: "📚 Documentation" regexp: "^.*docs[(\\w)]*:+.*$" order: 2 - title: "🔧 Improvements" regexp: "^.*refactor[(\\w)]*:+.*$" order: 3 - title: "Other Changes" order: 999 release: github: owner: crhuber name: kelp draft: false prerelease: auto mode: replace header: | ## kelp {{ .Tag }} This release includes kelp. ### Installation Download the appropriate binary for your platform and follow the installation instructions in the README. ================================================ FILE: .tool-versions ================================================ golang 1.24.0 ================================================ FILE: README.md ================================================ # KELP

KELP

## What is it A simple replacement for homebrew for installing binary packages on MacOS & Linux written in Go. ## How Does it Work? 1. It downloads a Github releases package matching your operating system and architecture to `~/.kelp/cache` 2. It extracts any binary files to `~/.kelp/bin` 3. It unquarantines the binary (mac) Example: ``` kelp update --install crumb 🌐 Getting releases for crhuber/crumb:latest... Latest release v0.0.24. Kelp configured release v0.0.21. ===> Installing crhuber/crumb:v0.0.24... 🌐 Getting releases by tag v0.0.24... 🍏 Finding assets to download... ===> Downloading https://api.github.com/repos/crhuber/crumb/releases/assets/372978000... Downloading 100% 📂 Extracting /Users/Craig/.kelp/cache/crumb_0.0.24_darwin_arm64.tar.gz 🧐 Checking for binary files in extract... 💾 Copying crumb to kelp bin... ✅ Installed crumb ! 🛃 Unquarantining /Users/Craig/.kelp/bin/crumb.. ``` ## Why? I built Kelp to scratch my own itch: * No waiting for a formula to become available on homebrew * Keep all your computers up to date with a single installation manifest * No homebrew auto update * No bloat, no magic Couldn't this just be a bash script? Probably. ## How To Install Go to the [releases](https://github.com/crhuber/kelp/releases) page. Download the latest release Add kelp binary path to your PATH ``` export PATH=~/.kelp/bin/:$PATH ``` ## Quick Setup 1. Initialize Kelp ``` kelp init ``` 2. Add a new package ``` kelp add junegunn/fzf ``` To use a specific version use the `-r` flag. Where `-r` is the github release version ``` kelp add junegunn/fzf -r 1.0.0 ``` 3. Install ``` kelp install fzf ``` or ``` kelp add junegunn/fzf --install ``` ### Updating a Package Update ``` kelp update fzf kelp install fzf ``` Update and install ``` kelp update fzf --install ``` Update to a specific version ``` kelp set fzf -r v0.72.0 kelp install fzf ``` ## Configuration ``` GLOBAL OPTIONS: --config string, -c string path to kelp config file (default: "/Users/Craig/.kelp/kelp.json") [$KELP_CONFIG] --verbose verbose output [$KELP_VERBOSE] ``` ## Troubleshooting ### What if the package I want is not on github releases? Just kelp add the link to the binary, ie: ``` kelp add hashicorp/terraform -r https://releases.hashicorp.com/terraform/0.11.13/terraform_0.11.13_darwin_amd64.zip ``` Also supported are packages like `helm` that provide external non-github download links in the release description. ### Increase logging level Use `--verbose` or set `KELP_VERBOSE=1` to get more information, expecially during the installation process. ### Why wasnt my package installed ? Kelp looks for binaries made for MacOS or Linux. If it cannot file a suitable binary for your machine architecture and operating system it will skip downloading it or wont extract it. Use inspect to open the cache and bin directories for your package ``` kelp inspect ``` To see what binaries exist use: ``` kelp doctor ``` If your binary has a different filename than the name of the Github project, kelp doctor may not find it. To give it a hint you can add the name of the binary to the kelp config ``` kelp set jira-cli -b "jira"` ``` To see whats in your config use: ``` kelp ls ``` ### Does it work for Linux? Yes! ### What if I'm rate limited by Github Api? Set a github token environment variable ``` export GITHUB_TOKEN="XYZ" ``` ## Contributing If you find bugs, please open an issue first. If you have feature requests, I may not honor it because this project is being built mostly to suit my personal workflow and preferences. ================================================ FILE: TODO.txt ================================================ - when file already exists in cache do a hash before skipping it - add post install hooks ================================================ FILE: Taskfile.yaml ================================================ version: '3' vars: BINARY_NAME: kelp BUILD_DIR: ./build COVERAGE_DIR: ./coverage tasks: default: desc: Show available tasks cmds: - task --list clean: desc: Clean build artifacts and coverage reports cmds: - rm -rf {{.BUILD_DIR}} - rm -rf {{.COVERAGE_DIR}} - rm -f {{.BINARY_NAME}} - rm -f coverage.out coverage.html deps: desc: Download and verify dependencies cmds: - go mod download - go mod verify build: desc: Build the application deps: [clean] cmds: - mkdir -p {{.BUILD_DIR}} - go build -o {{.BUILD_DIR}}/{{.BINARY_NAME}} . test: desc: Run all tests cmds: - echo "Running unit tests..." - go test -v ./... test-unit: desc: Run unit tests only cmds: - echo "Running unit tests..." - go test -v ./... -run "^Test" test-integration: desc: Run integration tests only cmds: - echo "Running integration tests..." - go test -v ./... -run "Integration" test-coverage: desc: Run tests with coverage report cmds: - mkdir -p {{.COVERAGE_DIR}} - go test -v -cover -coverprofile={{.COVERAGE_DIR}}/coverage.out ./... - go tool cover -html={{.COVERAGE_DIR}}/coverage.out -o {{.COVERAGE_DIR}}/coverage.html - 'echo "Coverage report generated: {{.COVERAGE_DIR}}/coverage.html"' test-coverage-text: desc: Run tests with coverage report (text output) cmds: - go test -v -cover -coverprofile=coverage.out ./... - go tool cover -func=coverage.out - rm -f coverage.out lint: desc: Run linter cmds: - echo "Running linter..." - go vet ./... - go fmt ./... - golangci-lint run --timeout=5m lint-check: desc: Check code formatting and linting cmds: - echo "Checking code formatting..." - test -z "$(go fmt ./...)" - go vet ./... security: desc: Run security scanner (requires gosec) cmds: - echo "Running security scanner..." - gosec ./... preconditions: - sh: command -v gosec msg: "gosec is not installed. Run: go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest" ci: desc: Run CI pipeline (lint, test, build) cmds: - task: lint-check - task: test-coverage-text - task: build ci-full: desc: Run full CI pipeline with benchmarks cmds: - task: deps - task: lint-check - task: test-coverage - task: build setup-dev: desc: Setup development environment cmds: - echo "Setting up development environment..." - go mod download - go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest - go install github.com/go-task/task/v3/cmd/task@latest - echo "Development environment setup complete!" test-race: desc: Run tests with race detection cmds: - echo "Running tests with race detection..." - go test -v -race ./... release: desc: Build release binaries for multiple platforms deps: [clean, test-coverage-text, lint-check] cmds: - mkdir -p {{.BUILD_DIR}}/release - echo "Building release binaries..." - GOOS=darwin GOARCH=amd64 go build -o {{.BUILD_DIR}}/release/{{.BINARY_NAME}}-darwin-amd64 . - GOOS=darwin GOARCH=arm64 go build -o {{.BUILD_DIR}}/release/{{.BINARY_NAME}}-darwin-arm64 . - GOOS=linux GOARCH=amd64 go build -o {{.BUILD_DIR}}/release/{{.BINARY_NAME}}-linux-amd64 . - GOOS=linux GOARCH=arm64 go build -o {{.BUILD_DIR}}/release/{{.BINARY_NAME}}-linux-arm64 . - echo "Release binaries built in {{.BUILD_DIR}}/release/" ================================================ FILE: go.mod ================================================ module crhuber/kelp go 1.23.0 toolchain go1.23.8 require ( github.com/gabriel-vasile/mimetype v1.4.12 github.com/h2non/gock v1.2.0 github.com/mholt/archives v0.1.4 github.com/schollz/progressbar/v3 v3.19.0 github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v3 v3.6.1 ) require ( github.com/STARRY-S/zip v0.2.3 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/sevenzip v1.6.1 // indirect github.com/bodgit/windows v1.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/minlz v1.0.1 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/nwaples/rardecode/v2 v2.1.1 // indirect github.com/pierrec/lz4/v4 v4.1.23 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/ulikunitz/xz v0.5.15 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4= github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mholt/archives v0.1.4 h1:sU+/lLNgafUontWFv3AVwO8VUWye3rrtN6hgC2dU11c= github.com/mholt/archives v0.1.4/go.mod h1:I2ia+SQTtQHej9w1GZM/mz7qfdgQv+BHr3hEKqDcGuk= github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew= github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU= github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc= github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= ================================================ FILE: main.go ================================================ package main import ( "context" "crhuber/kelp/pkg/config" "crhuber/kelp/pkg/install" "crhuber/kelp/pkg/logging" "crhuber/kelp/pkg/types" "crhuber/kelp/pkg/utils" "errors" "fmt" "log" "os" "path/filepath" "strings" "github.com/urfave/cli/v3" ) var ( version = "dev" ) func main() { // default config var home, _ = os.UserHomeDir() var KelpConf = filepath.Join(home, "/.kelp/kelp.json") if types.GetCapabilities() == nil { fmt.Println("Sorry, your OS is not yet supported.") os.Exit(1) } app := &cli.Command{ Name: "kelp", Version: version, Flags: []cli.Flag{ &cli.StringFlag{ Name: "config", Aliases: []string{"c"}, Value: KelpConf, Usage: "path to kelp config file", Sources: cli.EnvVars("KELP_CONFIG"), }, &cli.BoolFlag{ Name: "verbose", Value: false, Usage: "verbose output", Sources: cli.EnvVars("KELP_VERBOSE"), Action: func(_ context.Context, _ *cli.Command, val bool) error { logging.SetLogVerbose(val) return nil }, }, }, Commands: []*cli.Command{ { Name: "add", Usage: "add a new package to config", Flags: []cli.Flag{ &cli.StringFlag{ Name: "release", Aliases: []string{"r"}, Value: "latest", Usage: "release for package", }, &cli.BoolFlag{ Name: "install", Aliases: []string{"i"}, Value: false, Usage: "also install package", }, }, Action: func(_ context.Context, cmd *cli.Command) error { project := cmd.Args().First() ownerRepo := strings.Split(project, "/") if len(ownerRepo) < 2 { return fmt.Errorf("use owner/repo format") } // resolve release version releaseFlag := cmd.String("release") var actualRelease string if releaseFlag == "latest" { // Get the actual latest release version from GitHub latestRelease, err := utils.GetGithubRelease(ownerRepo[0], ownerRepo[1], "latest") if err != nil { return fmt.Errorf("failed to get latest release for %s/%s: %s", ownerRepo[0], ownerRepo[1], err) } actualRelease = latestRelease.TagName } else { actualRelease = releaseFlag } // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } err = kc.AddPackage(ownerRepo[0], ownerRepo[1], actualRelease) if err != nil { return fmt.Errorf("%s", err) } // save config err = kc.Save() if err != nil { return fmt.Errorf("%s", err) } // auto install if cmd.Bool("install") { err = install.Install(ownerRepo[0], ownerRepo[1], actualRelease) if err != nil { return err } } return nil }, }, { Name: "browse", Usage: "browse to project github page", Action: func(_ context.Context, cmd *cli.Command) error { project := cmd.Args().First() if project == "" { return errors.New("project argument required") } // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } p, err := kc.GetPackage(project) if err != nil { return fmt.Errorf("%s", err) } config.Browse(p.Owner, p.Repo) return nil }, }, { Name: "doctor", Usage: "checks if packages are installed properly", Action: func(_ context.Context, cmd *cli.Command) error { // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } kc.Doctor() return nil }, }, { Name: "get", Usage: "get package details", Action: func(_ context.Context, cmd *cli.Command) error { project := cmd.Args().First() if project == "" { return errors.New("project argument required") } // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } p, err := kc.GetPackage(project) if err != nil { return fmt.Errorf("%s", err) } fmt.Printf("[%s/%s]\n", p.Owner, p.Repo) fmt.Printf("Release: %s\n", p.Release) fmt.Printf("Description: %s\n", p.Description) fmt.Printf("Url: https://github.com/%s/%s\n", p.Owner, p.Repo) fmt.Printf("Binary: %s\n", p.Binary) fmt.Printf("Updated At: %s\n", p.UpdatedAt) return nil }, }, { Name: "init", Usage: "initialize kelp", Action: func(_ context.Context, cmd *cli.Command) error { err := config.Initialize(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } return nil }, }, { Name: "inspect", Usage: "inspect kelp bin directory", Action: func(_ context.Context, _ *cli.Command) error { config.Inspect() return nil }, }, { Name: "install", Usage: "install kelp package", Action: func(_ context.Context, cmd *cli.Command) error { project := cmd.Args().First() if project == "" { return errors.New("project argument required") } // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } kp, err := kc.GetPackage(project) if err != nil { return fmt.Errorf("%s", err) } err = install.Install(kp.Owner, kp.Repo, kp.Release) if err != nil { return err } return nil }, }, { Name: "list", Aliases: []string{"ls"}, Usage: "list kelp packages", Action: func(_ context.Context, cmd *cli.Command) error { // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } kc.List() return nil }, }, { Name: "remove", Aliases: []string{"rm"}, Usage: "remove a package from config and disk", Action: func(_ context.Context, cmd *cli.Command) error { project := cmd.Args().First() if project == "" { return errors.New("project argument required") } // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } kp, err := kc.GetPackage(project) if err != nil { return fmt.Errorf("%s", err) } // remove from config err = kc.RemovePackage(kp.Repo) if err != nil { return fmt.Errorf("%s", err) } // save config err = kc.Save() if err != nil { return fmt.Errorf("error saving: %s", err) } return nil }, }, { Name: "set", Usage: "set package configuration in config", Flags: []cli.Flag{ &cli.StringFlag{ Name: "release", Aliases: []string{"r"}, Value: "latest", Usage: "release for package", }, &cli.StringFlag{ Name: "description", Aliases: []string{"d"}, Value: "", Usage: "description of package", }, &cli.StringFlag{ Name: "binary", Aliases: []string{"b"}, Value: "", Usage: "alias of binary", }, }, Action: func(_ context.Context, cmd *cli.Command) error { project := cmd.Args().First() if project == "" { return errors.New("project argument required") } // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } err = kc.SetPackage(project, cmd.String("release"), cmd.String("description"), cmd.String("binary")) if err != nil { return fmt.Errorf("%s", err) } // save config err = kc.Save() if err != nil { return fmt.Errorf("%s", err) } return nil }, }, { Name: "update", Usage: "update kelp package in config", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "install", Aliases: []string{"i"}, Value: false, Usage: "also install package", }, }, Action: func(_ context.Context, cmd *cli.Command) error { project := cmd.Args().First() if project == "" { return errors.New("project argument required") } // load config kc, err := config.Load(cmd.String("config")) if err != nil { return fmt.Errorf("%s", err) } kp, err := kc.GetPackage(project) if err != nil { return fmt.Errorf("%s", err) } // handle http packages if strings.HasPrefix(kp.Release, "http") { return errors.New("update functionality not supported for http packages") } ghr, err := utils.GetGithubRelease(kp.Owner, kp.Repo, "latest") if err != nil { return fmt.Errorf("%s", err) } if ghr.TagName == kp.Release { logging.LogInfo("Latest release %s already matches release %s in kelp config", ghr.TagName, kp.Release) return nil } logging.LogInfo("Latest release %s. Kelp configured release %s. Update config [y/n] ? : ", ghr.TagName, kp.Release) var confirmation string confirmation = strings.TrimSpace(confirmation) confirmation = strings.ToLower(confirmation) // Taking input from user fmt.Scanln(&confirmation) if confirmationUpper := strings.ToUpper(confirmation); confirmationUpper == "Y" || confirmationUpper == "YES" { err = kc.SetPackage(kp.Repo, ghr.TagName, "", "") if err != nil { return fmt.Errorf("%s", err) } // save config err = kc.Save() if err != nil { return fmt.Errorf("%s", err) } } // auto install if cmd.Bool("install") { err = install.Install(kp.Owner, kp.Repo, ghr.TagName) if err != nil { return err } } return nil }, }, }, } if err := app.Run(context.Background(), os.Args); err != nil { log.Fatal(err) } } ================================================ FILE: pkg/config/config.go ================================================ package config import ( "crhuber/kelp/pkg/logging" "crhuber/kelp/pkg/types" "crhuber/kelp/pkg/utils" "encoding/json" "errors" "fmt" "log" "os" "os/exec" "path/filepath" "regexp" "sort" "strings" "text/tabwriter" "time" ) var home, _ = os.UserHomeDir() var KelpDir = filepath.Join(home, "/.kelp/") var KelpBin = filepath.Join(home, "/.kelp/bin/") var KelpCache = filepath.Join(home, "/.kelp/cache/") type KelpConfig struct { Path string `json:"-"` Packages []KelpPackage } type KelpPackage struct { Owner string `json:"Owner"` Repo string `json:"Repo"` Release string `json:"Release"` UpdatedAt time.Time `json:"UpdatedAt"` Description string `json:"Description"` Binary string `json:"Binary"` } func (kc *KelpConfig) Pop(index int) []KelpPackage { return append(kc.Packages[:index], kc.Packages[index+1:]...) } func (kc *KelpConfig) GetPackage(repo string) (*KelpPackage, error) { parts := strings.Split(repo, "/") // Check if there is an owner part since some projects have the same repo name // like cli if len(parts) > 1 { // If there is an owner, get the more specific project first for _, kp := range kc.Packages { if kp.Owner == parts[0] && kp.Repo == parts[1] { return &kp, nil } } } else { for _, kp := range kc.Packages { if kp.Repo == repo { return &kp, nil } } } return nil, errors.New("package not found in config, try adding it first") } func Load(path string) (*KelpConfig, error) { bs, _ := os.ReadFile(path) kc := KelpConfig{} err := json.Unmarshal(bs, &kc.Packages) if err != nil { return nil, err } kc.Path = path return &kc, nil } func (kc *KelpConfig) Save() error { bs, _ := json.MarshalIndent(kc.Packages, "", " ") err := os.WriteFile(kc.Path, bs, 0600) if err != nil { return err } logging.LogInfo("Config saved.") return nil } func (kc *KelpConfig) RemovePackage(repo string) error { for i, kp := range kc.Packages { if kp.Repo == repo { kc.Packages = kc.Pop(i) logging.LogInfo("Package %s removed\n", repo) return nil } } return errors.New("package not found in config") } func (kc *KelpConfig) AddPackage(owner, repo, release string) error { for _, p := range kc.Packages { if p.Owner == owner && p.Repo == repo { return fmt.Errorf("package already exists in config") } } // append a new item kp := KelpPackage{ Owner: owner, Repo: repo, Release: release, UpdatedAt: time.Now(), } kc.Packages = append(kc.Packages, kp) logging.LogDebug("Config added for %s/%s", owner, repo) return nil } func (kc *KelpConfig) UpdatePackage(repo string) (string, error) { for _, p := range kc.Packages { if p.Repo == repo { ghr, err := utils.GetGithubRelease(p.Owner, p.Repo, "latest") if err != nil { return "", err } return ghr.TagName, nil } } return "", errors.New("package not found in config") } func (kc *KelpConfig) SetPackage(repo, release, description, binary string) error { for i, p := range kc.Packages { if p.Repo == repo { if release != "" { kc.Packages[i].Release = release kc.Packages[i].UpdatedAt = time.Now() } if description != "" { kc.Packages[i].Description = description } if binary != "" { kc.Packages[i].Binary = binary } logging.LogDebug("Config set for %s", repo) return nil } } return nil } func (kc *KelpConfig) List() { w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) // sort by date sort.Slice(kc.Packages, func(i, j int) bool { return kc.Packages[i].UpdatedAt.Before(kc.Packages[j].UpdatedAt) }) for _, pkg := range kc.Packages { // Format the timestamp in a more human-friendly way humanFriendlyTimestamp := pkg.UpdatedAt.Format("Jan 2 2006") if humanFriendlyTimestamp == "Jan 1 0001" { humanFriendlyTimestamp = "" } release := "" if strings.HasPrefix(pkg.Release, "http") { // Define the regex pattern to extract version numbers pattern := `[/v-]([\d.]+)` // Compile the regex pattern re := regexp.MustCompile(pattern) match := re.FindStringSubmatch(pkg.Release) if len(match) > 1 { release = fmt.Sprintf("%s (https)", match[1]) } else { release = "unknown (https)" } } else { release = pkg.Release } fmt.Fprintf(w, "%s/%s\t%s\t%s\n", pkg.Owner, pkg.Repo, release, humanFriendlyTimestamp) } w.Flush() } func Initialize(path string) error { if !utils.DirExists(KelpDir) { logging.LogDebug("Creating Kelp dir...") err := os.Mkdir(KelpDir, 0777) if err != nil { return err } } if !utils.DirExists(KelpCache) { logging.LogDebug("Creating Kelp cache...") err := os.Mkdir(KelpCache, 0777) if err != nil { return err } } if !utils.DirExists(KelpBin) { logging.LogDebug("Creating Kelp bin...") err := os.Mkdir(KelpBin, 0777) if err != nil { return err } } // create empty config kp := KelpPackage{ Owner: "crhuber", Repo: "kelp", Release: "latest", UpdatedAt: time.Now(), Description: "Simple homebrew alternative", } kc := KelpConfig{ Path: path, Packages: []KelpPackage{kp}, } if !utils.FileExists(path) { logging.LogDebug("Creating Kelp config file...") err := kc.Save() if err != nil { return err } } else { logging.LogDebug("Skipping Kelp config file creation since one alredy exists...") } logging.LogInfo("🌱 Kelp Initialized!") logging.LogInfo("🗒 Add Kelp to your path by running: \nexport PATH=%s:$PATH >> ~/.bash_profile\n", KelpBin) return nil } func Inspect() { var err error switch types.GetOS() { case types.Darwin: err = exec.Command("open", KelpDir).Start() case types.Linux: err = exec.Command("xdg-open", KelpDir).Start() default: err = fmt.Errorf("unsupported platform") } if err != nil { log.Fatal(err) } } func Browse(owner, repo string) { var err error url := fmt.Sprintf("https://github.com/%s/%s", owner, repo) logging.LogDebug("Opening %s\n", url) switch types.GetOS() { case types.Darwin: err = exec.Command("open", url).Start() case types.Linux: err = exec.Command("xdg-open", url).Start() default: err = fmt.Errorf("unsupported platform") } if err != nil { log.Fatal(err) } } func (kc *KelpConfig) Doctor() { w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) for _, p := range kc.Packages { // check alias first var binary string if p.Binary != "" { binary = p.Binary } else { binary = p.Repo } status := "" path, err := commandExists(binary) if err != nil { status = "❌ Binary not found" } else { if strings.HasPrefix(path, KelpBin) { status = "✅ Installed" } else { status = "⛔️ Installed outside kelp" } } logging.LogInfo("%s\t%s\n", binary, status) } w.Flush() } func commandExists(cmd string) (string, error) { return exec.LookPath(cmd) } ================================================ FILE: pkg/install/install.go ================================================ package install import ( "context" "errors" "fmt" "io" "log" "net/http" "os" "os/exec" "path/filepath" "strings" "crhuber/kelp/pkg/config" "crhuber/kelp/pkg/logging" "crhuber/kelp/pkg/types" "crhuber/kelp/pkg/utils" "github.com/gabriel-vasile/mimetype" "github.com/mholt/archives" "github.com/schollz/progressbar/v3" ) func Install(owner, repo, release string) error { // handle http packages tempdir, _ := os.MkdirTemp("", "kelp") defer os.RemoveAll(tempdir) var downloadPath string if strings.HasPrefix(release, "http") { urlsplit := strings.SplitAfter(release, "/") filename := urlsplit[len(urlsplit)-1] downloadPath = filepath.Join(config.KelpCache, filename) err := downloadFile(downloadPath, release) if err != nil { return err } } else { asset, err := downloadGithubRelease(owner, repo, release) if err != nil { return err } downloadPath = filepath.Join(config.KelpCache, asset.Name) } err := extractPackage(downloadPath, tempdir) if err != nil { return err } destinations := installBinary(tempdir) if types.IsDarwin() { for _, d := range destinations { unquarantineFile(d) } } return nil } func unquarantineFile(filepath string) error { logging.LogInfo("🛃 Unquarantining %s...\n", filepath) cmd := exec.Command("xattr", "-d", "com.apple.quarantine", filepath) return cmd.Run() } // downloadFile downloads files func downloadFile(filepath string, url string) error { logging.LogInfo("===> Downloading %s...\n", url) logging.LogDebug("To: %s...\n", filepath) // Get the data req, _ := http.NewRequest("GET", url, nil) // set headers for github auth if ghToken := os.Getenv("GITHUB_TOKEN"); ghToken != "" { logging.LogDebug("Using Github token in http request") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ghToken)) } req.Header.Set("Accept", "application/octet-stream") resp, err := http.DefaultClient.Do(req) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("\ninvalid HTTP status: %v", resp.StatusCode) } defer resp.Body.Close() // Create the file out, err := os.Create(filepath) if err != nil { return err } defer out.Close() // Write the body to file bar := progressbar.DefaultBytes( resp.ContentLength, "Downloading", ) _, err = io.Copy(io.MultiWriter(out, bar), resp.Body) return err } func extractPackage(downloadPath, tempDir string) error { logging.LogInfo("📂 Extracting %s\n", downloadPath) // Handle dmg files if strings.HasSuffix(downloadPath, ".dmg") { return errors.New("kelp does not support dmg files") } // Open the file file, err := os.Open(downloadPath) if err != nil { return fmt.Errorf("could not open file: %w", err) } defer file.Close() // Try to identify archive format ctx := context.Background() format, stream, err := archives.Identify(ctx, downloadPath, file) if err != nil { // Not a recognized archive — treat as raw binary logging.LogDebug("File is not a recognized archive format. Treating as raw binary.") cleanName := cleanBinaryName(filepath.Base(downloadPath)) destPath := filepath.Join(tempDir, cleanName) if copyErr := utils.CopyFile(downloadPath, destPath); copyErr != nil { return fmt.Errorf("could not copy binary to temp dir: %w", copyErr) } os.Chmod(destPath, 0o755) return nil } // Check if the format supports extraction extractor, ok := format.(archives.Extractor) if !ok { return fmt.Errorf("archive format does not support extraction") } // Extract all files to destination directory err = extractor.Extract(ctx, stream, func(_ context.Context, f archives.FileInfo) error { return extractFile(f, tempDir) }) if err != nil { return fmt.Errorf("extraction failed: %w", err) } return nil } // cleanBinaryName strips OS/arch suffixes from binary filenames. // For example, "direnv.darwin-arm64" becomes "direnv". func cleanBinaryName(name string) string { osNames := []string{"darwin", "linux", "macos", "windows"} archNames := []string{"arm64", "aarch64", "amd64", "x86_64", "x64"} lower := strings.ToLower(name) for _, osName := range osNames { for _, arch := range archNames { for _, sep := range []string{"-", "_"} { // os-arch: direnv.darwin-arm64 suffix := "." + osName + sep + arch if strings.HasSuffix(lower, suffix) { return name[:len(name)-len(suffix)] } // arch-os: direnv.arm64-darwin suffix = "." + arch + sep + osName if strings.HasSuffix(lower, suffix) { return name[:len(name)-len(suffix)] } } } } return name } // Helper function to extract a single file func extractFile(f archives.FileInfo, destDir string) error { extractPath := filepath.Join(destDir, f.NameInArchive) if f.IsDir() { return os.MkdirAll(extractPath, f.Mode()) } if err := os.MkdirAll(filepath.Dir(extractPath), 0o755); err != nil { return err } rc, err := f.Open() if err != nil { return err } defer rc.Close() outFile, err := os.OpenFile(extractPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode()) if err != nil { return err } defer outFile.Close() _, err = io.Copy(outFile, rc) return err } func installBinary(tempDir string) []string { logging.LogInfo("🧐 Checking for binary files in extract...") files, err := utils.FilePathWalkDir(tempDir) if err != nil { log.Panic("Could not walk directory") } destinations := []string{} osCap := types.GetCapabilities() var foundLibs []string for _, file := range files { mime, _ := mimetype.DetectFile(string(file)) // only install binary files switch mime.String() { case osCap.ExecutableMime: destinations = append(destinations, copyToKelpBin(file)) case osCap.SharedLibrary: splits := strings.SplitAfter(file, "/") fileName := splits[len(splits)-1] logging.LogDebug("Shared/Static Library file %s found in extract.\n", fileName) foundLibs = append(foundLibs, file) default: logging.LogDebug("Skipping non executable file: %v - %v\n", file, mime.String()) } } if len(destinations) == 0 { // if no binary was found in extract, then filter shared libararies if len(foundLibs) == 1 { destinations = append(destinations, copyToKelpBin(foundLibs[0])) } else { var filteredLibs []string for _, currentLib := range foundLibs { if !strings.HasPrefix(currentLib, "lib") && !strings.HasSuffix(currentLib, "dynlib") { filteredLibs = append(filteredLibs, currentLib) } else { mime, _ := mimetype.DetectFile(string(currentLib)) logging.LogDebug("Skipping non executable file: %v - %v\n", currentLib, mime.String()) } } if len(filteredLibs) == 1 { destinations = append(destinations, copyToKelpBin(filteredLibs[0])) } else { for _, currentUnrecognizedLib := range filteredLibs { mime, _ := mimetype.DetectFile(string(currentUnrecognizedLib)) logging.LogDebug("Skipping non executable file: %v - %v\n", currentUnrecognizedLib, mime.String()) } } } } return destinations } func copyToKelpBin(file string) string { splits := strings.SplitAfter(file, "/") fileName := splits[len(splits)-1] logging.LogDebug("Binary file %s found in extract.\n", fileName) destination := filepath.Join(config.KelpBin, fileName) logging.LogInfo("💾 Copying %v to kelp bin...\n", fileName) utils.CopyFile(file, destination) logging.LogInfo("✅ Installed %v !\n", fileName) return destination } func downloadGithubRelease(owner, repo, release string) (*types.Asset, error) { logging.LogInfo("===> Installing %s/%s:%s...\n", owner, repo, release) ghr, err := utils.GetGithubRelease(owner, repo, release) if err != nil { return nil, err } logging.LogInfo("🍏 Finding assets to download...") downloadableAsset, err := ghr.FindBestAsset(types.GetCapabilities()) if err != nil { return nil, err } downloadPath := filepath.Join(config.KelpCache, downloadableAsset.Name) if utils.FileExists(downloadPath) { logging.LogDebug("File %v already exists in cache, skipping download.\n", downloadableAsset.Name) } else { err := downloadFile(downloadPath, downloadableAsset.URL) if err != nil { return nil, err } } return downloadableAsset, nil } ================================================ FILE: pkg/install/install_test.go ================================================ package install import "testing" func TestCleanBinaryName(t *testing.T) { tests := []struct { input string want string }{ {"direnv.darwin-arm64", "direnv"}, {"direnv.darwin-amd64", "direnv"}, {"direnv.linux-arm64", "direnv"}, {"direnv.linux-amd64", "direnv"}, {"tool.darwin_arm64", "tool"}, {"tool.linux_amd64", "tool"}, {"tool.arm64-darwin", "tool"}, {"tool.amd64-linux", "tool"}, {"tool.macos-arm64", "tool"}, {"tool.windows-x86_64", "tool"}, {"tool.linux-aarch64", "tool"}, {"tool.darwin-x64", "tool"}, // No matching suffix — returned as-is {"mybinary", "mybinary"}, {"archive.tar.gz", "archive.tar.gz"}, {"tool.v1.2.3", "tool.v1.2.3"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { got := cleanBinaryName(tt.input) if got != tt.want { t.Errorf("cleanBinaryName(%q) = %q, want %q", tt.input, got, tt.want) } }) } } ================================================ FILE: pkg/logging/logging.go ================================================ package logging import "log" var logVerbose = false func SetLogVerbose(verbose bool) { logVerbose = verbose } func LogDebug(message string, args ...any) { if logVerbose { log.Printf(message, args...) } } func LogInfo(message string, args ...any) { log.Printf(message, args...) } func init() { // set log output to the simplest as possible, without anything but the message log.SetFlags(0) } ================================================ FILE: pkg/rm/rm.go ================================================ package rm import ( "crhuber/kelp/pkg/config" "crhuber/kelp/pkg/logging" "crhuber/kelp/pkg/utils" "os" "path/filepath" ) func RemoveBinary(binary string) error { binaryPath := filepath.Join(config.KelpBin, binary) if utils.FileExists(binaryPath) { logging.LogInfo("Removing binary %s...", binary) return os.Remove(binaryPath) } return nil } ================================================ FILE: pkg/types/github.go ================================================ package types import ( "crhuber/kelp/pkg/logging" "errors" "regexp" "sort" "strings" "time" ) // Asset represents a downloadable asset from a Github release type Asset struct { URL string `json:"url"` ID int `json:"id"` Name string `json:"name"` Label string `json:"label"` ContentType string `json:"content_type"` State string `json:"state"` Size int `json:"size"` DownloadCount int `json:"download_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` BrowserDownloadURL string `json:"browser_download_url"` } // GithubRelease represents a Github release type GithubRelease struct { URL string `json:"url"` AssetsURL string `json:"assets_url"` UploadURL string `json:"upload_url"` HTMLURL string `json:"html_url"` ID int `json:"id"` TagName string `json:"tag_name"` TargetCommitish string `json:"target_commitish"` Name string `json:"name"` Draft bool `json:"draft"` Prerelease bool `json:"prerelease"` CreatedAt time.Time `json:"created_at"` PublishedAt time.Time `json:"published_at"` Assets []Asset `json:"assets"` Body string `json:"body"` } // methods func (a *Asset) isDownloadableExtension() bool { downLoadableExtension := []string{".zip", ".tar", ".gz", ".xz", ".dmg", ".pkg", ".tgz", ".bz2"} for _, word := range downLoadableExtension { result := strings.HasSuffix(a.BrowserDownloadURL, word) if result { return result } } return false } func (a *Asset) isChecksumFile() bool { checksumExtension := []string{".asc", ".sha256.asc", ".sha512.asc", ".sha256sum.asc", ".sha512sum.asc", ".sha1.asc", ".md5.asc"} for _, word := range checksumExtension { if strings.HasSuffix(a.BrowserDownloadURL, word) { return true } } return false } func (a *Asset) hasNoExtension() bool { bdu := strings.SplitAfter(a.BrowserDownloadURL, "/") filename := bdu[len(bdu)-1] return !strings.Contains(filename, ".") } // IsMacAsset checks if the download url contains "mac", "macos", "darwin", "osx", "apple" and returns true if so func (a *Asset) isMacAsset() bool { macIdentifiers := []string{"mac", "macos", "darwin", "osx", "apple"} for _, word := range macIdentifiers { result := strings.Contains(strings.ToLower(a.BrowserDownloadURL), word) if result { return result } } return false } func (a *Asset) isLinuxAsset() bool { macIdentifiers := []string{"linux"} for _, word := range macIdentifiers { result := strings.Contains(strings.ToLower(a.BrowserDownloadURL), word) if result { return result } } return false } func (a *Asset) isSameOS(capabilities *Capabilities) bool { switch capabilities.OS { case Darwin: return a.isMacAsset() case Linux: return a.isLinuxAsset() } return false } func (a *Asset) isSameArchitecture(capabilities *Capabilities) bool { lowerURL := strings.ToLower(a.BrowserDownloadURL) // First check if the URL contains the exact arch name if strings.Contains(lowerURL, strings.ToLower(capabilities.Arch)) { return true } // Then handle architecture aliases switch capabilities.Arch { case "amd64": return strings.Contains(lowerURL, "x86_64") case "arm64": return strings.Contains(lowerURL, "arm64") || strings.Contains(lowerURL, "aarch64") default: return false } } const ( MIN_ASSET_SCORE = 6 // minimum score for an asset to be considered suitable for download ) func (a *Asset) EvaluateSuitability(capabilities *Capabilities) int { assetScore := 0 if a.isSameOS(capabilities) { assetScore += 4 } if a.isSameArchitecture(capabilities) { assetScore += 3 } if a.isDownloadableExtension() { assetScore += 2 } if a.hasNoExtension() { assetScore += 1 } if a.isChecksumFile() { assetScore -= 10 } return assetScore } func (a *Asset) RealFilename() string { if a.Name != "" { return a.Name } url := a.URL if url == "" { url = a.BrowserDownloadURL } filename := strings.Split(url, "/") return filename[len(filename)-1] } // A data structure to hold key/value pairs type Pair struct { Key int Value int } // A slice of pairs that implements sort.Interface to sort by values type PairList []Pair func (p PairList) Len() int { return len(p) } func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p PairList) Less(i, j int) bool { return p[i].Value < p[j].Value } func (ghr *GithubRelease) FindBestAsset(capabilities *Capabilities) (*Asset, error) { var bestAsset Asset assetScores := map[int]int{} for index, asset := range ghr.Assets { if assetScore := asset.EvaluateSuitability(capabilities); assetScore >= MIN_ASSET_SCORE { logging.LogDebug("Found suitable candidate %v for download. Score: %v", asset.RealFilename(), assetScore) assetScores[index] = assetScore } } if len(assetScores) == 0 { // inspect the release body for links to downloadable assets links := ghr.inspectLinksInReleaseBody() // create a list of assets from the links and evaluate them assetsFromBodyScores := map[int]int{} assetLinks := make([]Asset, len(links)) for index, link := range links { filename := strings.Split(link, "/") realFilename := filename[len(filename)-1] a := Asset{ BrowserDownloadURL: link, URL: link, Name: realFilename, } assetLinks[index] = a if assetScore := a.EvaluateSuitability(capabilities); assetScore >= MIN_ASSET_SCORE { logging.LogDebug("Found suitable candidate %v for download in release body. Score: %v", realFilename, assetScore) assetsFromBodyScores[index] = assetScore } } if len(assetsFromBodyScores) == 0 { return nil, errors.New("no suitable candidates found in release body") } // sort the map by value of score. highest := getHighestScore(assetsFromBodyScores) bestAsset = assetLinks[highest.Key] } else { // sort the map by value of score. highest := getHighestScore(assetScores) bestAsset = ghr.Assets[highest.Key] } logging.LogDebug("Adding highest ranked asset %v to download queue.", bestAsset.RealFilename()) return &bestAsset, nil } func getHighestScore(assetScores map[int]int) Pair { // sort the map by value of score. assetsByScore := make(PairList, len(assetScores)) i := 0 for k, v := range assetScores { assetsByScore[i] = Pair{k, v} i++ } sort.Sort(assetsByScore) // return highest return assetsByScore[len(assetsByScore)-1] } func (ghr *GithubRelease) inspectLinksInReleaseBody() []string { const ( NAME_REGEXP = `([a-z][a-z0-9_-]+?)` ARCH_REGEXP = `[._-](amd64|x86_64|x64|arm64|aarch64)` OS_REGEXP = `[._-]((unknown[._-])?(linux|linux-gnu|linux-musl))|((apple[._-])?(darwin|macos|osx))` VERSION_REGEXP = `([_-]v?[0-9.]+)?` SUFFIX_REGEXP = `([_-][a-z0-9_-]+)?` EXTENSION_REGEXP = `(\.zip|\.tar\.gz|\.gz|\.tgz|\.tar\.xz|\.txz|\.tar\.bz2|\.tbz)?` REGEXP = NAME_REGEXP + VERSION_REGEXP + "(" + OS_REGEXP + ARCH_REGEXP + "|" + ARCH_REGEXP + OS_REGEXP + ")" + SUFFIX_REGEXP + EXTENSION_REGEXP ) re := regexp.MustCompile(`https:\/\/[a-z0-9.\/]+\/` + REGEXP) matches := re.FindAllString(ghr.Body, -1) // remove duplicates sort.Strings(matches) // remove duplicates seen := make(map[string]bool) result := make([]string, 0) for _, item := range matches { if !seen[item] { seen[item] = true result = append(result, item) } } return result } ================================================ FILE: pkg/types/github_test.go ================================================ package types import ( "encoding/json" "os" "path/filepath" "testing" "github.com/stretchr/testify/require" ) func TestEvalAssetSuitabilityDarwin(t *testing.T) { t.Parallel() // pluto_4.2.0_darwin_amd64.tar.gz = 9 // ruplacer-osx = 6 // croc_9.2.0_macOS-64bit.tar.gz = 7 // conftest_0.28.1_Darwin_x86_64.tar.gz = 7 // conftest_0.28.1_Darwin_arm64.tar.gz = 6 // pandoc-2.14.2-macOS.pkg = 6 // direnv.darwin-amd64 =8 osCap := &Capabilities{ OS: Darwin, ExecutableMime: "application/x-mach-binary", Arch: "arm64", } asset := Asset{ BrowserDownloadURL: "https://github.com/foo/bar/releases/download/v1.0/direnv.darwin-arm64", } require.Equal(t, 7, asset.EvaluateSuitability(osCap)) // pluto_4.2.0_darwin_amd64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/pluto_4.2.0_darwin_arm64.tar.gz" require.Equal(t, 9, asset.EvaluateSuitability(osCap)) // ruplacer-osx asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/ruplacer-osx" require.Equal(t, 5, asset.EvaluateSuitability(osCap)) // croc_9.2.0_macOS-64bit.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/croc_9.2.0_macOS-64bit.tar.gz" require.Equal(t, 6, asset.EvaluateSuitability(osCap)) // conftest_0.28.1_Darwin_x86_64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/conftest_0.28.1_Darwin_x86_64.tar.gz" require.Equal(t, 6, asset.EvaluateSuitability(osCap)) // conftest_0.28.1_Darwin_arm64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/conftest_0.28.1_Darwin_arm64.tar.gz" require.Equal(t, 9, asset.EvaluateSuitability(osCap)) // pandoc-2.14.2-macOS.pkg asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/pandoc-2.14.2-macOS.pkg" require.Equal(t, 6, asset.EvaluateSuitability(osCap)) // gopass-1.15.11-darwin-amd64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/gopass-1.15.11-darwin-amd64.tar.gz" require.Equal(t, 6, asset.EvaluateSuitability(osCap)) // gopass-1.15.11-darwin-arm64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/gopass-1.15.11-darwin-arm64.tar.gz" require.Equal(t, 9, asset.EvaluateSuitability(osCap)) } func TestEvalAssetSuitabilityLinux(t *testing.T) { t.Parallel() // pluto_4.2.0_darwin_amd64.tar.gz = 9 // ruplacer-osx = 6 // croc_9.2.0_macOS-64bit.tar.gz = 7 // conftest_0.28.1_Darwin_x86_64.tar.gz = 7 // conftest_0.28.1_Darwin_arm64.tar.gz = 6 // pandoc-2.14.2-macOS.pkg = 6 // direnv.darwin-amd64 =8 // helm-v4.0.4-linux-arm64.tar.gz.asc <0 osCap := &Capabilities{ OS: Linux, ExecutableMime: "asdf", Arch: "amd64", } asset := Asset{ BrowserDownloadURL: "https://github.com/foo/bar/releases/download/v1.0/direnv.linux-amd64", } require.Equal(t, 7, asset.EvaluateSuitability(osCap)) // pluto_4.2.0_darwin_amd64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/pluto_4.2.0_linux_amd64.tar.gz" require.Equal(t, 9, asset.EvaluateSuitability(osCap)) // ruplacer-osx asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/ruplacer-linux" require.Equal(t, 5, asset.EvaluateSuitability(osCap)) // croc_9.2.0_macOS-64bit.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/croc_9.2.0_linuX-64bit.tar.gz" require.Equal(t, 6, asset.EvaluateSuitability(osCap)) // conftest_0.28.1_Darwin_x86_64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/conftest_0.28.1_Linux_x86_64.tar.gz" require.Equal(t, 9, asset.EvaluateSuitability(osCap)) // conftest_0.28.1_Darwin_arm64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/conftest_0.28.1_Linux_arm64.tar.gz" require.Equal(t, 6, asset.EvaluateSuitability(osCap)) // pandoc-2.14.2-macOS.pkg asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/pandoc-2.14.2-linux.pkg" require.Equal(t, 6, asset.EvaluateSuitability(osCap)) // gopass-1.15.11-darwin-amd64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/gopass-1.15.11-linux-arm64.tar.gz" require.Equal(t, 6, asset.EvaluateSuitability(osCap)) // gopass-1.15.11-darwin-arm64.tar.gz asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/gopass-1.15.11-linux-amd64.tar.gz" require.Equal(t, 9, asset.EvaluateSuitability(osCap)) // helm-v4.0.4-linux-arm64.tar.gz.asc asset.BrowserDownloadURL = "https://github.com/foo/bar/releases/download/v1.0/helm-v4.0.4-linux-arm64.tar.gz.asc" require.Less(t, asset.EvaluateSuitability(osCap), 0) } func TestFindGithubReleaseMacAssets(t *testing.T) { t.Parallel() var assets []Asset asset1 := Asset{ BrowserDownloadURL: "https://github.com/trufflesecurity/trufflehog/releases/download/v3.60.1/trufflehog_3.60.1_linux_amd64.tar.gz", } asset2 := Asset{ BrowserDownloadURL: "https://github.com/trufflesecurity/trufflehog/releases/download/v3.60.1/trufflehog_3.60.1_linux_arm64.tar.gz", } assets = append(assets, asset1, asset2) ghr := GithubRelease{ Assets: assets, } capAMD64 := &Capabilities{ OS: Linux, Arch: "amd64", } capARM64 := &Capabilities{ OS: Linux, Arch: "arm64", } downloadableAsset, _ := ghr.FindBestAsset(capAMD64) require.Equal(t, asset1, *downloadableAsset) downloadableAsset, _ = ghr.FindBestAsset(capARM64) require.Equal(t, asset2, *downloadableAsset) } func TestGetHighestScore(t *testing.T) { t.Parallel() assetScores := map[int]int{} assetScores[0] = 6 assetScores[1] = 8 assetScores[2] = 1 assetScores[3] = 9 assetScores[4] = 3 assetsByScore := getHighestScore(assetScores) require.Equal(t, assetsByScore.Value, assetScores[3]) } func TestInspectLinksInReleaseBody(t *testing.T) { t.Parallel() filename := filepath.Join("..", "..", "testdata", "helm-latest.json") jsonBytes, err := os.ReadFile(filename) if err != nil { t.Fatal("error reading testdata: ", err) } ghr := GithubRelease{} if err := json.Unmarshal(jsonBytes, &ghr); err != nil { t.Fatal("error unmarshalling testdata: ", err) } asset, err := ghr.FindBestAsset(&Capabilities{ OS: Linux, Arch: "amd64", }) require.NoError(t, err) require.Equal(t, "helm-v4.0.4-linux-amd64.tar.gz", asset.Name) require.Contains(t, asset.BrowserDownloadURL, "get.helm.sh") } ================================================ FILE: pkg/types/os.go ================================================ package types import ( "runtime" ) type OS int const ( Darwin OS = iota Linux ) func IsDarwin() bool { return runtime.GOOS == "darwin" } func IsLinux() bool { return runtime.GOOS == "linux" } type Capabilities struct { OS OS ExecutableMime string SharedLibrary string Arch string } func GetOS() OS { if runtime.GOOS == "darwin" { return Darwin } if runtime.GOOS == "linux" { return Linux } return -1 } var current *Capabilities func GetCapabilities() *Capabilities { if current != nil { return current } switch runtime.GOOS { case "darwin": current = &Capabilities{OS: Darwin, ExecutableMime: "application/x-mach-binary", SharedLibrary: "application/x-sharedlib"} case "linux": current = &Capabilities{OS: Linux, ExecutableMime: "application/x-executable", SharedLibrary: "application/x-sharedlib"} } current.Arch = runtime.GOARCH return current } ================================================ FILE: pkg/utils/utils.go ================================================ package utils import ( "crhuber/kelp/pkg/logging" "crhuber/kelp/pkg/types" "encoding/json" "fmt" "io" "log" "net/http" "os" "path/filepath" ) func DirExists(dir string) bool { info, err := os.Stat(dir) if err != nil { return false } return info.IsDir() } func FileExists(filename string) bool { info, err := os.Stat(filename) return !os.IsNotExist(err) && !info.IsDir() } func FilePathWalkDir(root string) ([]string, error) { var files []string err := filepath.Walk(root, func(path string, info os.FileInfo, _ error) error { if !info.IsDir() { files = append(files, path) } return nil }) return files, err } func CopyFile(source, destination string) error { from, err := os.Open(source) if err != nil { log.Fatal(err) } defer from.Close() to, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE, 0744) if err != nil { return err } defer to.Close() _, err = io.Copy(to, from) if err != nil { return err } return nil } func GetGithubRelease(owner, repo, release string) (*types.GithubRelease, error) { var url string if release == "latest" { logging.LogInfo("🌐 Getting releases for %s/%s:%s...", owner, repo, release) url = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/%s", owner, repo, release) } else { // try by tag logging.LogInfo("🌐 Getting releases by tag %s...", release) url = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", owner, repo, release) } // create client client := &http.Client{} req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } // set headers for github auth ghToken := os.Getenv("GITHUB_TOKEN") if ghToken != "" { logging.LogDebug("Using Github token in http request") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ghToken)) } // make request resp, err := client.Do(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("invalid HTTP status: %v", resp.StatusCode) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } ghr := types.GithubRelease{} if err := json.Unmarshal(body, &ghr); err != nil { return nil, err } return &ghr, nil } ================================================ FILE: pkg/utils/utils_test.go ================================================ package utils import ( "os" "path/filepath" "testing" "github.com/h2non/gock" "github.com/stretchr/testify/require" ) func TestGetGithubRelease(t *testing.T) { defer gock.Off() filename := filepath.Join("..", "..", "testdata", "helm-latest.json") jsonBytes, err := os.ReadFile(filename) if err != nil { t.Fatal("error reading testdata: ", err) } gock.New("https://api.github.com"). Get("/repos/helm/helm/releases/latest"). Reply(200). JSON(jsonBytes) ghr, err := GetGithubRelease("helm", "helm", "latest") require.NoError(t, err) require.Equal(t, "v4.0.4", ghr.TagName) } ================================================ FILE: testdata/helm-latest.json ================================================ { "url": "https://api.github.com/repos/helm/helm/releases/270052681", "assets_url": "https://api.github.com/repos/helm/helm/releases/270052681/assets", "upload_url": "https://uploads.github.com/repos/helm/helm/releases/270052681/assets{?name,label}", "html_url": "https://github.com/helm/helm/releases/tag/v4.0.4", "id": 270052681, "author": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "node_id": "RE_kwDOApspmc4QGK1J", "tag_name": "v4.0.4", "target_commitish": "main", "name": "Helm v4.0.4", "draft": false, "immutable": false, "prerelease": false, "created_at": "2025-12-13T01:05:06Z", "updated_at": "2025-12-13T01:23:13Z", "published_at": "2025-12-13T01:22:50Z", "assets": [ { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071815", "id": 328071815, "node_id": "RA_kwDOApspmc4TjfqH", "name": "helm-v4.0.4-darwin-amd64.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:943463856d9a7da8abfdb6421e2b7bef3197565b32ecea04bfb0a93fa15db2fe", "download_count": 266, "created_at": "2025-12-13T01:23:08Z", "updated_at": "2025-12-13T01:23:08Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-darwin-amd64.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071822", "id": 328071822, "node_id": "RA_kwDOApspmc4TjfqO", "name": "helm-v4.0.4-darwin-amd64.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:5c0fcfab9868357ab5f540c6d470c3cf0c9373ab33d353f56914ba1731b656be", "download_count": 170, "created_at": "2025-12-13T01:23:08Z", "updated_at": "2025-12-13T01:23:08Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-darwin-amd64.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071823", "id": 328071823, "node_id": "RA_kwDOApspmc4TjfqP", "name": "helm-v4.0.4-darwin-amd64.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:1db897efd998fcadc5edd249a828b5d0401d6c2bd643828d9c528f607362e458", "download_count": 172, "created_at": "2025-12-13T01:23:08Z", "updated_at": "2025-12-13T01:23:08Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-darwin-amd64.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071827", "id": 328071827, "node_id": "RA_kwDOApspmc4TjfqT", "name": "helm-v4.0.4-darwin-arm64.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:fd44bbc3d2b875ad61713fd13de0dff34864d5c52381c7247bd1dfbebbc7316a", "download_count": 187, "created_at": "2025-12-13T01:23:08Z", "updated_at": "2025-12-13T01:23:09Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-darwin-arm64.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071832", "id": 328071832, "node_id": "RA_kwDOApspmc4TjfqY", "name": "helm-v4.0.4-darwin-arm64.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:538510d78f375c6b2ef7d7b87ce33ee279680c32a670a33317c5c7a71228eee0", "download_count": 147, "created_at": "2025-12-13T01:23:08Z", "updated_at": "2025-12-13T01:23:09Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-darwin-arm64.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071835", "id": 328071835, "node_id": "RA_kwDOApspmc4Tjfqb", "name": "helm-v4.0.4-darwin-arm64.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:6f6b2b3704ba05d7357563868335155f0d4b2852f430b57bd167486ee8b481b9", "download_count": 153, "created_at": "2025-12-13T01:23:09Z", "updated_at": "2025-12-13T01:23:09Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-darwin-arm64.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071838", "id": 328071838, "node_id": "RA_kwDOApspmc4Tjfqe", "name": "helm-v4.0.4-linux-386.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:02fa09a1e5f6f40b33e1fe7a8e404cced85f47480c3d272f4d75f297afe64b8f", "download_count": 219, "created_at": "2025-12-13T01:23:09Z", "updated_at": "2025-12-13T01:23:09Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-386.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071840", "id": 328071840, "node_id": "RA_kwDOApspmc4Tjfqg", "name": "helm-v4.0.4-linux-386.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:ced78ed6756b7820b764b9487e7bee13d259ce8d943a1b6ad27f478b1bf019ed", "download_count": 164, "created_at": "2025-12-13T01:23:09Z", "updated_at": "2025-12-13T01:23:09Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-386.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071842", "id": 328071842, "node_id": "RA_kwDOApspmc4Tjfqi", "name": "helm-v4.0.4-linux-386.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:b33b73be03e14589873a63b59a01ddd2f75d63d6783b206abcf1ca3a29f9359d", "download_count": 166, "created_at": "2025-12-13T01:23:09Z", "updated_at": "2025-12-13T01:23:09Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-386.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071844", "id": 328071844, "node_id": "RA_kwDOApspmc4Tjfqk", "name": "helm-v4.0.4-linux-amd64.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:37184ca689c09c8fbe67801b3c0c6da602013f84514f18a5da40351fb16e716d", "download_count": 6591, "created_at": "2025-12-13T01:23:09Z", "updated_at": "2025-12-13T01:23:09Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-amd64.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071849", "id": 328071849, "node_id": "RA_kwDOApspmc4Tjfqp", "name": "helm-v4.0.4-linux-amd64.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:970ce8c0f959eb632867991e769ac0bcd8121bce15c4960f89d4cfe369268b03", "download_count": 6101, "created_at": "2025-12-13T01:23:09Z", "updated_at": "2025-12-13T01:23:10Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-amd64.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071850", "id": 328071850, "node_id": "RA_kwDOApspmc4Tjfqq", "name": "helm-v4.0.4-linux-amd64.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:d3255c9bb0a7a12fa18998ba2b0bb0f1704c0fea07bf45c8a078274376bfebe0", "download_count": 205, "created_at": "2025-12-13T01:23:10Z", "updated_at": "2025-12-13T01:23:10Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-amd64.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071851", "id": 328071851, "node_id": "RA_kwDOApspmc4Tjfqr", "name": "helm-v4.0.4-linux-arm.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:3bfc27ba402781a4ebd20a2afddca7ec31f3ae0ef66317f769ae9c4840cd03cd", "download_count": 146, "created_at": "2025-12-13T01:23:10Z", "updated_at": "2025-12-13T01:23:10Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-arm.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071853", "id": 328071853, "node_id": "RA_kwDOApspmc4Tjfqt", "name": "helm-v4.0.4-linux-arm.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:b7d7ed2334381758e159154c649b4045bca08c8aff72caf22ced967e2e10746d", "download_count": 137, "created_at": "2025-12-13T01:23:10Z", "updated_at": "2025-12-13T01:23:10Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-arm.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071856", "id": 328071856, "node_id": "RA_kwDOApspmc4Tjfqw", "name": "helm-v4.0.4-linux-arm.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:98694fe87b905ca078bc844dd1e9b6ed8bf73e761fda33fcb11acfb0fdd4a56f", "download_count": 139, "created_at": "2025-12-13T01:23:10Z", "updated_at": "2025-12-13T01:23:10Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-arm.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071858", "id": 328071858, "node_id": "RA_kwDOApspmc4Tjfqy", "name": "helm-v4.0.4-linux-arm64.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:29f58b92bc616ff27a96157c89f4c71b44ad74ff2b93225ef7e5bf6f04ca6444", "download_count": 1328, "created_at": "2025-12-13T01:23:10Z", "updated_at": "2025-12-13T01:23:10Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-arm64.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071859", "id": 328071859, "node_id": "RA_kwDOApspmc4Tjfqz", "name": "helm-v4.0.4-linux-arm64.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:85c2c1b29af5020aee6f865830ca7c78535582c08961e442c3841edfc4ae9bc6", "download_count": 1305, "created_at": "2025-12-13T01:23:10Z", "updated_at": "2025-12-13T01:23:10Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-arm64.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071868", "id": 328071868, "node_id": "RA_kwDOApspmc4Tjfq8", "name": "helm-v4.0.4-linux-arm64.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:7008fa1fe790ce223945651da31dec9e2147412ee6c77ffe9dfdb3a36e1fdde0", "download_count": 133, "created_at": "2025-12-13T01:23:10Z", "updated_at": "2025-12-13T01:23:11Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-arm64.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071876", "id": 328071876, "node_id": "RA_kwDOApspmc4TjfrE", "name": "helm-v4.0.4-linux-loong64.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:6494e3aa743c4e7e18cc9d7f5afdadbc6798a5a0e8fc63dd2285b3a951982481", "download_count": 143, "created_at": "2025-12-13T01:23:11Z", "updated_at": "2025-12-13T01:23:11Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-loong64.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071889", "id": 328071889, "node_id": "RA_kwDOApspmc4TjfrR", "name": "helm-v4.0.4-linux-loong64.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:8ae2f601edf1110027f54459dc40130a4395308bcdf75ec17a68f40b7706169a", "download_count": 143, "created_at": "2025-12-13T01:23:11Z", "updated_at": "2025-12-13T01:23:11Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-loong64.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071891", "id": 328071891, "node_id": "RA_kwDOApspmc4TjfrT", "name": "helm-v4.0.4-linux-loong64.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:51fc79563532f1e3949bdf3b254f2d3c255dc8b79b399fd94262b675e97d20d8", "download_count": 144, "created_at": "2025-12-13T01:23:11Z", "updated_at": "2025-12-13T01:23:11Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-loong64.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071892", "id": 328071892, "node_id": "RA_kwDOApspmc4TjfrU", "name": "helm-v4.0.4-linux-ppc64le.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:61848dd0d2c20ad62145b89adc9ddcf7bb3d26bb486292662b6593a210645720", "download_count": 51, "created_at": "2025-12-13T01:23:11Z", "updated_at": "2025-12-13T01:23:11Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-ppc64le.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071894", "id": 328071894, "node_id": "RA_kwDOApspmc4TjfrW", "name": "helm-v4.0.4-linux-ppc64le.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:7c5eb882502e657db2ea23d08974021f1d6585a2fd6acc8e7e2c9d2c65910f3a", "download_count": 54, "created_at": "2025-12-13T01:23:11Z", "updated_at": "2025-12-13T01:23:11Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-ppc64le.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071897", "id": 328071897, "node_id": "RA_kwDOApspmc4TjfrZ", "name": "helm-v4.0.4-linux-ppc64le.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:ab546edb11ea05ad21b3a10ed94c289c8cd1e91be856914bcda68feb30a74bc5", "download_count": 51, "created_at": "2025-12-13T01:23:11Z", "updated_at": "2025-12-13T01:23:11Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-ppc64le.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071898", "id": 328071898, "node_id": "RA_kwDOApspmc4Tjfra", "name": "helm-v4.0.4-linux-riscv64.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:91c1382737e7426f51c25e3643e24cd1e2c53a79d6564105b657dfc9ec33dcde", "download_count": 142, "created_at": "2025-12-13T01:23:11Z", "updated_at": "2025-12-13T01:23:12Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-riscv64.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071899", "id": 328071899, "node_id": "RA_kwDOApspmc4Tjfrb", "name": "helm-v4.0.4-linux-riscv64.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:a9d3b44f24caafa628ace829f96043f13bebfe19635134bd3e81aa16a51809dc", "download_count": 141, "created_at": "2025-12-13T01:23:12Z", "updated_at": "2025-12-13T01:23:12Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-riscv64.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071900", "id": 328071900, "node_id": "RA_kwDOApspmc4Tjfrc", "name": "helm-v4.0.4-linux-riscv64.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:f6ec68ffd30bf9b01083699097e67353eea81bba60164ba7c4d561f6bdce25e9", "download_count": 144, "created_at": "2025-12-13T01:23:12Z", "updated_at": "2025-12-13T01:23:12Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-riscv64.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071901", "id": 328071901, "node_id": "RA_kwDOApspmc4Tjfrd", "name": "helm-v4.0.4-linux-s390x.tar.gz.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:699c5924c1ca2d02229e43f3f0120ad98a74007e52e9eae82cd54e0f30adb098", "download_count": 52, "created_at": "2025-12-13T01:23:12Z", "updated_at": "2025-12-13T01:23:12Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-s390x.tar.gz.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071902", "id": 328071902, "node_id": "RA_kwDOApspmc4Tjfre", "name": "helm-v4.0.4-linux-s390x.tar.gz.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:72348b11c9e2c4868fd201a15657f4049f9c91c5cc51df5c0f2d7efb2eee7bd9", "download_count": 49, "created_at": "2025-12-13T01:23:12Z", "updated_at": "2025-12-13T01:23:12Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-s390x.tar.gz.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071903", "id": 328071903, "node_id": "RA_kwDOApspmc4Tjfrf", "name": "helm-v4.0.4-linux-s390x.tar.gz.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:c6c42a06b3d02ee3b45f74ae7799dcddfdb3d626d8447f0bfa915ae96d5d8394", "download_count": 51, "created_at": "2025-12-13T01:23:12Z", "updated_at": "2025-12-13T01:23:12Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-linux-s390x.tar.gz.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071904", "id": 328071904, "node_id": "RA_kwDOApspmc4Tjfrg", "name": "helm-v4.0.4-windows-amd64.zip.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:eb6d3871d204f77552c6e6783eec76b7182df3bd3ab60c4b95a34e6e73eba409", "download_count": 340, "created_at": "2025-12-13T01:23:12Z", "updated_at": "2025-12-13T01:23:12Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-windows-amd64.zip.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071905", "id": 328071905, "node_id": "RA_kwDOApspmc4Tjfrh", "name": "helm-v4.0.4-windows-amd64.zip.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:afd5bccf1a5ae673e7e765dc358e19f4a1ecc1a33b8bf1fedd8c04e085788779", "download_count": 181, "created_at": "2025-12-13T01:23:12Z", "updated_at": "2025-12-13T01:23:12Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-windows-amd64.zip.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071906", "id": 328071906, "node_id": "RA_kwDOApspmc4Tjfri", "name": "helm-v4.0.4-windows-amd64.zip.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:a813554ad03442e5b2ce6bd53ec6dd71c27b06f6bec449d1f5ebf2091d38db19", "download_count": 161, "created_at": "2025-12-13T01:23:12Z", "updated_at": "2025-12-13T01:23:13Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-windows-amd64.zip.sha256sum.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071907", "id": 328071907, "node_id": "RA_kwDOApspmc4Tjfrj", "name": "helm-v4.0.4-windows-arm64.zip.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:63cfea9ba48829939f459ade799583247955116dd3bcaae7eecb4ca575aca933", "download_count": 166, "created_at": "2025-12-13T01:23:13Z", "updated_at": "2025-12-13T01:23:13Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-windows-arm64.zip.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071909", "id": 328071909, "node_id": "RA_kwDOApspmc4Tjfrl", "name": "helm-v4.0.4-windows-arm64.zip.sha256.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:463780016e11b617f87a9c79b20513c61a0ddc9c27ebb42ee267c5a2e4654fed", "download_count": 128, "created_at": "2025-12-13T01:23:13Z", "updated_at": "2025-12-13T01:23:13Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-windows-arm64.zip.sha256.asc" }, { "url": "https://api.github.com/repos/helm/helm/releases/assets/328071910", "id": 328071910, "node_id": "RA_kwDOApspmc4Tjfrm", "name": "helm-v4.0.4-windows-arm64.zip.sha256sum.asc", "label": null, "uploader": { "login": "scottrigby", "id": 407675, "node_id": "MDQ6VXNlcjQwNzY3NQ==", "avatar_url": "https://avatars.githubusercontent.com/u/407675?v=4", "gravatar_id": "", "url": "https://api.github.com/users/scottrigby", "html_url": "https://github.com/scottrigby", "followers_url": "https://api.github.com/users/scottrigby/followers", "following_url": "https://api.github.com/users/scottrigby/following{/other_user}", "gists_url": "https://api.github.com/users/scottrigby/gists{/gist_id}", "starred_url": "https://api.github.com/users/scottrigby/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/scottrigby/subscriptions", "organizations_url": "https://api.github.com/users/scottrigby/orgs", "repos_url": "https://api.github.com/users/scottrigby/repos", "events_url": "https://api.github.com/users/scottrigby/events{/privacy}", "received_events_url": "https://api.github.com/users/scottrigby/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, "content_type": "application/octet-stream", "state": "uploaded", "size": 833, "digest": "sha256:4f27866327d78cca9bcafd836796bec8a0c79b15ac48a8001607593913a635ce", "download_count": 129, "created_at": "2025-12-13T01:23:13Z", "updated_at": "2025-12-13T01:23:13Z", "browser_download_url": "https://github.com/helm/helm/releases/download/v4.0.4/helm-v4.0.4-windows-arm64.zip.sha256sum.asc" } ], "tarball_url": "https://api.github.com/repos/helm/helm/tarball/v4.0.4", "zipball_url": "https://api.github.com/repos/helm/helm/zipball/v4.0.4", "body": "Helm v4.0.4 is a security fix for a Go CVE in the previous tag. This patch release rebuilds the Helm `v4.0.2` release with the latest Go toolchain, to fix the Go CVE. Users are encouraged to upgrade. Note that tag v4.0.3 was skipped due to a build failure.\r\n\r\nThe community keeps growing, and we'd love to see you there!\r\n\r\n- Join the discussion in [Kubernetes Slack](https://kubernetes.slack.com):\r\n - for questions and just to hang out\r\n - for discussing PRs, code, and bugs\r\n- Hang out at the Public Developer Call: Thursday, 9:30 Pacific via [Zoom](https://zoom.us/j/696660622)\r\n- Test, debug, and contribute charts: [ArtifactHub/packages](https://artifacthub.io/packages/search?kind=0)\r\n\r\n## Installation and Upgrading\r\n\r\nDownload Helm v4.0.4. The common platform binaries are here:\r\n\r\n- [MacOS amd64](https://get.helm.sh/helm-v4.0.4-darwin-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-darwin-amd64.tar.gz.sha256sum) / 73bcfd6ab000fdc95acf9fe1c59e8e47179426a653e45ae485889869d4a00523)\r\n- [MacOS arm64](https://get.helm.sh/helm-v4.0.4-darwin-arm64.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-darwin-arm64.tar.gz.sha256sum) / a7ea99937a9679b3935fa0a2b70e577aa1ea84e5856e7c0821ca6ffa064ea976)\r\n- [Linux amd64](https://get.helm.sh/helm-v4.0.4-linux-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-linux-amd64.tar.gz.sha256sum) / 29454bc351f4433e66c00f5d37841627cbbcc02e4c70a6d796529d355237671c)\r\n- [Linux arm](https://get.helm.sh/helm-v4.0.4-linux-arm.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-linux-arm.tar.gz.sha256sum) / 9255732e31b5aa5ee7b55be8497eea4723e3dfb08a63c37603ae0d15a9a9d82c)\r\n- [Linux arm64](https://get.helm.sh/helm-v4.0.4-linux-arm64.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-linux-arm64.tar.gz.sha256sum) / 16b88acc6503d646b7537a298e7389bef469c5cc9ebadf727547abe9f6a35903)\r\n- [Linux i386](https://get.helm.sh/helm-v4.0.4-linux-386.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-linux-386.tar.gz.sha256sum) / e6dbf45313bab48e51a2b7a5f3271a19bb3d8b9f07b4bb48ba342389d902af53)\r\n- [Linux loong64](https://get.helm.sh/helm-v4.0.4-linux-loong64.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-linux-loong64.tar.gz.sha256sum) / BlobNotFoundThe specified blob does not exist.\r\nRequestId:11673868-901e-003e-10cd-6b624b000000\r\nTime:2025-12-13T01:15:26.0922049Z)\r\n- [Linux ppc64le](https://get.helm.sh/helm-v4.0.4-linux-ppc64le.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-linux-ppc64le.tar.gz.sha256sum) / c108d181a0e29dadf281fbb4f4a0e0f2149922b119ec745ced1a5ae6f0918703)\r\n- [Linux s390x](https://get.helm.sh/helm-v4.0.4-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-linux-s390x.tar.gz.sha256sum) / cdf172c59379f0a3fe1db4743c16f122745fdaaebb2fbbfa40ce5722a4787717)\r\n- [Linux riscv64](https://get.helm.sh/helm-v4.0.4-linux-riscv64.tar.gz) ([checksum](https://get.helm.sh/helm-v4.0.4-linux-riscv64.tar.gz.sha256sum) / 2cf1c77d993bf5386e85249007bdaf38358d2516b18454212206a81b132e1330)\r\n- [Windows amd64](https://get.helm.sh/helm-v4.0.4-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-v4.0.4-windows-amd64.zip.sha256sum) / 135bffadd3c87aff8856e06efb366bea2a48ac4d1742d73af80250410246f14d)\r\n- [Windows arm64](https://get.helm.sh/helm-v4.0.4-windows-arm64.zip) ([checksum](https://get.helm.sh/helm-v4.0.4-windows-arm64.zip.sha256sum) / b65d05f15260e78311f463773f54fe68f6d74444b3c3e84cecf270cdb927cd8a)\r\n\r\nThis release was signed with `208D D36E D5BB 3745 A167 43A4 C7C6 FBB5 B91C 1155` and can be found at @scottrigby [keybase account](https://keybase.io/r6by). Please use the attached signatures for verifying this release using `gpg`.\r\n\r\nThe [Quickstart Guide](https://helm.sh/docs/intro/quickstart/) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://helm.sh/docs/intro/install/). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3) on any system with `bash`.\r\n\r\n## What's Next\r\n\r\n- 3.19.5 and 4.0.5 are the next patch releases and will be on January 14, 2026\r\n- 3.20.0 and 4.1.0 is the next minor releases and will be on January 21, 2026\r\n\r\n## Changelog\r\n\r\n- Bump v4.0.2 CVE deps cd700e0627b8d9a4997a7ab2bc3b712d0de4dcd3 (George Jenkins)\r\n- Use latest patch release of Go in releases 9db13ee5c343196f642c568a03e58d3221b324d6 (Matt Farina)", "reactions": { "url": "https://api.github.com/repos/helm/helm/releases/270052681/reactions", "total_count": 8, "+1": 8, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 }, "mentions_count": 1 }