├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── issue-report.md ├── dependabot.yml ├── release.yml └── workflows │ ├── build-test.yml │ ├── codeql-analysis.yml │ ├── compat-checks.yaml │ ├── dep-auto-merge.yml │ ├── dockerhub-push.yml │ ├── release-binary.yml │ ├── release-test.yml │ └── setup-test.yaml ├── .gitignore ├── .goreleaser.yaml ├── Dockerfile ├── LICENSE.md ├── Makefile ├── PRIVACY.md ├── README.md ├── cmd └── pdtm │ └── pdtm.go ├── examples └── main.go ├── go.mod ├── go.sum ├── internal └── runner │ ├── banner.go │ ├── options.go │ └── runner.go └── pkg ├── crud_test.go ├── gh.go ├── install.go ├── path ├── path.go ├── subpath.go ├── unix.go ├── windows.go ├── winpath.go └── winpath_unsafe.go ├── remove.go ├── types └── types.go ├── update.go ├── utils ├── helpers.go └── utils.go └── version └── version.go /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Ask an question / advise on using pdtm 5 | url: https://github.com/projectdiscovery/pdtm/discussions/categories/q-a 6 | about: Ask a question or request support for using pdtm 7 | 8 | - name: Share idea / feature to discuss for pdtm 9 | url: https://github.com/projectdiscovery/pdtm/discussions/categories/ideas 10 | about: Share idea / feature to discuss for pdtm 11 | 12 | - name: Connect with PD Team (Discord) 13 | url: https://discord.gg/projectdiscovery 14 | about: Connect with PD Team for direct communication -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request feature to implement in this project 4 | labels: 'Type: Enhancement' 5 | --- 6 | 7 | 13 | 14 | ### Please describe your feature request: 15 | 16 | 17 | ### Describe the use case of this feature: 18 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue report 3 | about: Create a report to help us to improve the project 4 | labels: 'Type: Bug' 5 | 6 | --- 7 | 8 | 13 | 14 | 15 | 16 | ### pdtm version: 17 | 18 | 19 | 20 | 21 | ### Current Behavior: 22 | 23 | 24 | ### Expected Behavior: 25 | 26 | 27 | ### Steps To Reproduce: 28 | 33 | 34 | 35 | ### Anything else: 36 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | 9 | # Maintain dependencies for go modules 10 | - package-ecosystem: "gomod" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | target-branch: "dev" 15 | commit-message: 16 | prefix: "chore" 17 | include: "scope" 18 | labels: 19 | - "Type: Maintenance" 20 | allow: 21 | - dependency-name: "github.com/projectdiscovery/*" 22 | 23 | # # Maintain dependencies for GitHub Actions 24 | # - package-ecosystem: "github-actions" 25 | # directory: "/" 26 | # schedule: 27 | # interval: "weekly" 28 | # target-branch: "dev" 29 | # commit-message: 30 | # prefix: "chore" 31 | # include: "scope" 32 | # labels: 33 | # - "Type: Maintenance" 34 | # 35 | # # Maintain dependencies for docker 36 | # - package-ecosystem: "docker" 37 | # directory: "/" 38 | # schedule: 39 | # interval: "weekly" 40 | # target-branch: "dev" 41 | # commit-message: 42 | # prefix: "chore" 43 | # include: "scope" 44 | # labels: 45 | # - "Type: Maintenance" -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | authors: 4 | - dependabot 5 | categories: 6 | - title: 🎉 New Features 7 | labels: 8 | - "Type: Enhancement" 9 | 10 | - title: 🐞 Bugs Fixes 11 | labels: 12 | - "Type: Bug" 13 | 14 | - title: 🔨 Maintenance 15 | labels: 16 | - "Type: Maintenance" 17 | 18 | - title: Other Changes 19 | labels: 20 | - "*" -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: 🔨 Build Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | lint: 12 | name: Lint Test 13 | if: "${{ !endsWith(github.actor, '[bot]') }}" 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: projectdiscovery/actions/setup/go@v1 18 | - name: Run golangci-lint 19 | uses: projectdiscovery/actions/golangci-lint/v2@v1 20 | with: 21 | version: latest 22 | args: --timeout 5m 23 | working-directory: . 24 | 25 | build: 26 | name: Test Builds 27 | needs: [lint] 28 | runs-on: ${{ matrix.os }} 29 | strategy: 30 | matrix: 31 | os: [ubuntu-latest, windows-latest, macOS-latest] 32 | steps: 33 | - name: Set up Go 34 | uses: actions/setup-go@v4 35 | with: 36 | go-version: 1.24.x 37 | 38 | - name: Check out code 39 | uses: actions/checkout@v3 40 | 41 | - name: Build 42 | run: go build . 43 | working-directory: cmd/pdtm/ 44 | 45 | - name: Test 46 | run: go test ./... 47 | working-directory: . 48 | env: 49 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 50 | 51 | - name: Race Condition Tests 52 | run: go run -race . 53 | working-directory: cmd/pdtm/ 54 | 55 | - name: Test Example Code 56 | run: go run . 57 | working-directory: examples/ 58 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: 🚨 CodeQL Analysis 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | paths: 7 | - '**.go' 8 | - '**.mod' 9 | branches: 10 | - dev 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'go' ] 25 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 26 | 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v3 30 | 31 | # Initializes the CodeQL tools for scanning. 32 | - name: Initialize CodeQL 33 | uses: github/codeql-action/init@v2 34 | with: 35 | languages: ${{ matrix.language }} 36 | 37 | - name: Autobuild 38 | uses: github/codeql-action/autobuild@v2 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v2 -------------------------------------------------------------------------------- /.github/workflows/compat-checks.yaml: -------------------------------------------------------------------------------- 1 | name: ♾️ Compatibility Checks 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | branches: 7 | - dev 8 | 9 | jobs: 10 | check: 11 | if: github.actor == 'dependabot[bot]' 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: projectdiscovery/actions/setup/go/compat-checks@master 18 | with: 19 | go-version-file: 'go.mod' 20 | -------------------------------------------------------------------------------- /.github/workflows/dep-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: 🤖 Auto Merge 2 | 3 | on: 4 | pull_request_review: 5 | types: [submitted] 6 | workflow_run: 7 | workflows: ["♾️ Compatibility Check"] 8 | types: 9 | - completed 10 | 11 | permissions: 12 | pull-requests: write 13 | issues: write 14 | repository-projects: write 15 | 16 | jobs: 17 | auto-merge: 18 | runs-on: ubuntu-latest 19 | if: github.actor == 'dependabot[bot]' 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | token: ${{ secrets.DEPENDABOT_PAT }} 24 | 25 | - uses: ahmadnassri/action-dependabot-auto-merge@v2 26 | with: 27 | github-token: ${{ secrets.DEPENDABOT_PAT }} 28 | target: all -------------------------------------------------------------------------------- /.github/workflows/dockerhub-push.yml: -------------------------------------------------------------------------------- 1 | name: 🌥 Docker Push 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["🎉 Release Binary"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest-16-cores 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Get Github tag 18 | id: meta 19 | run: | 20 | curl --silent "https://api.github.com/repos/projectdiscovery/pdtm/releases/latest" | jq -r .tag_name | xargs -I {} echo TAG={} >> $GITHUB_OUTPUT 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v2 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v2 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v2 30 | with: 31 | username: ${{ secrets.DOCKER_USERNAME }} 32 | password: ${{ secrets.DOCKER_TOKEN }} 33 | 34 | - name: Build and push 35 | uses: docker/build-push-action@v4 36 | with: 37 | context: . 38 | platforms: linux/amd64,linux/arm64,linux/arm 39 | push: true 40 | tags: projectdiscovery/pdtm:latest,projectdiscovery/pdtm:${{ steps.meta.outputs.TAG }} -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: 🎉 Release Binary 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | workflow_dispatch: 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest-16-cores 12 | steps: 13 | - name: "Check out code" 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: "Set up Go" 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: 1.24.x 22 | check-latest: true 23 | cache: true 24 | 25 | - name: "Create release on GitHub" 26 | uses: goreleaser/goreleaser-action@v4 27 | with: 28 | args: "release --clean" 29 | version: latest 30 | workdir: . 31 | env: 32 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 33 | SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" 34 | DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" 35 | DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" -------------------------------------------------------------------------------- /.github/workflows/release-test.yml: -------------------------------------------------------------------------------- 1 | name: 🔨 Release Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | - '**.yml' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | release-test: 13 | runs-on: ubuntu-latest-16-cores 14 | steps: 15 | - name: "Check out code" 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version: 1.24.x 24 | 25 | - name: release test 26 | uses: goreleaser/goreleaser-action@v4 27 | with: 28 | args: "release --clean --snapshot" 29 | version: latest 30 | workdir: . -------------------------------------------------------------------------------- /.github/workflows/setup-test.yaml: -------------------------------------------------------------------------------- 1 | name: 🔨 Setup Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | workflow_dispatch: 9 | 10 | env: 11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | 13 | jobs: 14 | build: 15 | name: Test Setups 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, windows-latest, macOS-latest] 20 | steps: 21 | - name: Set up Go 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version: 1.24.x 25 | 26 | - name: Check out code 27 | uses: actions/checkout@v3 28 | 29 | - name: Dry Run - No Tool Installed 30 | run: go run . 31 | working-directory: cmd/pdtm/ 32 | env: 33 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 34 | 35 | - name: Install Run - Setup All The Tools 36 | run: go run . -ia 37 | working-directory: cmd/pdtm/ 38 | env: 39 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 40 | 41 | - name: Add binaries folder to ENV PATH (Unix-Like) 42 | if: runner.os != 'Windows' 43 | run: go run . -show-path >> $GITHUB_PATH 44 | working-directory: cmd/pdtm/ 45 | 46 | - name: Add binaries folder to ENV PATH (Windows) 47 | if: runner.os == 'Windows' 48 | run: | 49 | go run . -show-path | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append 50 | working-directory: cmd/pdtm/ 51 | 52 | - name: Checking tools existence 53 | run: | 54 | go run . | grep -v "not installed" 55 | working-directory: cmd/pdtm/ 56 | 57 | - name: Update Run - Update All The Tools 58 | run: go run . -ua 59 | working-directory: cmd/pdtm/ 60 | env: 61 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 62 | 63 | - name: Remove Run - Remove All The Tools 64 | run: go run . -ra 65 | working-directory: cmd/pdtm/ 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pdtm 2 | dist/ 3 | .idea 4 | .vscode 5 | .devcontainer -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - windows 10 | - linux 11 | - darwin 12 | goarch: 13 | - amd64 14 | - '386' 15 | - arm 16 | - arm64 17 | 18 | ignore: 19 | - goos: darwin 20 | goarch: '386' 21 | - goos: windows 22 | goarch: 'arm' 23 | - goos: windows 24 | goarch: 'arm64' 25 | 26 | binary: '{{ .ProjectName }}' 27 | main: cmd/{{ .ProjectName }}/{{ .ProjectName }}.go 28 | 29 | archives: 30 | - format: zip 31 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' 32 | 33 | checksum: 34 | algorithm: sha256 35 | 36 | announce: 37 | slack: 38 | enabled: true 39 | channel: '#release' 40 | username: GoReleaser 41 | message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}' 42 | 43 | discord: 44 | enabled: true 45 | message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.3-alpine AS builder 2 | RUN apk add --no-cache git gcc musl-dev 3 | WORKDIR /app 4 | COPY . /app 5 | RUN go mod download 6 | RUN go build ./cmd/pdtm 7 | 8 | FROM alpine:latest 9 | RUN apk add --no-cache bind-tools ca-certificates 10 | COPY --from=builder /app/pdtm /usr/local/bin/ 11 | 12 | ENTRYPOINT ["pdtm"] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ProjectDiscovery, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOMOD=$(GOCMD) mod 5 | GOTEST=$(GOCMD) test 6 | GOFLAGS := -v 7 | # This should be disabled if the binary uses pprof 8 | LDFLAGS := -s -w 9 | 10 | ifneq ($(shell go env GOOS),darwin) 11 | LDFLAGS := -extldflags "-static" 12 | endif 13 | 14 | all: build 15 | build: 16 | $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "pdtm" cmd/pdtm/pdtm.go 17 | test: 18 | $(GOTEST) $(GOFLAGS) ./... 19 | tidy: 20 | $(GOMOD) tidy 21 | -------------------------------------------------------------------------------- /PRIVACY.md: -------------------------------------------------------------------------------- 1 | ## Privacy Policy 2 | 3 | This privacy policy (the "Policy") is intended to inform you of the types of information that we collect from uses of `https://api.pdtm.sh` API, as well as how we use, share, and protect that information. By using our API, you consent to the collection and use of your information as described in this Policy. 4 | 5 | ## Information Collection 6 | 7 | We collect the following types of information from users of API: 8 | 9 | - OS / Vendor / Arch 10 | - Go version 11 | - MachineID ([Hashed](https://github.com/denisbrodbeck/machineid#function-protectedidappid-string-string-error)) 12 | - IP address 13 | 14 | This information is collected automatically when the API is used and is used to improve the project functionality / availability. 15 | 16 | ## Use of Information 17 | 18 | We use the information collected from users of our API to improve the performance and functionality of the OSS Projects. This may include analyzing usage patterns, troubleshooting technical issues, and identifying areas for improvement. 19 | 20 | ## Sharing of Information 21 | 22 | We do not share the information collected from users of our API with any third parties, except as required by law. 23 | 24 | ## Protection of Information 25 | 26 | We take reasonable measures to protect the information collected from users of our API from unauthorized access, use, or disclosure. However, no security measures are perfect, and we cannot guarantee the absolute security of your information. 27 | 28 | ## Changes to this Policy 29 | 30 | We reserve the right to change this Policy at any time. If we make changes to this Policy, we will update the date at the top of this page. 31 | 32 | If you have any questions or concerns about this Policy, please contact us at contact@projectdiscovery.io 33 | 34 | Last updated: April 1, 2024 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |

