gitghost-scanner 1.2.0__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. gitghost_scanner-1.4.0/PKG-INFO +181 -0
  2. gitghost_scanner-1.4.0/README.md +161 -0
  3. gitghost_scanner-1.4.0/gitghost/__init__.py +1 -0
  4. gitghost_scanner-1.4.0/gitghost/banner.py +31 -0
  5. gitghost_scanner-1.4.0/gitghost/cli.py +434 -0
  6. gitghost_scanner-1.4.0/gitghost/console.py +102 -0
  7. gitghost_scanner-1.4.0/gitghost/ghost.py +269 -0
  8. gitghost_scanner-1.4.0/gitghost/github.py +259 -0
  9. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/gitghost/report.py +50 -11
  10. gitghost_scanner-1.4.0/gitghost/rules.py +308 -0
  11. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/gitghost/scanner.py +11 -2
  12. gitghost_scanner-1.4.0/gitghost/score.py +153 -0
  13. gitghost_scanner-1.4.0/gitghost/ui.py +47 -0
  14. gitghost_scanner-1.4.0/gitghost_scanner.egg-info/PKG-INFO +181 -0
  15. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/gitghost_scanner.egg-info/SOURCES.txt +2 -0
  16. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/pyproject.toml +6 -1
  17. gitghost_scanner-1.4.0/tests/test_detection.py +282 -0
  18. gitghost_scanner-1.2.0/PKG-INFO +0 -99
  19. gitghost_scanner-1.2.0/README.md +0 -79
  20. gitghost_scanner-1.2.0/gitghost/__init__.py +0 -1
  21. gitghost_scanner-1.2.0/gitghost/banner.py +0 -38
  22. gitghost_scanner-1.2.0/gitghost/cli.py +0 -123
  23. gitghost_scanner-1.2.0/gitghost/ghost.py +0 -81
  24. gitghost_scanner-1.2.0/gitghost/github.py +0 -80
  25. gitghost_scanner-1.2.0/gitghost/rules.py +0 -178
  26. gitghost_scanner-1.2.0/gitghost/score.py +0 -86
  27. gitghost_scanner-1.2.0/gitghost_scanner.egg-info/PKG-INFO +0 -99
  28. gitghost_scanner-1.2.0/tests/test_detection.py +0 -116
  29. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/LICENSE +0 -0
  30. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/gitghost/__main__.py +0 -0
  31. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/gitghost/metadata.py +0 -0
  32. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/gitghost_scanner.egg-info/dependency_links.txt +0 -0
  33. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/gitghost_scanner.egg-info/entry_points.txt +0 -0
  34. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/gitghost_scanner.egg-info/top_level.txt +0 -0
  35. {gitghost_scanner-1.2.0 → gitghost_scanner-1.4.0}/setup.cfg +0 -0
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.2
2
+ Name: gitghost-scanner
3
+ Version: 1.4.0
4
+ Summary: Find secrets in a GitHub account's public repos — including the ones still hiding in git history after being 'deleted'.
5
+ Author: cy3erm
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/cy3erm/gitghost
8
+ Project-URL: Repository, https://github.com/cy3erm/gitghost
9
+ Project-URL: Issues, https://github.com/cy3erm/gitghost/issues
10
+ Keywords: security,secrets-detection,secret-scanning,credential-scanner,leaked-credentials,git,git-history,osint,devsecops,bug-bounty,pentest,red-team,offensive-security,api-keys,security-tools,cli,github
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Security
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+
21
+ # gitghost
22
+
23
+ [![CI](https://github.com/cy3erm/gitghost/actions/workflows/ci.yml/badge.svg)](https://github.com/cy3erm/gitghost/actions/workflows/ci.yml)
24
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue)
25
+ ![License](https://img.shields.io/badge/license-MIT-green)
26
+ ![Dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen)
27
+
28
+ ![gitghost exposure dossier](preview.png)
29
+
30
+ **The secrets you deleted are still in git history.** gitghost finds them.
31
+
32
+ Finds live credentials in a GitHub account's public repos — *and* the ones that were committed, "deleted," and are still sitting one `git log` away for anyone who clones.
33
+
34
+ I built this after noticing how often the real leak isn't in someone's current code — it's in a commit from eight months ago that they thought they'd cleaned up. You paste an API key, catch it, delete the line, and move on. The latest version looks fine. But the old commit still has the key, and anyone who clones the repo gets it. Most scanners only look at your current files and miss this entirely. gitghost digs through history for exactly those, then rolls everything into a single **exposure score** so you can tell how bad things are at a glance.
35
+
36
+ ## Highlights
37
+
38
+ - **Ghost recovery** — walks the full git history of every repo (including unreachable objects) to recover secrets that were removed from HEAD but never purged
39
+ - **Force-push recovery** — even if you rewrote history, your recent push events still list the orphaned commit SHAs; gitghost fetches those commits by SHA and scans what the force-push tried to bury
40
+ - **Gist revision history** — secrets you edited out of a gist stay readable at old revision URLs; gitghost scans every revision and flags the ones that were "deleted"
41
+ - **40+ credential formats** — AWS, GCP, GitHub, OpenAI, Anthropic, Stripe, Slack, Google, Azure, Telegram, Discord, Notion, Shopify, Vault, Postman, Square, and many more (full list below)
42
+ - **Generic high-entropy detection** — catches secrets that don't match any known format, with false-positive filtering tuned against real codebases
43
+ - **Exposure score (0–100)** — one number that weights severity, age of the leak, cross-repo reuse, and whether a "deleted" secret is *still in use* somewhere
44
+ - **Metadata leaks** — the author email in your commits, plus inferred timezone and working hours; profile enrichment pulls public name, company, location, blog, and account age
45
+ - **HTML dossier output** — every finding links to the exact file+line or commit; findings are shown as irreversible fingerprints so reports are safe to share
46
+ - **Interactive console** or one-shot CLI — your choice
47
+ - **Zero dependencies** — pure Python 3.10+ standard library
48
+
49
+ ## Install
50
+
51
+ You need Python 3.10+ and `git`. That's it.
52
+
53
+ ```bash
54
+ pipx install gitghost-scanner
55
+ gitghost
56
+ ```
57
+
58
+ Or run straight from a clone:
59
+
60
+ ```bash
61
+ git clone https://github.com/cy3erm/gitghost
62
+ cd gitghost
63
+ python3 -m gitghost
64
+ ```
65
+
66
+ > Set `GITHUB_TOKEN` (any token works — it doesn't need scopes) to raise GitHub's 60-requests/hour anonymous limit before scanning more than an account or two.
67
+
68
+ ## Usage
69
+
70
+ ### Interactive mode
71
+
72
+ Run gitghost with no arguments and you get a guided launcher: the screen clears, the banner prints, you pick what to scan, type it in, and off it goes.
73
+
74
+ ```
75
+ $ gitghost
76
+ ____ _(_) /_____ _/ /_ ____ _____/ /_
77
+ / __ `/ / __/ __ `/ __ \/ __ \/ ___/ __/
78
+ / /_/ / / /_/ /_/ / / / /_/ (__ ) /_
79
+ \__, /_/\__/\__, /_/ /_/\____/____/\__/
80
+ /____/ /____/
81
+ the secrets you deleted are still in git history v1.3.0
82
+ by cy3erm · github.com/cy3erm/gitghost
83
+
84
+ 1) Scan a GitHub user public repos + gists
85
+ 2) Scan an organization public repos of an org
86
+ 3) Scan a single repo by owner/name or URL
87
+ 4) Scan a local checkout a repo already on disk
88
+
89
+ select a mode [1-4], q to quit > 1
90
+ GitHub username > octocat
91
+
92
+ [*] enumerating public repos for @octocat ...
93
+ ...
94
+
95
+ [+] exposure score: 29/100 [LOW, grade B]
96
+ · real author email exposed (octocat@nowhere.com)
97
+ [+] dossier written to gitghost-dossier.html
98
+
99
+ scan another? [Y/n] >
100
+ ```
101
+
102
+ ### One-shot CLI
103
+
104
+ Interactive mode covers the common cases; every option also has a flag for scripting:
105
+
106
+ ```bash
107
+ gitghost <username> # audit a user's public repos (+ gists)
108
+ gitghost <username> --limit 50 # cap repos per identity
109
+ gitghost <username> --network # also crawl followers/following
110
+ gitghost --org acme # scan an org's public repos
111
+ gitghost --org acme --members # ...plus every public member's repos
112
+ gitghost --repo owner/name # one repo by URL or owner/name
113
+ gitghost --local ./my-checkout # scan a checkout already on disk
114
+ gitghost --out report.html # where to write the dossier
115
+ gitghost --no-gists # skip gist scanning
116
+ gitghost --jobs 8 # parallel clones/scans (default 4)
117
+ gitghost --debug # full traceback if something breaks
118
+ ```
119
+
120
+ Try the demo first if you'd rather not point this at a real person — it builds a tiny repo with fake credentials, including one that gets "deleted" a few commits in:
121
+
122
+ ```bash
123
+ bash demo/make_demo_repo.sh /tmp/demo
124
+ gitghost --local /tmp/demo --name demo
125
+ ```
126
+
127
+ Watch it recover the deleted key from history.
128
+
129
+ ## What it detects
130
+
131
+ **Cloud & infrastructure**
132
+ AWS access keys and secret keys · GCP service-account keys · Azure storage keys · database connection strings (Postgres, MySQL, MongoDB, Redis) · private key blocks (RSA/EC/OpenSSH/DSA/PGP) · HashiCorp Vault tokens · internal hostnames and private IPs
133
+
134
+ **Developer platforms**
135
+ GitHub PATs (classic + fine-grained) and OAuth tokens · GitLab PATs · npm publish tokens · PyPI upload tokens · Postman API keys · Sentry auth tokens · Netlify tokens · Linear API keys · Figma tokens · PlanetScale service tokens
136
+
137
+ **AI services**
138
+ OpenAI (classic + project keys) · Anthropic · Groq · Perplexity · xAI · Hugging Face · Replicate
139
+
140
+ **Communication & SaaS**
141
+ Slack (tokens, webhooks, app tokens) · Discord (webhooks, bot tokens) · Telegram bot tokens · Twilio · SendGrid · Mailgun · Notion integration secrets · Shopify · Square · Razorpay · DigitalOcean · Doppler · Firebase Cloud Messaging server keys · Google API keys and OAuth refresh tokens
142
+
143
+ **Everything else** — a generic high-entropy scanner looks for values assigned to suspiciously-named variables (`api_key = "..."`) even when no provider pattern matches, filtered through placeholder/entropy checks to keep false positives near zero.
144
+
145
+ Missing a provider you care about? Rules are four lines each in [`gitghost/rules.py`](gitghost/rules.py) — name, regex, severity, remediation advice. PRs welcome.
146
+
147
+ ## The score
148
+
149
+ One number, 0–100, higher is worse:
150
+
151
+ - **Severity-weighted findings** — a live AWS secret key counts far more than a webhook URL
152
+ - **Ghost weighting** — recovered-from-history secrets scale with age: something exposed for 2 years hurts more than something from last week
153
+ - **Reuse correlation** — the same secret across multiple repos, and the nastiest case: a "deleted" secret whose fingerprint still matches something *live*
154
+ - **Metadata exposure** — real email in commits, inferable timezone
155
+
156
+ A single live cloud key is enough to put an account in the red by itself. Run it, clean up, run again — the number tells you if it worked.
157
+
158
+ ## Where it draws the line (legal & ethics)
159
+
160
+ It only ever reads public repositories — things the account already chose to publish — and it's detection-only. It'll tell you a string *looks like* a credential and leave it at that. It won't try the key against the actual service to see if it still works, because quietly logging into someone else's account isn't the tool's job, and honestly it's not yours either. Use it on your own identity, or one you're authorized to assess.
161
+
162
+ Findings are shown as non-reversible fingerprints (a safe prefix, a length, a partial hash) — never recoverable key material — so reports are safe to share. gitghost does not retain raw secrets past the match.
163
+
164
+ Point it at yourself first. Most people turn up at least one thing they'd completely forgotten about — I did.
165
+
166
+ ## Leaked something? The fix is rotate, not delete.
167
+
168
+ Deleting the file or the repo doesn't un-leak anything — anyone who cloned already has the key. Rotate the credential first, then purge history with `git filter-repo`. The end of every dossier walks through both steps.
169
+
170
+ ## Development
171
+
172
+ ```bash
173
+ python -m pytest tests # detection engine tests
174
+ pip install pyflakes && python -m pyflakes gitghost # lint
175
+ ```
176
+
177
+ CI runs the suite on Python 3.10, 3.11, and 3.12.
178
+
179
+ ## License
180
+
181
+ MIT
@@ -0,0 +1,161 @@
1
+ # gitghost
2
+
3
+ [![CI](https://github.com/cy3erm/gitghost/actions/workflows/ci.yml/badge.svg)](https://github.com/cy3erm/gitghost/actions/workflows/ci.yml)
4
+ ![Python](https://img.shields.io/badge/python-3.10%2B-blue)
5
+ ![License](https://img.shields.io/badge/license-MIT-green)
6
+ ![Dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen)
7
+
8
+ ![gitghost exposure dossier](preview.png)
9
+
10
+ **The secrets you deleted are still in git history.** gitghost finds them.
11
+
12
+ Finds live credentials in a GitHub account's public repos — *and* the ones that were committed, "deleted," and are still sitting one `git log` away for anyone who clones.
13
+
14
+ I built this after noticing how often the real leak isn't in someone's current code — it's in a commit from eight months ago that they thought they'd cleaned up. You paste an API key, catch it, delete the line, and move on. The latest version looks fine. But the old commit still has the key, and anyone who clones the repo gets it. Most scanners only look at your current files and miss this entirely. gitghost digs through history for exactly those, then rolls everything into a single **exposure score** so you can tell how bad things are at a glance.
15
+
16
+ ## Highlights
17
+
18
+ - **Ghost recovery** — walks the full git history of every repo (including unreachable objects) to recover secrets that were removed from HEAD but never purged
19
+ - **Force-push recovery** — even if you rewrote history, your recent push events still list the orphaned commit SHAs; gitghost fetches those commits by SHA and scans what the force-push tried to bury
20
+ - **Gist revision history** — secrets you edited out of a gist stay readable at old revision URLs; gitghost scans every revision and flags the ones that were "deleted"
21
+ - **40+ credential formats** — AWS, GCP, GitHub, OpenAI, Anthropic, Stripe, Slack, Google, Azure, Telegram, Discord, Notion, Shopify, Vault, Postman, Square, and many more (full list below)
22
+ - **Generic high-entropy detection** — catches secrets that don't match any known format, with false-positive filtering tuned against real codebases
23
+ - **Exposure score (0–100)** — one number that weights severity, age of the leak, cross-repo reuse, and whether a "deleted" secret is *still in use* somewhere
24
+ - **Metadata leaks** — the author email in your commits, plus inferred timezone and working hours; profile enrichment pulls public name, company, location, blog, and account age
25
+ - **HTML dossier output** — every finding links to the exact file+line or commit; findings are shown as irreversible fingerprints so reports are safe to share
26
+ - **Interactive console** or one-shot CLI — your choice
27
+ - **Zero dependencies** — pure Python 3.10+ standard library
28
+
29
+ ## Install
30
+
31
+ You need Python 3.10+ and `git`. That's it.
32
+
33
+ ```bash
34
+ pipx install gitghost-scanner
35
+ gitghost
36
+ ```
37
+
38
+ Or run straight from a clone:
39
+
40
+ ```bash
41
+ git clone https://github.com/cy3erm/gitghost
42
+ cd gitghost
43
+ python3 -m gitghost
44
+ ```
45
+
46
+ > Set `GITHUB_TOKEN` (any token works — it doesn't need scopes) to raise GitHub's 60-requests/hour anonymous limit before scanning more than an account or two.
47
+
48
+ ## Usage
49
+
50
+ ### Interactive mode
51
+
52
+ Run gitghost with no arguments and you get a guided launcher: the screen clears, the banner prints, you pick what to scan, type it in, and off it goes.
53
+
54
+ ```
55
+ $ gitghost
56
+ ____ _(_) /_____ _/ /_ ____ _____/ /_
57
+ / __ `/ / __/ __ `/ __ \/ __ \/ ___/ __/
58
+ / /_/ / / /_/ /_/ / / / /_/ (__ ) /_
59
+ \__, /_/\__/\__, /_/ /_/\____/____/\__/
60
+ /____/ /____/
61
+ the secrets you deleted are still in git history v1.3.0
62
+ by cy3erm · github.com/cy3erm/gitghost
63
+
64
+ 1) Scan a GitHub user public repos + gists
65
+ 2) Scan an organization public repos of an org
66
+ 3) Scan a single repo by owner/name or URL
67
+ 4) Scan a local checkout a repo already on disk
68
+
69
+ select a mode [1-4], q to quit > 1
70
+ GitHub username > octocat
71
+
72
+ [*] enumerating public repos for @octocat ...
73
+ ...
74
+
75
+ [+] exposure score: 29/100 [LOW, grade B]
76
+ · real author email exposed (octocat@nowhere.com)
77
+ [+] dossier written to gitghost-dossier.html
78
+
79
+ scan another? [Y/n] >
80
+ ```
81
+
82
+ ### One-shot CLI
83
+
84
+ Interactive mode covers the common cases; every option also has a flag for scripting:
85
+
86
+ ```bash
87
+ gitghost <username> # audit a user's public repos (+ gists)
88
+ gitghost <username> --limit 50 # cap repos per identity
89
+ gitghost <username> --network # also crawl followers/following
90
+ gitghost --org acme # scan an org's public repos
91
+ gitghost --org acme --members # ...plus every public member's repos
92
+ gitghost --repo owner/name # one repo by URL or owner/name
93
+ gitghost --local ./my-checkout # scan a checkout already on disk
94
+ gitghost --out report.html # where to write the dossier
95
+ gitghost --no-gists # skip gist scanning
96
+ gitghost --jobs 8 # parallel clones/scans (default 4)
97
+ gitghost --debug # full traceback if something breaks
98
+ ```
99
+
100
+ Try the demo first if you'd rather not point this at a real person — it builds a tiny repo with fake credentials, including one that gets "deleted" a few commits in:
101
+
102
+ ```bash
103
+ bash demo/make_demo_repo.sh /tmp/demo
104
+ gitghost --local /tmp/demo --name demo
105
+ ```
106
+
107
+ Watch it recover the deleted key from history.
108
+
109
+ ## What it detects
110
+
111
+ **Cloud & infrastructure**
112
+ AWS access keys and secret keys · GCP service-account keys · Azure storage keys · database connection strings (Postgres, MySQL, MongoDB, Redis) · private key blocks (RSA/EC/OpenSSH/DSA/PGP) · HashiCorp Vault tokens · internal hostnames and private IPs
113
+
114
+ **Developer platforms**
115
+ GitHub PATs (classic + fine-grained) and OAuth tokens · GitLab PATs · npm publish tokens · PyPI upload tokens · Postman API keys · Sentry auth tokens · Netlify tokens · Linear API keys · Figma tokens · PlanetScale service tokens
116
+
117
+ **AI services**
118
+ OpenAI (classic + project keys) · Anthropic · Groq · Perplexity · xAI · Hugging Face · Replicate
119
+
120
+ **Communication & SaaS**
121
+ Slack (tokens, webhooks, app tokens) · Discord (webhooks, bot tokens) · Telegram bot tokens · Twilio · SendGrid · Mailgun · Notion integration secrets · Shopify · Square · Razorpay · DigitalOcean · Doppler · Firebase Cloud Messaging server keys · Google API keys and OAuth refresh tokens
122
+
123
+ **Everything else** — a generic high-entropy scanner looks for values assigned to suspiciously-named variables (`api_key = "..."`) even when no provider pattern matches, filtered through placeholder/entropy checks to keep false positives near zero.
124
+
125
+ Missing a provider you care about? Rules are four lines each in [`gitghost/rules.py`](gitghost/rules.py) — name, regex, severity, remediation advice. PRs welcome.
126
+
127
+ ## The score
128
+
129
+ One number, 0–100, higher is worse:
130
+
131
+ - **Severity-weighted findings** — a live AWS secret key counts far more than a webhook URL
132
+ - **Ghost weighting** — recovered-from-history secrets scale with age: something exposed for 2 years hurts more than something from last week
133
+ - **Reuse correlation** — the same secret across multiple repos, and the nastiest case: a "deleted" secret whose fingerprint still matches something *live*
134
+ - **Metadata exposure** — real email in commits, inferable timezone
135
+
136
+ A single live cloud key is enough to put an account in the red by itself. Run it, clean up, run again — the number tells you if it worked.
137
+
138
+ ## Where it draws the line (legal & ethics)
139
+
140
+ It only ever reads public repositories — things the account already chose to publish — and it's detection-only. It'll tell you a string *looks like* a credential and leave it at that. It won't try the key against the actual service to see if it still works, because quietly logging into someone else's account isn't the tool's job, and honestly it's not yours either. Use it on your own identity, or one you're authorized to assess.
141
+
142
+ Findings are shown as non-reversible fingerprints (a safe prefix, a length, a partial hash) — never recoverable key material — so reports are safe to share. gitghost does not retain raw secrets past the match.
143
+
144
+ Point it at yourself first. Most people turn up at least one thing they'd completely forgotten about — I did.
145
+
146
+ ## Leaked something? The fix is rotate, not delete.
147
+
148
+ Deleting the file or the repo doesn't un-leak anything — anyone who cloned already has the key. Rotate the credential first, then purge history with `git filter-repo`. The end of every dossier walks through both steps.
149
+
150
+ ## Development
151
+
152
+ ```bash
153
+ python -m pytest tests # detection engine tests
154
+ pip install pyflakes && python -m pyflakes gitghost # lint
155
+ ```
156
+
157
+ CI runs the suite on Python 3.10, 3.11, and 3.12.
158
+
159
+ ## License
160
+
161
+ MIT
@@ -0,0 +1 @@
1
+ __version__ = "1.4.0"
@@ -0,0 +1,31 @@
1
+ import os
2
+ import sys
3
+
4
+ from . import __version__
5
+
6
+ _ART = r"""
7
+ .-') _ ('-. .-. .-') .-') _
8
+ ( OO) ) ( OO ) / ( OO ). ( OO) )
9
+ ,----. ,-.-') / '._ ,----. ,--. ,--. .-'),-----. (_)---\_)/ '._
10
+ ' .-./-') | |OO)|'--...__)' .-./-') | | | |( OO' .-. '/ _ | |'--...__)
11
+ | |_( O- ) | | \'--. .--'| |_( O- )| .| |/ | | | |\ :` `. '--. .--'
12
+ | | .--, \ | |(_/ | | | | .--, \| |\_) | |\| | '..`''.) | |
13
+ (| | '. (_/,| |_.' | | (| | '. (_/| .-. | \ | | | |.-._) \ | |
14
+ | '--' |(_| | | | | '--' | | | | | `' '-' '\ / | |
15
+ `------' `--' `--' `------' `--' `--' `-----' `-----' `--'
16
+ """.strip("\n").splitlines()
17
+
18
+ _TAGLINE = "the secrets you deleted are still in git history"
19
+
20
+
21
+ def print_banner() -> None:
22
+ """Print the wordmark; plain text (and shorter) when not on a TTY."""
23
+ if os.environ.get("NO_COLOR") or not sys.stdout.isatty():
24
+ print(f"gitghost v{__version__} - {_TAGLINE}")
25
+ return
26
+
27
+ orange, dim, grey, reset = "\033[38;5;208m", "\033[2m", "\033[38;5;245m", "\033[0m"
28
+ for line in _ART:
29
+ print(orange + line + reset)
30
+ print(f"{dim}{_TAGLINE}{reset} {grey}v{__version__}{reset}")
31
+ print()