├── .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 |

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 |
--------------------------------------------------------------------------------