5 | 6 |

ProjectDiscovery's Open Source Tool Manager

7 | 8 |

9 | 10 | 11 | 12 | 13 | 14 |

15 | 16 |

17 | Features • 18 | Installation • 19 | Usage • 20 | Running pdtm • 21 | Join Discord 22 | 23 | **pdtm** is a simple and easy-to-use golang based tool for managing open source projects from ProjectDiscovery. 24 | 25 |

26 | 27 |

28 | 29 |

30 | 31 | ## Installation 32 | 33 | 34 | **`pdtm`** requires **go1.24.3** to install successfully. Run the following command to install the latest version: 35 | 36 | 1. Install using go install - 37 | 38 | ```sh 39 | go install -v github.com/projectdiscovery/pdtm/cmd/pdtm@latest 40 | ``` 41 | 42 | 2. Install by downloading binary from https://github.com/projectdiscovery/pdtm/releases 43 | 44 | 45 | 46 |
47 | 48 | > **Notes**: 49 | 50 | > - *Currently, projects are installed by downloading the released project binary. This means that projects can only be installed on the platforms for which binaries have been published.* 51 | > - *The path $HOME/.pdtm/go/bin is added to the $PATH variable by default* 52 | 53 |
54 | 55 | 56 | 57 | ## Usage: 58 | 59 | 60 | ```console 61 | pdtm is a simple and easy-to-use golang based tool for managing open source projects from ProjectDiscovery 62 | 63 | Usage: 64 | ./pdtm [flags] 65 | 66 | Flags: 67 | CONFIG: 68 | -config string cli flag configuration file (default "$HOME/.config/pdtm/config.yaml") 69 | -bp, -binary-path string custom location to download project binary (default "$HOME/.pdtm/go/bin") 70 | 71 | INSTALL: 72 | -i, -install string[] install single or multiple project by name (comma separated) 73 | -ia, -install-all install all the projects 74 | -ip, -install-path append path to PATH environment variables 75 | -igp, -install-go-path append GOBIN/GOPATH to PATH environment variables 76 | 77 | UPDATE: 78 | -u, -update string[] update single or multiple project by name (comma separated) 79 | -ua, -update-all update all the projects 80 | -up, -self-update update pdtm to latest version 81 | -duc, -disable-update-check disable automatic pdtm update check 82 | 83 | REMOVE: 84 | -r, -remove string[] remove single or multiple project by name (comma separated) 85 | -ra, -remove-all remove all the projects 86 | -rp, -remove-path remove path from PATH environment variables 87 | 88 | DEBUG: 89 | -sp, -show-path show the current binary path then exit 90 | -version show version of the project 91 | -v, -verbose show verbose output 92 | -nc, -no-color disable output content coloring (ANSI escape codes) 93 | -disable-changelog, -dc disable release changelog in output 94 | ``` 95 | 96 | ## Running pdtm 97 | 98 | ```console 99 | $ pdtm -install-all 100 | ____ 101 | ____ ____/ / /_____ ___ 102 | / __ \/ __ / __/ __ __ \ 103 | / /_/ / /_/ / /_/ / / / / / 104 | / .___/\__,_/\__/_/ /_/ /_/ 105 | /_/ v0.0.1 106 | 107 | projectdiscovery.io 108 | 109 | [INF] Installed httpx v1.1.1 110 | [INF] Installed nuclei v2.6.3 111 | [INF] Installed naabu v2.6.3 112 | [INF] Installed dnsx v2.6.3 113 | ``` 114 | 115 | ### Todo 116 | 117 | - support for go setup + project install from source 118 | - support for installing from source as fallback option 119 | 120 | -------- 121 | 122 |
123 | 124 | **pdtm** is made with ❤️ by the [projectdiscovery](https://projectdiscovery.io) team and distributed under [MIT License](LICENSE). 125 | 126 | 127 | Join Discord 128 | 129 |
130 | -------------------------------------------------------------------------------- /cmd/pdtm/pdtm.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "github.com/projectdiscovery/gologger" 10 | "github.com/projectdiscovery/pdtm/internal/runner" 11 | ) 12 | 13 | func main() { 14 | options := runner.ParseOptions() 15 | pdtmRunner, err := runner.NewRunner(options) 16 | if err != nil { 17 | gologger.Fatal().Msgf("Could not create runner: %s\n", err) 18 | } 19 | 20 | c := make(chan os.Signal, 1) 21 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 22 | // Setup close handler 23 | go func() { 24 | <-c 25 | fmt.Println("\r- Ctrl+C pressed in Terminal, Exiting...") 26 | pdtmRunner.Close() 27 | os.Exit(0) 28 | }() 29 | 30 | err = pdtmRunner.Run() 31 | if err != nil { 32 | gologger.Fatal().Msgf("Could not run pdtm: %s\n", err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/projectdiscovery/goflags" 9 | "github.com/projectdiscovery/pdtm/pkg/utils" 10 | ) 11 | 12 | type options struct { 13 | DisableUpdateCheck bool 14 | } 15 | 16 | func main() { 17 | options := &options{} 18 | flagSet := goflags.NewFlagSet() 19 | toolName := "nuclei" 20 | 21 | flagSet.CreateGroup("update", "Update", 22 | flagSet.CallbackVarP(utils.GetUpdaterCallback(toolName), "update", "up", fmt.Sprintf("update %v to the latest released version", toolName)), 23 | flagSet.BoolVarP(&options.DisableUpdateCheck, "disable-update-check", "duc", false, "disable automatic update check"), 24 | ) 25 | 26 | if err := flagSet.Parse(); err != nil { 27 | panic(err) 28 | } 29 | 30 | if !options.DisableUpdateCheck { 31 | home, err := os.UserHomeDir() 32 | if err != nil { 33 | panic(err) 34 | } 35 | basePath := filepath.Join(home, ".pdtm/go/bin") 36 | msg := utils.GetVersionCheckCallback(toolName, basePath)() 37 | fmt.Println(msg) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/projectdiscovery/pdtm 2 | 3 | go 1.24.3 4 | 5 | require ( 6 | github.com/charmbracelet/glamour v0.10.0 7 | github.com/google/go-github v17.0.0+incompatible 8 | github.com/projectdiscovery/goflags v0.1.74 9 | github.com/projectdiscovery/gologger v1.1.54 10 | github.com/projectdiscovery/utils v0.4.18 11 | github.com/stretchr/testify v1.10.0 12 | golang.org/x/oauth2 v0.29.0 13 | golang.org/x/sys v0.32.0 14 | ) 15 | 16 | require ( 17 | aead.dev/minisign v0.3.0 // indirect 18 | github.com/Masterminds/semver/v3 v3.3.1 // indirect 19 | github.com/STARRY-S/zip v0.2.3 // indirect 20 | github.com/VividCortex/ewma v1.2.0 // indirect 21 | github.com/alecthomas/chroma/v2 v2.17.2 // indirect 22 | github.com/andybalholm/brotli v1.1.1 // indirect 23 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 24 | github.com/bodgit/plumbing v1.3.0 // indirect 25 | github.com/bodgit/sevenzip v1.6.1 // indirect 26 | github.com/bodgit/windows v1.0.1 // indirect 27 | github.com/charmbracelet/colorprofile v0.3.1 // indirect 28 | github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect 29 | github.com/charmbracelet/x/ansi v0.9.2 // indirect 30 | github.com/charmbracelet/x/cellbuf v0.0.13 // indirect 31 | github.com/charmbracelet/x/exp/slice v0.0.0-20250501183327-ad3bc78c6a81 // indirect 32 | github.com/charmbracelet/x/term v0.2.1 // indirect 33 | github.com/cheggaaa/pb/v3 v3.1.7 // indirect 34 | github.com/davecgh/go-spew v1.1.1 // indirect 35 | github.com/dlclark/regexp2 v1.11.5 // indirect 36 | github.com/ebitengine/purego v0.8.4 // indirect 37 | github.com/fatih/color v1.18.0 // indirect 38 | github.com/go-ole/go-ole v1.3.0 // indirect 39 | github.com/google/go-github/v30 v30.1.0 // indirect 40 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 41 | github.com/google/uuid v1.6.0 // indirect 42 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 43 | github.com/klauspost/compress v1.18.0 // indirect 44 | github.com/klauspost/pgzip v1.2.6 // indirect 45 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 46 | github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect 47 | github.com/mattn/go-colorable v0.1.14 // indirect 48 | github.com/mattn/go-isatty v0.0.20 // indirect 49 | github.com/mattn/go-runewidth v0.0.16 // indirect 50 | github.com/mholt/archives v0.1.1 // indirect 51 | github.com/minio/minlz v1.0.0 // indirect 52 | github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect 53 | github.com/muesli/reflow v0.3.0 // indirect 54 | github.com/muesli/termenv v0.16.0 // indirect 55 | github.com/nwaples/rardecode/v2 v2.1.1 // indirect 56 | github.com/pierrec/lz4/v4 v4.1.22 // indirect 57 | github.com/pmezard/go-difflib v1.0.0 // indirect 58 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect 59 | github.com/projectdiscovery/blackrock v0.0.1 // indirect 60 | github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 // indirect 61 | github.com/rivo/uniseg v0.4.7 // indirect 62 | github.com/shirou/gopsutil/v3 v3.24.5 // indirect 63 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 64 | github.com/sorairolake/lzip-go v0.3.7 // indirect 65 | github.com/spf13/afero v1.14.0 // indirect 66 | github.com/therootcompany/xz v1.0.1 // indirect 67 | github.com/tidwall/gjson v1.18.0 // indirect 68 | github.com/tidwall/match v1.1.1 // indirect 69 | github.com/tidwall/pretty v1.2.1 // indirect 70 | github.com/tklauser/go-sysconf v0.3.15 // indirect 71 | github.com/tklauser/numcpus v0.10.0 // indirect 72 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 73 | github.com/yuin/goldmark v1.7.11 // indirect 74 | github.com/yuin/goldmark-emoji v1.0.6 // indirect 75 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 76 | github.com/zcalusic/sysinfo v1.1.3 // indirect 77 | go4.org v0.0.0-20230225012048-214862532bf5 // indirect 78 | golang.org/x/crypto v0.37.0 // indirect 79 | golang.org/x/sync v0.13.0 // indirect 80 | golang.org/x/term v0.31.0 // indirect 81 | golang.org/x/text v0.24.0 // indirect 82 | ) 83 | 84 | require ( 85 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 86 | github.com/aymerick/douceur v0.2.0 // indirect 87 | github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect 88 | github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect 89 | github.com/google/go-querystring v1.1.0 // indirect 90 | github.com/gorilla/css v1.0.1 // indirect 91 | github.com/json-iterator/go v1.1.12 // indirect 92 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 93 | github.com/logrusorgru/aurora/v4 v4.0.0 94 | github.com/microcosm-cc/bluemonday v1.0.27 // indirect 95 | github.com/miekg/dns v1.1.65 // indirect 96 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 97 | github.com/modern-go/reflect2 v1.0.2 // indirect 98 | github.com/pkg/errors v0.9.1 // indirect 99 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect 100 | github.com/ulikunitz/xz v0.5.12 // indirect 101 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect 102 | golang.org/x/mod v0.24.0 // indirect 103 | golang.org/x/net v0.39.0 // indirect 104 | golang.org/x/tools v0.32.0 // indirect 105 | gopkg.in/djherbis/times.v1 v1.3.0 // indirect 106 | gopkg.in/yaml.v3 v3.0.1 // indirect 107 | ) 108 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= 2 | aead.dev/minisign v0.3.0 h1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA= 3 | aead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y= 4 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 5 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 6 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 7 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 8 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 9 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 10 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 11 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 12 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 13 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 14 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 15 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 16 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 17 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 18 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 19 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 20 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 21 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 22 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 23 | github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= 24 | github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 25 | github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= 26 | github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= 27 | github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= 28 | github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= 29 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 30 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 31 | github.com/alecthomas/chroma/v2 v2.17.2 h1:Rm81SCZ2mPoH+Q8ZCc/9YvzPUN/E7HgPiPJD8SLV6GI= 32 | github.com/alecthomas/chroma/v2 v2.17.2/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk= 33 | github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 34 | github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 35 | github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 36 | github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 37 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= 38 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 39 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 40 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 41 | github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= 42 | github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= 43 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 44 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 45 | github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= 46 | github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= 47 | github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4= 48 | github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8= 49 | github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= 50 | github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= 51 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 52 | github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= 53 | github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0= 54 | github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= 55 | github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= 56 | github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= 57 | github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= 58 | github.com/charmbracelet/x/ansi v0.9.2 h1:92AGsQmNTRMzuzHEYfCdjQeUzTrgE1vfO5/7fEVoXdY= 59 | github.com/charmbracelet/x/ansi v0.9.2/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= 60 | github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= 61 | github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 62 | github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= 63 | github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= 64 | github.com/charmbracelet/x/exp/slice v0.0.0-20250501183327-ad3bc78c6a81 h1:WcoOeReajHsx60hzixuNsv+QsCnibNmaALJB7G1qfbg= 65 | github.com/charmbracelet/x/exp/slice v0.0.0-20250501183327-ad3bc78c6a81/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms= 66 | github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 67 | github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 68 | github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= 69 | github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= 70 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 71 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 72 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 73 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 74 | github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a h1:Ohw57yVY2dBTt+gsC6aZdteyxwlxfbtgkFEMTEkwgSw= 75 | github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= 76 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 77 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 78 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 79 | github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= 80 | github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 81 | github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= 82 | github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= 83 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 84 | github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= 85 | github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 86 | github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= 87 | github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 88 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 89 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 90 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 91 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 92 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 93 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 94 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 95 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 96 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 97 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 98 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 99 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 100 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 101 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 102 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 103 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 104 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 105 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 106 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 107 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 108 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 109 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 110 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 111 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 112 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 113 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 114 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 115 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 116 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 117 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 118 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 119 | github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= 120 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 121 | github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= 122 | github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= 123 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 124 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 125 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 126 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 127 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 128 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 129 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 130 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 131 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 132 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 133 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 134 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 135 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 136 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 137 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 138 | github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 139 | github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 140 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 141 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 142 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 143 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 144 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 145 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 146 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 147 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 148 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 149 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 150 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 151 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 152 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 153 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 154 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 155 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 156 | github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= 157 | github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 158 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 159 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 160 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 161 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 162 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 163 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 164 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 165 | github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA= 166 | github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ= 167 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 168 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 169 | github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= 170 | github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= 171 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 172 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 173 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 174 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 175 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 176 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 177 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 178 | github.com/mholt/archives v0.1.1 h1:c7J3qXN1FB54y0qiUXiq9Bxk4eCUc8pdXWwOhZdRzeY= 179 | github.com/mholt/archives v0.1.1/go.mod h1:FQVz01Q2uXKB/35CXeW/QFO23xT+hSCGZHVtha78U4I= 180 | github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 181 | github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 182 | github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc= 183 | github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= 184 | github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ= 185 | github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= 186 | github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc= 187 | github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= 188 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 189 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 190 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 191 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 192 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 193 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 194 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 195 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 196 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 197 | github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew= 198 | github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= 199 | github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= 200 | github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 201 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 202 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 203 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 204 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 205 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= 206 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 207 | github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= 208 | github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= 209 | github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c= 210 | github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4= 211 | github.com/projectdiscovery/gologger v1.1.54 h1:WMzvJ8j/4gGfPKpCttSTaYCVDU1MWQSJnk3wU8/U6Ws= 212 | github.com/projectdiscovery/gologger v1.1.54/go.mod h1:vza/8pe2OKOt+ujFWncngknad1XWr8EnLKlbcejOyUE= 213 | github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 h1:ZScLodGSezQVwsQDtBSMFp72WDq0nNN+KE/5DHKY5QE= 214 | github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI= 215 | github.com/projectdiscovery/utils v0.4.18 h1:cSjMOLXI5gAajfA6KV+0iQG4dGx2IHWLQyND/Snvw7k= 216 | github.com/projectdiscovery/utils v0.4.18/go.mod h1:y5gnpQn802iEWqf0djTRNskJlS62P5eqe1VS1+ah0tk= 217 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 218 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 219 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 220 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 221 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 222 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 223 | github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= 224 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= 225 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= 226 | github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= 227 | github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= 228 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 229 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 230 | github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= 231 | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 232 | github.com/sorairolake/lzip-go v0.3.7 h1:vP2uiD/NoklLyzYMdgOWkZME0ulkSfVTTE4MNRKCwNs= 233 | github.com/sorairolake/lzip-go v0.3.7/go.mod h1:THOHr0FlNVCw2eOIEE9shFJAG1QxQg/pf2XUPAmNIqg= 234 | github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= 235 | github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= 236 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 237 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 238 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 239 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 240 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 241 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 242 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 243 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 244 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 245 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 246 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 247 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 248 | github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= 249 | github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= 250 | github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= 251 | github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 252 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 253 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 254 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 255 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 256 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 257 | github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= 258 | github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= 259 | github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= 260 | github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= 261 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 262 | github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= 263 | github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 264 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 265 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 266 | github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= 267 | github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= 268 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 269 | github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo= 270 | github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 271 | github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= 272 | github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= 273 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 274 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 275 | github.com/zcalusic/sysinfo v1.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0= 276 | github.com/zcalusic/sysinfo v1.1.3/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4= 277 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 278 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 279 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 280 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 281 | go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= 282 | go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= 283 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 284 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 285 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 286 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 287 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 288 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 289 | golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 290 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 291 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 292 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 293 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 294 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 295 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 296 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 297 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 298 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 299 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 300 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= 301 | golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= 302 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 303 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 304 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 305 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 306 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 307 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 308 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 309 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 310 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 311 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 312 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 313 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 314 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 315 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 316 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 317 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 318 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 319 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 320 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 321 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 322 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 323 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 324 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 325 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 326 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 327 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 328 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 329 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 330 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 331 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 332 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 333 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 334 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 335 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 336 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 337 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 338 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 339 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 340 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 341 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 342 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 343 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 344 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 345 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 346 | golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= 347 | golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 348 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 349 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 350 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 351 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 352 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 353 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 354 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 355 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 356 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 357 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 358 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 359 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 360 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 361 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 362 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 363 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 364 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 365 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 366 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 367 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 368 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 369 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 370 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 371 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 372 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 373 | golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 374 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 375 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 376 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 377 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 378 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 379 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 380 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 381 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 382 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 383 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 384 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 385 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 386 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 387 | golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 388 | golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 389 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 390 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 391 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 392 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 393 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 394 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 395 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 396 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 397 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 398 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 399 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 400 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 401 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 402 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 403 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 404 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 405 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 406 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 407 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 408 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 409 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 410 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 411 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 412 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 413 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 414 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 415 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 416 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 417 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 418 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 419 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 420 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 421 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 422 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 423 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 424 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 425 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 426 | golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= 427 | golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= 428 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 429 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 430 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 431 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 432 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 433 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 434 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 435 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 436 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 437 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 438 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 439 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 440 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 441 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 442 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 443 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 444 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 445 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 446 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 447 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 448 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 449 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 450 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 451 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 452 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 453 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 454 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 455 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 456 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 457 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 458 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 459 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 460 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 461 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 462 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 463 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 464 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 465 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 466 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 467 | gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= 468 | gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= 469 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 470 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 471 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 472 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 473 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 474 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 475 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 476 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 477 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 478 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 479 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 480 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 481 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 482 | -------------------------------------------------------------------------------- /internal/runner/banner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "github.com/projectdiscovery/gologger" 5 | updateutils "github.com/projectdiscovery/utils/update" 6 | ) 7 | 8 | const version = "v0.1.3" 9 | 10 | var banner = ` 11 | ____ 12 | ____ ____/ / /_____ ___ 13 | / __ \/ __ / __/ __ __ \ 14 | / /_/ / /_/ / /_/ / / / / / 15 | / .___/\__,_/\__/_/ /_/ /_/ 16 | /_/ 17 | ` 18 | 19 | // showBanner is used to show the banner to the user 20 | func showBanner() { 21 | gologger.Print().Msgf("%s\n", banner) 22 | gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n") 23 | } 24 | 25 | // GetUpdateCallback returns a callback function that updates pdtm 26 | func GetUpdateCallback() func() { 27 | return func() { 28 | showBanner() 29 | updateutils.GetUpdateToolCallback("pdtm", version)() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/runner/options.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/logrusorgru/aurora/v4" 8 | "github.com/projectdiscovery/goflags" 9 | "github.com/projectdiscovery/gologger" 10 | "github.com/projectdiscovery/gologger/formatter" 11 | "github.com/projectdiscovery/gologger/levels" 12 | fileutil "github.com/projectdiscovery/utils/file" 13 | updateutils "github.com/projectdiscovery/utils/update" 14 | ) 15 | 16 | var ( 17 | // retrieve home directory or fail 18 | homeDir = func() string { 19 | home, err := os.UserHomeDir() 20 | if err != nil { 21 | gologger.Fatal().Msgf("Failed to get user home directory: %s", err) 22 | } 23 | return home 24 | }() 25 | 26 | defaultConfigLocation = filepath.Join(homeDir, ".config/pdtm/config.yaml") 27 | cacheFile = filepath.Join(homeDir, ".config/pdtm/cache.json") 28 | defaultPath = filepath.Join(homeDir, ".pdtm/go/bin") 29 | ) 30 | 31 | var au *aurora.Aurora 32 | 33 | // Options contains the configuration options for tuning the enumeration process. 34 | type Options struct { 35 | ConfigFile string 36 | Path string 37 | NoColor bool 38 | SetPath bool 39 | SetGoPath bool 40 | UnSetPath bool 41 | 42 | Install goflags.StringSlice 43 | Update goflags.StringSlice 44 | Remove goflags.StringSlice 45 | 46 | InstallAll bool 47 | UpdateAll bool 48 | RemoveAll bool 49 | 50 | Verbose bool 51 | Silent bool 52 | Version bool 53 | ShowPath bool 54 | DisableUpdateCheck bool 55 | DisableChangeLog bool 56 | } 57 | 58 | // ParseOptions parses the command line flags provided by a user 59 | func ParseOptions() *Options { 60 | options := &Options{} 61 | flagSet := goflags.NewFlagSet() 62 | 63 | flagSet.SetDescription(`pdtm is a simple and easy-to-use golang based tool for managing open source projects from ProjectDiscovery`) 64 | 65 | flagSet.CreateGroup("config", "Config", 66 | flagSet.StringVar(&options.ConfigFile, "config", defaultConfigLocation, "cli flag configuration file"), 67 | flagSet.StringVarP(&options.Path, "binary-path", "bp", defaultPath, "custom location to download project binary"), 68 | ) 69 | 70 | flagSet.CreateGroup("install", "Install", 71 | flagSet.StringSliceVarP(&options.Install, "install", "i", nil, "install single or multiple project by name (comma separated)", goflags.NormalizedStringSliceOptions), 72 | flagSet.BoolVarP(&options.InstallAll, "install-all", "ia", false, "install all the projects"), 73 | flagSet.BoolVarP(&options.SetPath, "install-path", "ip", false, "append path to PATH environment variables"), 74 | flagSet.BoolVarP(&options.SetGoPath, "install-go-path", "igp", false, "append GOBIN/GOPATH to PATH environment variables"), 75 | ) 76 | 77 | flagSet.CreateGroup("update", "Update", 78 | flagSet.StringSliceVarP(&options.Update, "update", "u", nil, "update single or multiple project by name (comma separated)", goflags.NormalizedStringSliceOptions), 79 | flagSet.BoolVarP(&options.UpdateAll, "update-all", "ua", false, "update all the projects"), 80 | flagSet.CallbackVarP(GetUpdateCallback(), "self-update", "up", "update pdtm to latest version"), 81 | flagSet.BoolVarP(&options.DisableUpdateCheck, "disable-update-check", "duc", false, "disable automatic pdtm update check"), 82 | ) 83 | 84 | flagSet.CreateGroup("remove", "Remove", 85 | flagSet.StringSliceVarP(&options.Remove, "remove", "r", nil, "remove single or multiple project by name (comma separated)", goflags.NormalizedStringSliceOptions), 86 | flagSet.BoolVarP(&options.RemoveAll, "remove-all", "ra", false, "remove all the projects"), 87 | flagSet.BoolVarP(&options.UnSetPath, "remove-path", "rp", false, "remove path from PATH environment variables"), 88 | ) 89 | 90 | flagSet.CreateGroup("debug", "Debug", 91 | flagSet.BoolVarP(&options.ShowPath, "show-path", "sp", false, "show the current binary path then exit"), 92 | flagSet.BoolVar(&options.Version, "version", false, "show version of the project"), 93 | flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "show verbose output"), 94 | flagSet.BoolVarP(&options.NoColor, "no-color", "nc", false, "disable output content coloring (ANSI escape codes)"), 95 | flagSet.BoolVarP(&options.DisableChangeLog, "dc", "disable-changelog", false, "disable release changelog in output"), 96 | ) 97 | 98 | if err := flagSet.Parse(); err != nil { 99 | gologger.Fatal().Msgf("%s\n", err) 100 | } 101 | 102 | // configure aurora for logging 103 | au = aurora.New(aurora.WithColors(true)) 104 | 105 | options.configureOutput() 106 | 107 | showBanner() 108 | 109 | if options.Version { 110 | gologger.Info().Msgf("Current Version: %s\n", version) 111 | os.Exit(0) 112 | } 113 | 114 | if options.ShowPath { 115 | // prints default path if not modified 116 | gologger.Silent().Msg(options.Path) 117 | os.Exit(0) 118 | } 119 | 120 | if !options.DisableUpdateCheck { 121 | latestVersion, err := updateutils.GetToolVersionCallback("pdtm", version)() 122 | if err != nil { 123 | if options.Verbose { 124 | gologger.Error().Msgf("pdtm version check failed: %v", err.Error()) 125 | } 126 | } else { 127 | gologger.Info().Msgf("Current pdtm version %v %v", version, updateutils.GetVersionDescription(version, latestVersion)) 128 | } 129 | } 130 | 131 | if options.ConfigFile != defaultConfigLocation { 132 | _ = options.loadConfigFrom(options.ConfigFile) 133 | } 134 | 135 | return options 136 | } 137 | 138 | // configureOutput configures the output on the screen 139 | func (options *Options) configureOutput() { 140 | // If the user desires verbose output, show verbose output 141 | if options.Verbose { 142 | gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) 143 | } 144 | if options.NoColor { 145 | gologger.DefaultLogger.SetFormatter(formatter.NewCLI(true)) 146 | au = aurora.New(aurora.WithColors(false)) 147 | } 148 | if options.Silent { 149 | gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) 150 | } 151 | } 152 | 153 | func (options *Options) loadConfigFrom(location string) error { 154 | return fileutil.Unmarshal(fileutil.YAML, []byte(location), options) 155 | } 156 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | 11 | "github.com/projectdiscovery/gologger" 12 | "github.com/projectdiscovery/pdtm/pkg" 13 | "github.com/projectdiscovery/pdtm/pkg/path" 14 | "github.com/projectdiscovery/pdtm/pkg/types" 15 | "github.com/projectdiscovery/pdtm/pkg/utils" 16 | errorutil "github.com/projectdiscovery/utils/errors" 17 | stringsutil "github.com/projectdiscovery/utils/strings" 18 | ) 19 | 20 | var excludedToolList = []string{"nuclei-templates"} 21 | 22 | // Runner contains the internal logic of the program 23 | type Runner struct { 24 | options *Options 25 | } 26 | 27 | // NewRunner instance 28 | func NewRunner(options *Options) (*Runner, error) { 29 | return &Runner{ 30 | options: options, 31 | }, nil 32 | } 33 | 34 | // Run the instance 35 | func (r *Runner) Run() error { 36 | // add default path to $PATH 37 | if r.options.SetPath || r.options.Path == defaultPath { 38 | if err := path.SetENV(r.options.Path); err != nil { 39 | return errorutil.NewWithErr(err).Msgf(`Failed to set path: %s. Add it to $PATH and run again`, r.options.Path) 40 | } 41 | } 42 | 43 | if r.options.SetGoPath { 44 | goBinEnvVar, goPathEnvVar := getGoEnv("GOBIN"), getGoEnv("GOPATH") 45 | goEnvVar := goBinEnvVar 46 | if goEnvVar == "" { 47 | goEnvVar = goPathEnvVar 48 | } 49 | if goEnvVar != "" { 50 | if err := path.SetENV(goEnvVar); err != nil { 51 | return errorutil.NewWithErr(err).Msgf(`Failed to set path: %s. Add it to $PATH and run again`, goEnvVar) 52 | } 53 | } 54 | } 55 | 56 | if r.options.UnSetPath { 57 | if err := path.UnsetENV(r.options.Path); err != nil { 58 | return errorutil.NewWithErr(err).Msgf(`Failed to unset path: %s. Remove it from $PATH and run again`, r.options.Path) 59 | } 60 | } 61 | 62 | toolListApi, err := utils.FetchToolList() 63 | var toolList []types.Tool 64 | 65 | for _, tool := range toolListApi { 66 | if !stringsutil.ContainsAny(tool.Name, excludedToolList...) { 67 | toolList = append(toolList, tool) 68 | } 69 | } 70 | 71 | // if toolList is not nil save/update the cache 72 | // else fetch from cache file 73 | if toolList != nil { 74 | go func() { 75 | if err := UpdateCache(toolList); err != nil { 76 | gologger.Warning().Msgf("%s\n", err) 77 | } 78 | }() 79 | } else { 80 | toolList, err = FetchFromCache() 81 | if err != nil { 82 | return errors.New("pdtm api is down, please try again later") 83 | } 84 | if toolList != nil { 85 | gologger.Warning().Msg("pdtm api is down, using cached information while we fix the issue \n\n") 86 | } 87 | } 88 | if toolList == nil && err != nil { 89 | return err 90 | } 91 | 92 | switch { 93 | case r.options.InstallAll: 94 | for _, tool := range toolList { 95 | r.options.Install = append(r.options.Install, tool.Name) 96 | } 97 | case r.options.UpdateAll: 98 | for _, tool := range toolList { 99 | r.options.Update = append(r.options.Update, tool.Name) 100 | } 101 | case r.options.RemoveAll: 102 | for _, tool := range toolList { 103 | r.options.Remove = append(r.options.Remove, tool.Name) 104 | } 105 | } 106 | gologger.Verbose().Msgf("using path %s", r.options.Path) 107 | 108 | for _, toolName := range r.options.Install { 109 | if !path.IsSubPath(homeDir, r.options.Path) { 110 | gologger.Error().Msgf("skipping install outside home folder: %s", toolName) 111 | continue 112 | } 113 | if i, ok := utils.Contains(toolList, toolName); ok { 114 | tool := toolList[i] 115 | if tool.InstallType == types.Go && isGoInstalled() { 116 | if err := pkg.GoInstall(r.options.Path, tool); err != nil { 117 | gologger.Error().Msgf("%s: %s", tool.Name, err) 118 | } 119 | continue 120 | } 121 | 122 | if err := pkg.Install(r.options.Path, tool); err != nil { 123 | if errors.Is(err, types.ErrIsInstalled) { 124 | gologger.Info().Msgf("%s: %s", tool.Name, err) 125 | } else { 126 | gologger.Error().Msgf("error while installing %s: %s", tool.Name, err) 127 | gologger.Info().Msgf("trying to install %s using go install", tool.Name) 128 | if err := pkg.GoInstall(r.options.Path, tool); err != nil { 129 | gologger.Error().Msgf("%s: %s", tool.Name, err) 130 | } 131 | } 132 | } 133 | } else { 134 | gologger.Error().Msgf("error while installing %s: %s not found in the list", toolName, toolName) 135 | } 136 | } 137 | for _, tool := range r.options.Update { 138 | if !path.IsSubPath(homeDir, r.options.Path) { 139 | gologger.Error().Msgf("skipping update outside home folder: %s", tool) 140 | continue 141 | } 142 | if i, ok := utils.Contains(toolList, tool); ok { 143 | if err := pkg.Update(r.options.Path, toolList[i], r.options.DisableChangeLog); err != nil { 144 | if err == types.ErrIsUpToDate { 145 | gologger.Info().Msgf("%s: %s", tool, err) 146 | } else { 147 | gologger.Info().Msgf("%s\n", err) 148 | } 149 | } 150 | } 151 | } 152 | for _, tool := range r.options.Remove { 153 | if !path.IsSubPath(homeDir, r.options.Path) { 154 | gologger.Error().Msgf("skipping remove outside home folder: %s", tool) 155 | continue 156 | } 157 | if i, ok := utils.Contains(toolList, tool); ok { 158 | if err := pkg.Remove(r.options.Path, toolList[i]); err != nil { 159 | var notFoundError *exec.Error 160 | if errors.As(err, ¬FoundError) { 161 | gologger.Info().Msgf("%s: not found", tool) 162 | } else { 163 | gologger.Info().Msgf("%s\n", err) 164 | } 165 | } 166 | 167 | } 168 | } 169 | if len(r.options.Install) == 0 && len(r.options.Update) == 0 && len(r.options.Remove) == 0 { 170 | return r.ListToolsAndEnv(toolList) 171 | } 172 | return nil 173 | } 174 | 175 | func getGoEnv(key string) string { 176 | cmd := exec.Command("go", "env", key) 177 | output, err := cmd.Output() 178 | if err != nil { 179 | return "" 180 | } 181 | return strings.TrimSpace(string(output)) 182 | } 183 | 184 | func isGoInstalled() bool { 185 | cmd := exec.Command("go", "version") 186 | if err := cmd.Run(); err != nil { 187 | return false 188 | } 189 | return true 190 | } 191 | 192 | // UpdateCache creates/updates cache file 193 | func UpdateCache(toolList []types.Tool) error { 194 | b, err := json.Marshal(toolList) 195 | if err != nil { 196 | return err 197 | } 198 | return os.WriteFile(cacheFile, b, os.ModePerm) 199 | } 200 | 201 | // FetchFromCache loads tool list from cache file 202 | func FetchFromCache() ([]types.Tool, error) { 203 | b, err := os.ReadFile(cacheFile) 204 | if err != nil { 205 | return nil, err 206 | } 207 | var toolList []types.Tool 208 | if err := json.Unmarshal(b, &toolList); err != nil { 209 | return nil, err 210 | } 211 | return toolList, nil 212 | } 213 | 214 | // ListToolsAndEnv prints the list of tools 215 | func (r *Runner) ListToolsAndEnv(tools []types.Tool) error { 216 | gologger.Info().Msgf("%s\n", path.GetOsData()) 217 | gologger.Info().Msgf("Path to download project binary: %s\n", r.options.Path) 218 | var fmtMsg string 219 | if path.IsSet(r.options.Path) { 220 | fmtMsg = "Path %s configured in environment variable $PATH\n" 221 | } else { 222 | fmtMsg = "Path %s not configured in environment variable $PATH\n" 223 | } 224 | gologger.Info().Msgf(fmtMsg, r.options.Path) 225 | 226 | for i, tool := range tools { 227 | msg := utils.InstalledVersion(tool, r.options.Path, au) 228 | fmt.Printf("%d. %s %s\n", i+1, tool.Name, msg) 229 | } 230 | return nil 231 | } 232 | 233 | // Close the runner instance 234 | func (r *Runner) Close() {} 235 | -------------------------------------------------------------------------------- /pkg/crud_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | ospath "github.com/projectdiscovery/pdtm/pkg/path" 8 | "github.com/projectdiscovery/pdtm/pkg/types" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func GetToolStruct() types.Tool { 13 | tool := types.Tool{ 14 | Name: "dnsx", 15 | Repo: "dnsx", 16 | Version: "1.1.1", 17 | Assets: map[string]string{ 18 | "dnsx_1.1.1_checksums.txt": "79344865", 19 | "dnsx_1.1.1_linux_386.zip": "79344862", 20 | "dnsx_1.1.1_linux_amd64.zip": "79344859", 21 | "dnsx_1.1.1_linux_arm64.zip": "79344852", 22 | "dnsx_1.1.1_linux_armv6.zip": "79344864", 23 | "dnsx_1.1.1_macOS_amd64.zip": "79344851", 24 | "dnsx_1.1.1_macOS_arm64.zip": "79344856", 25 | "dnsx_1.1.1_windows_386.zip": "79344855", 26 | "dnsx_1.1.1_windows_amd64.zip": "79344857", 27 | }, 28 | } 29 | 30 | return tool 31 | } 32 | 33 | func TestInstall(t *testing.T) { 34 | tool := GetToolStruct() 35 | 36 | pathBin, err := os.MkdirTemp("", "test-dir") 37 | require.Nil(t, err) 38 | defer func() { 39 | require.NoError(t, os.RemoveAll(pathBin)) 40 | }() 41 | 42 | // install first time 43 | err = Install(pathBin, tool) 44 | require.Nil(t, err) 45 | 46 | // installing again should trigger an error 47 | err = Install(pathBin, tool) 48 | require.NotNil(t, err) 49 | } 50 | 51 | func TestRemove(t *testing.T) { 52 | tool := GetToolStruct() 53 | 54 | pathBin, err := os.MkdirTemp("", "test-dir") 55 | require.Nil(t, err) 56 | defer func() { 57 | require.NoError(t, os.RemoveAll(pathBin)) 58 | }() 59 | 60 | // install the tool 61 | err = Install(pathBin, tool) 62 | require.Nil(t, err) 63 | 64 | // remove it from path 65 | err = Remove(pathBin, tool) 66 | require.Nil(t, err) 67 | 68 | // removing non existing tool triggers an error 69 | err = Remove(pathBin, tool) 70 | require.NotNil(t, err) 71 | } 72 | 73 | func TestUpdateSameVersion(t *testing.T) { 74 | tool := GetToolStruct() 75 | 76 | pathBin, err := os.MkdirTemp("", "test-dir") 77 | require.Nil(t, err) 78 | defer func() { 79 | require.NoError(t, os.RemoveAll(pathBin)) 80 | }() 81 | 82 | // install the tool 83 | err = Install(pathBin, tool) 84 | require.Nil(t, err) 85 | 86 | // updating a tool to the same version should trigger an error 87 | err = Update(pathBin, tool, true) 88 | require.Equal(t, "already up to date", err.Error()) 89 | } 90 | 91 | func TestUpdateNonExistingTool(t *testing.T) { 92 | tool := GetToolStruct() 93 | 94 | pathBin, err := os.MkdirTemp("", "test-dir") 95 | require.Nil(t, err) 96 | defer func() { 97 | require.NoError(t, os.RemoveAll(pathBin)) 98 | }() 99 | 100 | // updating non existing tool should error 101 | err = Update(pathBin, tool, true) 102 | require.NotNil(t, err) 103 | } 104 | 105 | func TestUpdateToolWithoutAssets(t *testing.T) { 106 | tool := GetToolStruct() 107 | 108 | pathBin, err := os.MkdirTemp("", "test-dir") 109 | require.Nil(t, err) 110 | defer func() { 111 | require.NoError(t, os.RemoveAll(pathBin)) 112 | }() 113 | 114 | // install the tool 115 | err = Install(pathBin, tool) 116 | require.Nil(t, err) 117 | 118 | // remove assets 119 | tool.Assets = nil 120 | 121 | // updating a tool without assets should trigger an error 122 | err = Update(pathBin, tool, true) 123 | require.NotNil(t, err) 124 | // and leave the original binary in place 125 | _, exists := ospath.GetExecutablePath(pathBin, tool.Name) 126 | require.True(t, exists) 127 | } 128 | -------------------------------------------------------------------------------- /pkg/gh.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/google/go-github/github" 9 | "golang.org/x/oauth2" 10 | ) 11 | 12 | func GithubClient() *github.Client { 13 | var httpclient *http.Client 14 | if token := os.Getenv("GITHUB_TOKEN"); token != "" { 15 | httpclient = oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})) 16 | } 17 | githubClient := github.NewClient(httpclient) 18 | return githubClient 19 | } 20 | -------------------------------------------------------------------------------- /pkg/install.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "archive/tar" 5 | "archive/zip" 6 | "bytes" 7 | "compress/gzip" 8 | "context" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "os" 13 | "os/exec" 14 | "path/filepath" 15 | "runtime" 16 | "strconv" 17 | "strings" 18 | 19 | "github.com/google/go-github/github" 20 | "github.com/logrusorgru/aurora/v4" 21 | "github.com/projectdiscovery/gologger" 22 | ospath "github.com/projectdiscovery/pdtm/pkg/path" 23 | "github.com/projectdiscovery/pdtm/pkg/types" 24 | osutils "github.com/projectdiscovery/utils/os" 25 | "github.com/projectdiscovery/utils/syscallutil" 26 | ) 27 | 28 | var ( 29 | extIfFound = ".exe" 30 | au = aurora.New(aurora.WithColors(true)) 31 | ) 32 | 33 | // Install installs given tool at path 34 | func Install(path string, tool types.Tool) error { 35 | if _, exists := ospath.GetExecutablePath(path, tool.Name); exists { 36 | return types.ErrIsInstalled 37 | } 38 | gologger.Info().Msgf("installing %s...", tool.Name) 39 | printRequirementInfo(tool) 40 | version, err := install(tool, path) 41 | if err != nil { 42 | return err 43 | } 44 | gologger.Info().Msgf("installed %s %s (%s)", tool.Name, version, au.BrightGreen("latest").String()) 45 | return nil 46 | } 47 | 48 | // GoInstall installs given tool at path 49 | func GoInstall(path string, tool types.Tool) error { 50 | if _, exists := ospath.GetExecutablePath(path, tool.Name); exists { 51 | return types.ErrIsInstalled 52 | } 53 | gologger.Info().Msgf("installing %s with go install...", tool.Name) 54 | printRequirementInfo(tool) 55 | cmd := exec.Command("go", "install", "-v", fmt.Sprintf("github.com/projectdiscovery/%s/%s", tool.Name, tool.GoInstallPath)) 56 | cmd.Env = append(os.Environ(), "GOBIN="+path) 57 | if output, err := cmd.CombinedOutput(); err != nil { 58 | return fmt.Errorf("go install failed %s", string(output)) 59 | } 60 | gologger.Info().Msgf("installed %s %s (%s)", tool.Name, tool.Version, au.BrightGreen("latest").String()) 61 | return nil 62 | } 63 | 64 | func install(tool types.Tool, path string) (string, error) { 65 | builder := &strings.Builder{} 66 | builder.WriteString(tool.Name) 67 | builder.WriteString("_") 68 | builder.WriteString(strings.TrimPrefix(tool.Version, "v")) 69 | builder.WriteString("_") 70 | if strings.EqualFold(runtime.GOOS, "darwin") { 71 | builder.WriteString("macOS") 72 | } else { 73 | builder.WriteString(runtime.GOOS) 74 | } 75 | builder.WriteString("_") 76 | builder.WriteString(runtime.GOARCH) 77 | var id int 78 | var isZip, isTar bool 79 | loop: 80 | for asset, assetID := range tool.Assets { 81 | switch { 82 | case strings.Contains(asset, ".zip"): 83 | if strings.EqualFold(asset, builder.String()+".zip") { 84 | id, _ = strconv.Atoi(assetID) 85 | isZip = true 86 | break loop 87 | } 88 | case strings.Contains(asset, ".tar.gz"): 89 | if strings.EqualFold(asset, builder.String()+".tar.gz") { 90 | id, _ = strconv.Atoi(assetID) 91 | isTar = true 92 | break loop 93 | } 94 | } 95 | } 96 | builder.Reset() 97 | 98 | // handle if id is zero (no asset found) 99 | if id == 0 { 100 | return "", fmt.Errorf(types.ErrNoAssetFound, runtime.GOOS, runtime.GOARCH) 101 | } 102 | 103 | _, rdurl, err := GithubClient().Repositories.DownloadReleaseAsset(context.Background(), types.Organization, tool.Repo, int64(id)) 104 | if err != nil { 105 | if arlErr, ok := err.(*github.AbuseRateLimitError); ok { 106 | // Provide user with more info regarding the rate limit 107 | gologger.Error().Msgf("error for remaining request per hour: %s, RetryAfter: %s", err.Error(), arlErr.RetryAfter) 108 | } 109 | return "", err 110 | } 111 | 112 | resp, err := http.Get(rdurl) 113 | if err != nil { 114 | return "", err 115 | } 116 | 117 | defer func() { 118 | if err := resp.Body.Close(); err != nil { 119 | gologger.Warning().Msgf("Error closing response body: %s", err) 120 | } 121 | }() 122 | if resp.StatusCode != 200 { 123 | return "", err 124 | } 125 | 126 | switch { 127 | case isZip: 128 | err := downloadZip(resp.Body, tool.Name, path) 129 | if err != nil { 130 | return "", err 131 | } 132 | case isTar: 133 | err := downloadTar(resp.Body, tool.Name, path) 134 | if err != nil { 135 | return "", err 136 | } 137 | } 138 | return tool.Version, nil 139 | } 140 | 141 | func downloadTar(reader io.Reader, toolName, path string) error { 142 | gzipReader, err := gzip.NewReader(reader) 143 | if err != nil { 144 | return err 145 | } 146 | tarReader := tar.NewReader(gzipReader) 147 | // iterate through the files in the archive 148 | for { 149 | header, err := tarReader.Next() 150 | if err == io.EOF { 151 | break 152 | } 153 | if err != nil { 154 | return err 155 | } 156 | if !strings.EqualFold(strings.TrimSuffix(header.FileInfo().Name(), extIfFound), toolName) { 157 | continue 158 | } 159 | // if the file is not a directory, extract it 160 | if !header.FileInfo().IsDir() { 161 | filePath := filepath.Join(path, header.FileInfo().Name()) 162 | if !strings.HasPrefix(filePath, filepath.Clean(path)+string(os.PathSeparator)) { 163 | return err 164 | } 165 | 166 | if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { 167 | return err 168 | } 169 | 170 | dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, header.FileInfo().Mode()) 171 | if err != nil { 172 | return err 173 | } 174 | defer func() { 175 | if err := dstFile.Close(); err != nil { 176 | gologger.Warning().Msgf("Error closing file: %s", err) 177 | } 178 | }() 179 | // copy the file data from the archive 180 | _, err = io.Copy(dstFile, tarReader) 181 | if err != nil { 182 | return err 183 | } 184 | // set the file permissions 185 | err = os.Chmod(dstFile.Name(), 0755) 186 | if err != nil { 187 | return err 188 | } 189 | } 190 | } 191 | return nil 192 | } 193 | 194 | func downloadZip(reader io.Reader, toolName, path string) error { 195 | buff := bytes.NewBuffer([]byte{}) 196 | size, err := io.Copy(buff, reader) 197 | if err != nil { 198 | return err 199 | } 200 | zipReader, err := zip.NewReader(bytes.NewReader(buff.Bytes()), size) 201 | if err != nil { 202 | return err 203 | } 204 | for _, f := range zipReader.File { 205 | if !strings.EqualFold(strings.TrimSuffix(f.Name, extIfFound), toolName) { 206 | continue 207 | } 208 | filePath := filepath.Join(path, f.Name) 209 | if !strings.HasPrefix(filePath, filepath.Clean(path)+string(os.PathSeparator)) { 210 | return err 211 | } 212 | 213 | if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { 214 | return err 215 | } 216 | 217 | dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) 218 | if err != nil { 219 | return err 220 | } 221 | 222 | fileInArchive, err := f.Open() 223 | if err != nil { 224 | return err 225 | } 226 | 227 | if _, err := io.Copy(dstFile, fileInArchive); err != nil { 228 | return err 229 | } 230 | err = os.Chmod(dstFile.Name(), 0755) 231 | if err != nil { 232 | return err 233 | } 234 | 235 | if err := dstFile.Close(); err != nil { 236 | gologger.Warning().Msgf("Error closing file: %s", err) 237 | } 238 | if err := fileInArchive.Close(); err != nil { 239 | gologger.Warning().Msgf("Error closing file in archive: %s", err) 240 | } 241 | } 242 | return nil 243 | } 244 | 245 | func printRequirementInfo(tool types.Tool) { 246 | specs := getSpecs(tool) 247 | 248 | printTitle := true 249 | stringBuilder := &strings.Builder{} 250 | for _, spec := range specs { 251 | if requirementSatisfied(spec.Name) { 252 | continue 253 | } 254 | if printTitle { 255 | fmt.Fprintf(stringBuilder, "%s\n", au.Bold(tool.Name+" requirements:").String()) 256 | printTitle = false 257 | } 258 | instruction := getFormattedInstruction(spec) 259 | isRequired := getRequirementStatus(spec) 260 | fmt.Fprintf(stringBuilder, "%s %s\n", isRequired, instruction) 261 | } 262 | if stringBuilder.Len() > 0 { 263 | gologger.Info().Msgf("%s", stringBuilder.String()) 264 | } 265 | } 266 | 267 | func getRequirementStatus(spec types.ToolRequirementSpecification) string { 268 | if spec.Required { 269 | return au.Yellow("required").String() 270 | } 271 | return au.BrightGreen("optional").String() 272 | } 273 | 274 | func getFormattedInstruction(spec types.ToolRequirementSpecification) string { 275 | return strings.Replace(spec.Instruction, "$CMD", spec.Command, 1) 276 | } 277 | 278 | func getSpecs(tool types.Tool) []types.ToolRequirementSpecification { 279 | var specs []types.ToolRequirementSpecification 280 | for _, requirement := range tool.Requirements { 281 | if requirement.OS == runtime.GOOS { 282 | specs = append(specs, requirement.Specification...) 283 | } 284 | } 285 | return specs 286 | } 287 | 288 | func requirementSatisfied(requirementName string) bool { 289 | if strings.HasPrefix(requirementName, "lib") { 290 | libNames := appendLibExtensionForOS(requirementName) 291 | for _, libName := range libNames { 292 | _, sysErr := syscallutil.LoadLibrary(libName) 293 | if sysErr == nil { 294 | return true 295 | } 296 | } 297 | return false 298 | } 299 | _, execErr := exec.LookPath(requirementName) 300 | return execErr == nil 301 | } 302 | 303 | func appendLibExtensionForOS(lib string) []string { 304 | switch { 305 | case osutils.IsWindows(): 306 | return []string{fmt.Sprintf("%s.dll", lib), lib} 307 | case osutils.IsLinux(): 308 | return []string{fmt.Sprintf("%s.so", lib), lib} 309 | case osutils.IsOSX(): 310 | return []string{fmt.Sprintf("%s.dylib", lib), lib} 311 | default: 312 | return []string{lib} 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /pkg/path/path.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "path/filepath" 5 | "runtime" 6 | "strings" 7 | 8 | osutils "github.com/projectdiscovery/utils/os" 9 | 10 | fileutil "github.com/projectdiscovery/utils/file" 11 | ) 12 | 13 | type Config struct { 14 | shellName string 15 | rcFile string 16 | } 17 | 18 | func IsSet(path string) bool { 19 | ok, _ := isSet(path) 20 | return ok 21 | } 22 | 23 | var CommonExtensions = []string{"", ".exe", ".bat"} 24 | 25 | func SetENV(path string) error { 26 | _, err := add(path) 27 | return err 28 | } 29 | 30 | func UnsetENV(path string) error { 31 | _, err := remove(path) 32 | return err 33 | } 34 | 35 | func CheckOS() string { 36 | os := runtime.GOOS 37 | arc := runtime.GOARCH 38 | switch os { 39 | case "windows": 40 | return "windows_" + arc 41 | case "darwin": 42 | return "macOS_" + arc 43 | case "linux": 44 | return "linux_" + arc 45 | default: 46 | return "not_found" 47 | } 48 | } 49 | 50 | func GetOsData() string { 51 | os := runtime.GOOS 52 | arc := runtime.GOARCH 53 | goVersion := strings.ReplaceAll(runtime.Version(), "go", "") 54 | return "[OS: " + strings.ToUpper(os) + "] [ARCH: " + strings.ToUpper(arc) + "] [GO: " + goVersion + "]" 55 | } 56 | 57 | func GetExecutablePath(path, toolName string) (string, bool) { 58 | basePath := filepath.Join(path, toolName) 59 | for _, ext := range CommonExtensions { 60 | executablePath := basePath + ext 61 | if fileutil.FileExists(executablePath) { 62 | return executablePath, true 63 | } 64 | } 65 | 66 | if osutils.IsWindows() { 67 | return basePath + ".exe", false 68 | } 69 | 70 | return basePath, false 71 | } 72 | -------------------------------------------------------------------------------- /pkg/path/subpath.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | ) 8 | 9 | // IsSubPath determines if sub is a subdirectory of parent 10 | func IsSubPath(parent, sub string) bool { 11 | up := ".." + string(os.PathSeparator) 12 | rel, err := filepath.Rel(parent, sub) 13 | if err != nil { 14 | return false 15 | } 16 | return !strings.HasPrefix(rel, up) && rel != ".." 17 | } 18 | -------------------------------------------------------------------------------- /pkg/path/unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package path 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/projectdiscovery/gologger" 13 | errorutil "github.com/projectdiscovery/utils/errors" 14 | fileutil "github.com/projectdiscovery/utils/file" 15 | sliceutil "github.com/projectdiscovery/utils/slice" 16 | ) 17 | 18 | var confList = []*Config{ 19 | { 20 | shellName: "bash", 21 | rcFile: ".bashrc", 22 | }, 23 | { 24 | shellName: "zsh", 25 | rcFile: ".zshrc", 26 | }, 27 | { 28 | shellName: "fish", 29 | rcFile: ".config/fish/config.fish", 30 | }, 31 | } 32 | 33 | func (c *Config) GetRCFilePath() (string, error) { 34 | home, err := os.UserHomeDir() 35 | if nil != err { 36 | return "", err 37 | } 38 | rcFilePath := filepath.Join(home, c.rcFile) 39 | 40 | // For fish, ensure the directory exists 41 | if c.shellName == "fish" { 42 | configDir := filepath.Dir(rcFilePath) 43 | if !fileutil.FolderExists(configDir) { 44 | if err := os.MkdirAll(configDir, os.ModePerm); err != nil { 45 | return "", fmt.Errorf("failed to create fish config directory %v got %v", configDir, err) 46 | } 47 | } 48 | } 49 | 50 | if fileutil.FileExists(rcFilePath) { 51 | return rcFilePath, nil 52 | } 53 | // if file doesn't exist create empty file 54 | if err := os.WriteFile(rcFilePath, []byte("#\n"), 0644); err != nil { 55 | return "", fmt.Errorf("failed to create rcFile %v got %v", rcFilePath, err) 56 | } 57 | return rcFilePath, nil 58 | } 59 | 60 | func lookupConfFromShell() (*Config, error) { 61 | shell := filepath.Base(os.Getenv("SHELL")) 62 | for _, conf := range confList { 63 | if conf.shellName == shell { 64 | if _, err := conf.GetRCFilePath(); err != nil { 65 | return nil, err 66 | } 67 | return conf, nil 68 | } 69 | } 70 | // assume bash as default shell if variable is empty in unix distros 71 | if shell == "." && len(confList) > 1 { 72 | conf := confList[0] 73 | if _, err := conf.GetRCFilePath(); err != nil { 74 | return nil, err 75 | } 76 | return conf, nil 77 | } 78 | return nil, errors.New("shell not supported") 79 | } 80 | 81 | func isSet(path string) (bool, error) { 82 | pathVars := paths() 83 | return sliceutil.Contains(pathVars, path), nil 84 | } 85 | 86 | func add(path string) (bool, error) { 87 | pathVars := paths() 88 | if sliceutil.Contains(pathVars, path) { 89 | return false, nil 90 | } 91 | 92 | conf, err := lookupConfFromShell() 93 | if err != nil { 94 | return false, errorutil.NewWithErr(err).Msgf("add %s to $PATH env", path) 95 | } 96 | 97 | var script string 98 | if conf.shellName == "fish" { 99 | script = fmt.Sprintf("fish_add_path %s\n\n", path) 100 | } else { 101 | script = fmt.Sprintf("export PATH=$PATH:%s\n\n", path) 102 | } 103 | 104 | return exportToConfig(conf, path, script) 105 | } 106 | 107 | func remove(path string) (bool, error) { 108 | pathVars := paths() 109 | if !sliceutil.Contains(pathVars, path) { 110 | return false, nil 111 | } 112 | 113 | conf, err := lookupConfFromShell() 114 | if err != nil { 115 | return false, errorutil.NewWithErr(err).Msgf("remove %s from $PATH env", path) 116 | } 117 | 118 | var script string 119 | if conf.shellName == "fish" { 120 | script = fmt.Sprintf("set --erase fish_user_paths[contains $fish_user_paths %s]\n\n", path) 121 | } else { 122 | pathVars = sliceutil.PruneEqual(pathVars, path) 123 | script = fmt.Sprintf("export PATH=%s\n\n", strings.Join(pathVars, ":")) 124 | } 125 | 126 | return exportToConfig(conf, path, script) 127 | } 128 | 129 | func paths() []string { 130 | return strings.Split(os.Getenv("PATH"), ":") 131 | } 132 | 133 | func exportToConfig(config *Config, path, script string) (bool, error) { 134 | rcFilePath, err := config.GetRCFilePath() 135 | if err != nil { 136 | return false, err 137 | } 138 | b, err := os.ReadFile(rcFilePath) 139 | if err != nil { 140 | return false, err 141 | } 142 | 143 | lines := strings.Split(strings.TrimSpace(string(b)), "\n") 144 | for _, line := range lines { 145 | if strings.EqualFold(line, strings.TrimSpace(script)) { 146 | if config.shellName == "fish" { 147 | gologger.Info().Msgf("Run `source %s` to add %s to $PATH ", rcFilePath, path) 148 | } else { 149 | gologger.Info().Msgf("Run `source ~/%s` to add %s to $PATH ", config.rcFile, path) 150 | } 151 | return true, nil 152 | } 153 | } 154 | f, err := os.OpenFile(rcFilePath, os.O_APPEND|os.O_WRONLY, 0644) 155 | if err != nil { 156 | return false, err 157 | } 158 | 159 | script = fmt.Sprintf("\n\n# Generated for pdtm. Do not edit.\n%s", script) 160 | if _, err := f.Write([]byte(script)); err != nil { 161 | return false, err 162 | } 163 | if err := f.Close(); err != nil { 164 | return false, err 165 | } 166 | 167 | if config.shellName == "fish" { 168 | gologger.Info().Label("WRN").Msgf("Run `source %s` to add $PATH (%s)", rcFilePath, path) 169 | } else { 170 | gologger.Info().Label("WRN").Msgf("Run `source ~/%s` to add $PATH (%s)", config.rcFile, path) 171 | } 172 | return true, nil 173 | } 174 | -------------------------------------------------------------------------------- /pkg/path/windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | // path from https://github.com/therootcompany/pathman with some minor changes 4 | package path 5 | 6 | // Needs to 7 | // * use the registry editor directly to avoid possible PATH truncation 8 | // ( https://stackoverflow.com/questions/9546324/adding-directory-to-path-environment-variable-in-windows ) 9 | // ( https://superuser.com/questions/387619/overcoming-the-1024-character-limit-with-setx ) 10 | // * explicitly send WM_SETTINGCHANGE 11 | // ( https://github.com/golang/go/issues/18680#issuecomment-275582179 ) 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | "strings" 17 | 18 | "github.com/projectdiscovery/gologger" 19 | sliceutil "github.com/projectdiscovery/utils/slice" 20 | "golang.org/x/sys/windows/registry" 21 | ) 22 | 23 | func add(p string) (bool, error) { 24 | cur, err := getPathsFromRegistry() 25 | if nil != err { 26 | return false, err 27 | } 28 | 29 | index, err := IndexOf(cur, p) 30 | if err != nil { 31 | return false, err 32 | } 33 | // skip silently, successfully 34 | if index >= 0 { 35 | return false, nil 36 | } 37 | 38 | cur = append(cur, p) 39 | err = write(p, cur) 40 | if nil != err { 41 | return false, err 42 | } 43 | return true, nil 44 | } 45 | 46 | func remove(p string) (bool, error) { 47 | cur, err := getPathsFromRegistry() 48 | if nil != err { 49 | return false, err 50 | } 51 | 52 | index, err := IndexOf(cur, p) 53 | if err != nil { 54 | return false, err 55 | } 56 | // skip silently, successfully 57 | if index == -1 { 58 | return false, nil 59 | } 60 | 61 | cur = sliceutil.PruneEqual(cur, p) 62 | 63 | err = write(p, cur) 64 | if nil != err { 65 | return false, err 66 | } 67 | return true, nil 68 | } 69 | 70 | func write(path string, cur []string) error { 71 | k, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.SET_VALUE) 72 | if err != nil { 73 | return fmt.Errorf("can't open HKCU Environment for writes: %s", err) 74 | } 75 | defer k.Close() 76 | 77 | err = k.SetStringValue(`Path`, strings.Join(cur, string(os.PathListSeparator))) 78 | if nil != err { 79 | return fmt.Errorf("can't set HKCU Environment[Path]: %s", err) 80 | } 81 | err = k.Close() 82 | if nil != err { 83 | return err 84 | } 85 | if nil != sendmsg { 86 | err := sendmsg() 87 | if err != nil { 88 | gologger.Info().Label("WRN").Msgf("Please reboot to load newly added $PATH (%s)", path) 89 | } 90 | } else { 91 | gologger.Info().Label("WRN").Msgf("Please reboot to load newly added $PATH (%s)", path) 92 | } 93 | gologger.Info().Label("WRN").Msgf("Please reload terminal to load newly added $PATH (%s)", path) 94 | return nil 95 | } 96 | 97 | func paths() []string { 98 | configuredPaths, _ := getPathsFromRegistry() 99 | return configuredPaths 100 | } 101 | 102 | func getPathsFromRegistry() ([]string, error) { 103 | k, err := registry.OpenKey(registry.CURRENT_USER, `Environment`, registry.QUERY_VALUE) 104 | if err != nil { 105 | return nil, fmt.Errorf("can't open HKCU Environment for reads: %s", err) 106 | } 107 | defer k.Close() 108 | s, _, err := k.GetStringValue("Path") 109 | if err != nil { 110 | if strings.Contains(err.Error(), "cannot find the file") { 111 | return []string{}, nil 112 | } 113 | return nil, fmt.Errorf("can't query HKCU Environment[Path]: %s", err) 114 | } 115 | return strings.Split(s, string(os.PathListSeparator)), nil 116 | } 117 | 118 | func isSet(path string) (bool, error) { 119 | cur, err := getPathsFromRegistry() 120 | if nil != err { 121 | return false, err 122 | } 123 | 124 | index, err := IndexOf(cur, path) 125 | if err != nil { 126 | return false, err 127 | } 128 | return index >= 0, nil 129 | } 130 | -------------------------------------------------------------------------------- /pkg/path/winpath.go: -------------------------------------------------------------------------------- 1 | // Package path is useful for managing PATH as part of the Environment 2 | // in the Windows HKey Local User registry. It returns an error for most 3 | // operations on non-Windows systems. 4 | package path 5 | 6 | import ( 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | // sendmsg uses a syscall to broadcast the registry change so that 13 | // new shells will get the new PATH immediately, without a reboot 14 | var sendmsg func() error // nolint 15 | 16 | // NormalizePathEntry will return the given directory path relative 17 | // from its absolute path to the %USERPROFILE% (home) directory. 18 | func NormalizePathEntry(pathentry string) (string, string, error) { 19 | home, err := os.UserHomeDir() 20 | if nil != err { 21 | return "", "", err 22 | } 23 | 24 | sep := string(os.PathSeparator) 25 | absentry, _ := filepath.Abs(pathentry) 26 | home, _ = filepath.Abs(home) 27 | 28 | var homeentry string 29 | if strings.HasPrefix(strings.ToLower(absentry)+sep, strings.ToLower(home)+sep) { 30 | // %USERPROFILE% is allowed, but only for user PATH 31 | // https://superuser.com/a/442163/73857 32 | homeentry = `%USERPROFILE%` + absentry[len(home):] 33 | } 34 | 35 | if absentry == pathentry { 36 | absentry = "" 37 | } 38 | if homeentry == pathentry { 39 | homeentry = "" 40 | } 41 | return absentry, homeentry, nil 42 | } 43 | 44 | // IndexOf searches the given path list for first occurence 45 | // of the given path entry and returns the index, or -1 46 | func IndexOf(paths []string, p string) (int, error) { 47 | index := -1 48 | 49 | abspath, homepath, err := NormalizePathEntry(p) 50 | if err != nil { 51 | return index, nil 52 | } 53 | for i, path := range paths { 54 | if path == "" { 55 | continue 56 | } 57 | if strings.EqualFold(strings.ToLower(p), strings.ToLower(path)) { 58 | index = i 59 | break 60 | } 61 | if strings.EqualFold(strings.ToLower(abspath), strings.ToLower(path)) { 62 | index = i 63 | break 64 | } 65 | if strings.EqualFold(strings.ToLower(homepath), strings.ToLower(path)) { 66 | index = i 67 | break 68 | } 69 | } 70 | return index, nil 71 | } 72 | -------------------------------------------------------------------------------- /pkg/path/winpath_unsafe.go: -------------------------------------------------------------------------------- 1 | //go:build windows && !nounsafe 2 | 3 | package path 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | const ( 13 | HWND_BROADCAST = uintptr(0xffff) 14 | WM_SETTINGCHANGE = uintptr(0x001A) 15 | ) 16 | 17 | func init() { 18 | // WM_SETTING_CHANGE 19 | // https://gist.github.com/microo8/c1b9525efab9bb462adf9d123e855c52 20 | sendmsg = func() error { 21 | utf16PtrENV, err := syscall.UTF16PtrFromString("ENVIRONMENT") 22 | if err != nil { 23 | fmt.Fprintf(os.Stderr, "%s\n", err) 24 | return err 25 | } 26 | _, _, err = syscall. 27 | NewLazyDLL("user32.dll"). 28 | NewProc("SendMessageW"). 29 | Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(utf16PtrENV))) 30 | if nil != err { 31 | fmt.Fprintf(os.Stderr, "Adding path: %s\n", err) 32 | } 33 | return nil 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/remove.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | ospath "github.com/projectdiscovery/pdtm/pkg/path" 8 | "github.com/projectdiscovery/pdtm/pkg/types" 9 | 10 | "github.com/projectdiscovery/gologger" 11 | ) 12 | 13 | // Remove removes given tool 14 | func Remove(path string, tool types.Tool) error { 15 | executablePath, exists := ospath.GetExecutablePath(path, tool.Name) 16 | if exists { 17 | gologger.Info().Msgf("removing %s...", tool.Name) 18 | err := os.Remove(executablePath) 19 | if err != nil { 20 | return err 21 | } 22 | gologger.Info().Msgf("removed %s", tool.Name) 23 | return nil 24 | } 25 | return fmt.Errorf(types.ErrToolNotFound, tool.Name, executablePath) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "errors" 4 | 5 | const Organization = "projectdiscovery" 6 | 7 | var ( 8 | ErrIsInstalled = errors.New("already installed") 9 | ErrIsUpToDate = errors.New("already up to date") 10 | 11 | ErrNoAssetFound = "could not find release asset for your platform (%s/%s)" 12 | ErrToolNotFound = "%s: tool not found in path %s: skipping" 13 | ) 14 | 15 | type Tool struct { 16 | Name string `json:"name"` 17 | Repo string `json:"repo"` 18 | Version string `json:"version"` 19 | GoInstallPath string `json:"go_install_path" yaml:"go_install_path"` 20 | Requirements []ToolRequirement `json:"requirements"` 21 | Assets map[string]string `json:"assets"` 22 | InstallType InstallType `json:"install_type" yaml:"install_type"` 23 | } 24 | 25 | type InstallType string 26 | 27 | const ( 28 | Binary InstallType = "binary" 29 | Go InstallType = "go" 30 | ) 31 | 32 | type ToolRequirement struct { 33 | OS string `json:"os"` 34 | Specification []ToolRequirementSpecification `json:"specification"` 35 | } 36 | 37 | type ToolRequirementSpecification struct { 38 | Name string `json:"name"` 39 | Required bool `json:"required"` 40 | Command string `json:"command"` 41 | Instruction string `json:"instruction"` 42 | } 43 | 44 | type NucleiData struct { 45 | IgnoreHash string `json:"ignore-hash"` 46 | Tools []Tool `json:"tools"` 47 | } 48 | -------------------------------------------------------------------------------- /pkg/update.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/charmbracelet/glamour" 9 | ospath "github.com/projectdiscovery/pdtm/pkg/path" 10 | "github.com/projectdiscovery/pdtm/pkg/types" 11 | "github.com/projectdiscovery/pdtm/pkg/version" 12 | updateutils "github.com/projectdiscovery/utils/update" 13 | 14 | "github.com/projectdiscovery/gologger" 15 | ) 16 | 17 | // Update updates a given tool 18 | func Update(path string, tool types.Tool, disableChangeLog bool) error { 19 | if executablePath, exists := ospath.GetExecutablePath(path, tool.Name); exists { 20 | if isUpToDate(tool, path) { 21 | return types.ErrIsUpToDate 22 | } 23 | gologger.Info().Msgf("updating %s...", tool.Name) 24 | 25 | if len(tool.Assets) == 0 { 26 | return fmt.Errorf(types.ErrNoAssetFound, tool.Name, executablePath) 27 | } 28 | 29 | if err := os.Remove(executablePath); err != nil { 30 | return err 31 | } 32 | 33 | version, err := install(tool, path) 34 | if err != nil { 35 | return err 36 | } 37 | if !disableChangeLog { 38 | showReleaseNotes(tool.Repo) 39 | } 40 | gologger.Info().Msgf("updated %s to %s (%s)", tool.Name, version, au.BrightGreen("latest").String()) 41 | return nil 42 | } else { 43 | return fmt.Errorf(types.ErrToolNotFound, tool.Name, executablePath) 44 | } 45 | } 46 | 47 | func isUpToDate(tool types.Tool, path string) bool { 48 | v, err := version.ExtractInstalledVersion(tool, path) 49 | return err == nil && strings.EqualFold(tool.Version, v) 50 | } 51 | 52 | func showReleaseNotes(toolname string) { 53 | gh, err := updateutils.NewghReleaseDownloader(toolname) 54 | if err != nil { 55 | gologger.Fatal().Label("updater").Msgf("failed to download latest release got %v", err) 56 | } 57 | gh.SetToolName(toolname) 58 | output := gh.Latest.GetBody() 59 | // adjust colors for both dark / light terminal themes 60 | r, err := glamour.NewTermRenderer(glamour.WithAutoStyle()) 61 | if err != nil { 62 | gologger.Error().Msgf("markdown rendering not supported: %v", err) 63 | } 64 | if rendered, err := r.Render(output); err == nil { 65 | output = rendered 66 | } else { 67 | gologger.Error().Msg(err.Error()) 68 | } 69 | gologger.Print().Msgf("%v\n", output) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/utils/helpers.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/projectdiscovery/gologger" 9 | "github.com/projectdiscovery/pdtm/pkg" 10 | "github.com/projectdiscovery/pdtm/pkg/types" 11 | ) 12 | 13 | // GetVersionCheckCallback returns a callback function and when it is executed returns a version string of that tool 14 | func GetVersionCheckCallback(toolName, basePath string) func() string { 15 | return func() string { 16 | tool, err := fetchTool(toolName) 17 | if err != nil { 18 | return err.Error() 19 | } 20 | return fmt.Sprintf("%s %s", toolName, InstalledVersion(tool, basePath, au)) 21 | } 22 | } 23 | 24 | // GetUpdaterCallback returns a callback function when executed updates that tool 25 | func GetUpdaterCallback(toolName string) func() { 26 | return func() { 27 | home, _ := os.UserHomeDir() 28 | dp := filepath.Join(home, ".pdtm/go/bin") 29 | tool, err := fetchTool(toolName) 30 | if err != nil { 31 | gologger.Error().Msgf("failed to fetch details of %v skipping update: %v", toolName, err) 32 | return 33 | } 34 | err = pkg.Update(dp, tool, false) 35 | if err == types.ErrIsUpToDate { 36 | gologger.Info().Msgf("%s: %s", toolName, err) 37 | } else { 38 | gologger.Error().Msgf("error while updating %s: %s", toolName, err) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "strings" 10 | 11 | "github.com/logrusorgru/aurora/v4" 12 | "github.com/projectdiscovery/pdtm/pkg/path" 13 | "github.com/projectdiscovery/pdtm/pkg/types" 14 | "github.com/projectdiscovery/pdtm/pkg/version" 15 | updateutils "github.com/projectdiscovery/utils/update" 16 | ) 17 | 18 | var host = getEnv("PDTM_SERVER", "https://api.pdtm.sh") 19 | 20 | func getEnv(key, defaultValue string) string { 21 | value := os.Getenv(key) 22 | if value == "" { 23 | return defaultValue 24 | } 25 | return value 26 | } 27 | 28 | // configure aurora for logging 29 | var au = aurora.New(aurora.WithColors(true)) 30 | 31 | func FetchToolList() ([]types.Tool, error) { 32 | tools := make([]types.Tool, 0) 33 | 34 | // Create the request URL with query parameters 35 | reqURL := fmt.Sprintf("%s/api/v1/tools/?%s", host, updateutils.GetpdtmParams("")) 36 | 37 | resp, err := http.Get(reqURL) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer func() { 42 | if err := resp.Body.Close(); err != nil { 43 | // Just log warning as we're already returning from function 44 | fmt.Printf("Error closing response body: %s\n", err) 45 | } 46 | }() 47 | 48 | if resp.StatusCode == http.StatusOK { 49 | body, err := io.ReadAll(resp.Body) 50 | if err != nil { 51 | return nil, err 52 | } 53 | err = json.Unmarshal(body, &tools) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return tools, nil 58 | } 59 | return nil, nil 60 | } 61 | 62 | func fetchTool(toolName string) (types.Tool, error) { 63 | var tool types.Tool 64 | // Create the request URL to get tool 65 | reqURL := fmt.Sprintf("%s/api/v1/tools/%s?%s", host, toolName, updateutils.GetpdtmParams("")) 66 | resp, err := http.Get(reqURL) 67 | if err != nil { 68 | return tool, err 69 | } 70 | defer func() { 71 | if err := resp.Body.Close(); err != nil { 72 | // Just log warning as we're already returning from function 73 | fmt.Printf("Error closing response body: %s\n", err) 74 | } 75 | }() 76 | 77 | if resp.StatusCode == http.StatusOK { 78 | body, err := io.ReadAll(resp.Body) 79 | if err != nil { 80 | return tool, err 81 | } 82 | // edge case for nuclei coz, the nuclei api send a list of tools including nuclei-templates 83 | if toolName == "nuclei" { 84 | var data types.NucleiData 85 | err = json.Unmarshal(body, &data) 86 | if err != nil { 87 | return tool, err 88 | } 89 | for _, v := range data.Tools { 90 | if v.Name == toolName { 91 | tool = v 92 | break 93 | } 94 | } 95 | return tool, nil 96 | } 97 | 98 | err = json.Unmarshal(body, &tool) 99 | if err != nil { 100 | return tool, err 101 | } 102 | return tool, nil 103 | } 104 | return tool, nil 105 | } 106 | 107 | func Contains(s []types.Tool, toolName string) (int, bool) { 108 | for i, a := range s { 109 | if strings.EqualFold(a.Name, toolName) { 110 | return i, true 111 | } 112 | } 113 | return -1, false 114 | } 115 | 116 | func InstalledVersion(tool types.Tool, basePath string, au *aurora.Aurora) string { 117 | var msg string 118 | 119 | installedVersion, err := version.ExtractInstalledVersion(tool, basePath) 120 | if err != nil { 121 | osAvailable := isOsAvailable(tool) 122 | if !osAvailable { 123 | msg = fmt.Sprintf("(%s)", au.Gray(10, "not supported").String()) 124 | } else { 125 | msg = fmt.Sprintf("(%s)", au.BrightYellow("not installed").String()) 126 | } 127 | } 128 | 129 | if installedVersion != "" { 130 | if strings.Contains(tool.Version, installedVersion) { 131 | msg = fmt.Sprintf("(%s) (%s)", au.BrightGreen("latest").String(), au.BrightGreen(tool.Version).String()) 132 | } else { 133 | msg = fmt.Sprintf("(%s) (%s) ➡ (%s)", 134 | au.Red("outdated").String(), 135 | au.Red(installedVersion).String(), 136 | au.BrightGreen(tool.Version).String()) 137 | } 138 | } 139 | 140 | return msg 141 | } 142 | 143 | func isOsAvailable(tool types.Tool) bool { 144 | osData := path.CheckOS() 145 | for asset := range tool.Assets { 146 | expectedAssetPrefix := tool.Name + "_" + tool.Version + "_" + osData 147 | if strings.Contains(asset, expectedAssetPrefix) { 148 | return true 149 | } 150 | } 151 | return false 152 | } 153 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "os/exec" 7 | "path/filepath" 8 | "regexp" 9 | "strings" 10 | 11 | "github.com/projectdiscovery/pdtm/pkg/types" 12 | ) 13 | 14 | var RegexVersionNumber = regexp.MustCompile(`(?m)[v\s](\d+\.\d+\.\d+)`) 15 | 16 | func ExtractInstalledVersion(tool types.Tool, basePath string) (string, error) { 17 | toolPath := filepath.Join(basePath, tool.Name) 18 | cmd := exec.Command(toolPath, "--version") 19 | 20 | var outb bytes.Buffer 21 | cmd.Stdout = &outb 22 | cmd.Stderr = &outb 23 | err := cmd.Run() 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | if installedVersion := RegexVersionNumber.FindString(strings.ToLower(outb.String())); installedVersion != "" { 29 | installedVersionString := strings.TrimPrefix(strings.TrimSpace(installedVersion), "v") 30 | return installedVersionString, nil 31 | } 32 | 33 | return "", errors.New("unable to extract installed version") 34 | } 35 | --------------------------------------------------------------------------------