rithik 0.1.1__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 (51) hide show
  1. rithik-0.1.1/.gitattributes +4 -0
  2. rithik-0.1.1/.github/workflows/ci.yml +68 -0
  3. rithik-0.1.1/.github/workflows/publish.yml +80 -0
  4. rithik-0.1.1/.gitignore +10 -0
  5. rithik-0.1.1/LICENSE +21 -0
  6. rithik-0.1.1/PKG-INFO +253 -0
  7. rithik-0.1.1/README.md +233 -0
  8. rithik-0.1.1/docs/ARCHITECTURE.md +63 -0
  9. rithik-0.1.1/docs/EVAL.md +43 -0
  10. rithik-0.1.1/docs/PROJECT_STATE.md +57 -0
  11. rithik-0.1.1/docs/RELEASING.md +96 -0
  12. rithik-0.1.1/docs/ROADMAP.md +12 -0
  13. rithik-0.1.1/js/bin/rithik.js +5 -0
  14. rithik-0.1.1/js/lib/card.js +160 -0
  15. rithik-0.1.1/js/lib/cli.js +605 -0
  16. rithik-0.1.1/js/lib/pyjson.js +147 -0
  17. rithik-0.1.1/js/lib/pytext.js +170 -0
  18. rithik-0.1.1/js/lib/scam/engine.js +199 -0
  19. rithik-0.1.1/js/lib/scam/index.js +8 -0
  20. rithik-0.1.1/js/lib/scam/model.js +56 -0
  21. rithik-0.1.1/js/lib/scam/pycompat.js +564 -0
  22. rithik-0.1.1/js/lib/scam/rules.js +1005 -0
  23. rithik-0.1.1/js/lib/scam/urls.js +475 -0
  24. rithik-0.1.1/js/lib/scam/weights.js +111 -0
  25. rithik-0.1.1/js/lib/stdin.js +210 -0
  26. rithik-0.1.1/js/lib/term.js +75 -0
  27. rithik-0.1.1/js/lib/version.js +7 -0
  28. rithik-0.1.1/js/test/cli.parity.test.js +498 -0
  29. rithik-0.1.1/js/test/golden/cli.json +114 -0
  30. rithik-0.1.1/js/test/golden/scam.json +4639 -0
  31. rithik-0.1.1/js/test/scam.parity.test.js +171 -0
  32. rithik-0.1.1/package.json +39 -0
  33. rithik-0.1.1/pyproject.toml +50 -0
  34. rithik-0.1.1/scripts/eval.py +99 -0
  35. rithik-0.1.1/scripts/export_golden.py +189 -0
  36. rithik-0.1.1/src/rithik/__init__.py +3 -0
  37. rithik-0.1.1/src/rithik/__main__.py +6 -0
  38. rithik-0.1.1/src/rithik/_term.py +106 -0
  39. rithik-0.1.1/src/rithik/card.py +154 -0
  40. rithik-0.1.1/src/rithik/cli.py +297 -0
  41. rithik-0.1.1/src/rithik/scam/__init__.py +9 -0
  42. rithik-0.1.1/src/rithik/scam/engine.py +151 -0
  43. rithik-0.1.1/src/rithik/scam/model.py +45 -0
  44. rithik-0.1.1/src/rithik/scam/rules.py +1018 -0
  45. rithik-0.1.1/src/rithik/scam/urls.py +352 -0
  46. rithik-0.1.1/src/rithik/scam/weights.py +106 -0
  47. rithik-0.1.1/tests/data/README.md +5 -0
  48. rithik-0.1.1/tests/data/dev.jsonl +80 -0
  49. rithik-0.1.1/tests/data/test.jsonl +80 -0
  50. rithik-0.1.1/tests/test_cli.py +421 -0
  51. rithik-0.1.1/uv.lock +229 -0
@@ -0,0 +1,4 @@
1
+ # The npm launcher starts with a shebang; a CRLF checkout would make it "node
2
+ js/bin/* text eol=lf
3
+ # Golden files are compared byte for byte, so git must not rewrite their line endings.
4
+ js/test/golden/* text eol=lf
@@ -0,0 +1,68 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ test:
12
+ name: test (${{ matrix.os }}, Python ${{ matrix.python }})
13
+ runs-on: ${{ matrix.os }}
14
+ strategy:
15
+ # One failing cell should not hide whether the others pass.
16
+ fail-fast: false
17
+ matrix:
18
+ os: [ubuntu-latest, windows-latest]
19
+ # The oldest supported Python and the newest one.
20
+ python: ["3.9", "3.13"]
21
+ steps:
22
+ - uses: actions/checkout@v7
23
+
24
+ # setup-uv stopped publishing floating major tags at v8, so this has to name a full
25
+ # release.
26
+ - uses: astral-sh/setup-uv@v10.1.0
27
+
28
+ - name: Test
29
+ run: uv run --python ${{ matrix.python }} pytest -q
30
+
31
+ - name: Lint
32
+ if: matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
33
+ run: |
34
+ uv run ruff check
35
+ uv run ruff format --check
36
+
37
+ # The npm package is a port of the Python one and is tested against files generated
38
+ # from it. A Python change that forgets to regenerate them fails here, not in the port.
39
+ - name: Golden files are current
40
+ if: matrix.os == 'ubuntu-latest' && matrix.python == '3.13'
41
+ run: uv run python scripts/export_golden.py --check
42
+
43
+ node:
44
+ name: node (${{ matrix.os }}, Node ${{ matrix.node }})
45
+ runs-on: ${{ matrix.os }}
46
+ strategy:
47
+ fail-fast: false
48
+ matrix:
49
+ os: [ubuntu-latest, windows-latest]
50
+ # The oldest Node that package.json promises, and the current LTS.
51
+ node: ["18", "24"]
52
+ steps:
53
+ - uses: actions/checkout@v7
54
+
55
+ - uses: actions/setup-node@v7
56
+ with:
57
+ node-version: ${{ matrix.node }}
58
+
59
+ - name: Parity tests against the Python reference
60
+ run: npm test
61
+
62
+ - name: The packed tarball runs
63
+ shell: bash
64
+ run: |
65
+ npm pack --pack-destination "$RUNNER_TEMP"
66
+ cd "$RUNNER_TEMP"
67
+ npx --yes ./rithik-*.tgz --version
68
+ npx --yes ./rithik-*.tgz scam "Your SBI KYC expires today, update at sbi-kyc.top"
@@ -0,0 +1,80 @@
1
+ name: publish
2
+
3
+ # Trusted publishing to both registries: PyPI and npm each accept uploads from this workflow,
4
+ # so no API token is stored anywhere. See docs/RELEASING.md.
5
+ on:
6
+ release:
7
+ types: [published]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+
18
+ # setup-uv stopped publishing floating major tags at v8, so this has to name a full
19
+ # release.
20
+ - uses: astral-sh/setup-uv@v10.1.0
21
+
22
+ # Neither registry ever accepts a version twice, so a tag that disagrees with the code
23
+ # should stop here rather than upload something under the wrong number.
24
+ - name: Check that the tag matches every version string
25
+ run: |
26
+ version="${GITHUB_REF_NAME#v}"
27
+ test "$version" = "$(uv version --short)"
28
+ grep -qx "__version__ = \"$version\"" src/rithik/__init__.py
29
+ test "$version" = "$(node -p 'require("./package.json").version')"
30
+
31
+ - name: Build
32
+ run: uv build
33
+
34
+ - uses: actions/upload-artifact@v7
35
+ with:
36
+ name: dist
37
+ path: dist/
38
+ if-no-files-found: error
39
+
40
+ pypi:
41
+ needs: build
42
+ runs-on: ubuntu-latest
43
+ environment:
44
+ name: pypi
45
+ url: https://pypi.org/project/rithik/
46
+ # Only the upload job can mint the OIDC token that PyPI exchanges for upload rights.
47
+ permissions:
48
+ id-token: write
49
+ steps:
50
+ - uses: actions/download-artifact@v8
51
+ with:
52
+ name: dist
53
+ path: dist/
54
+
55
+ - uses: pypa/gh-action-pypi-publish@release/v1
56
+
57
+ npm:
58
+ # Gated on the same checks as PyPI, so the two registries never disagree about a version.
59
+ needs: build
60
+ runs-on: ubuntu-latest
61
+ permissions:
62
+ id-token: write
63
+ steps:
64
+ - uses: actions/checkout@v7
65
+
66
+ - uses: actions/setup-node@v7
67
+ with:
68
+ node-version: "24"
69
+ registry-url: "https://registry.npmjs.org"
70
+ # A release build should never restore anything from a cache.
71
+ package-manager-cache: false
72
+
73
+ # Trusted publishing needs npm 11.5.1 or later, which not every Node 24 build bundles.
74
+ - run: npm install -g npm@latest
75
+
76
+ - run: npm test
77
+
78
+ # With trusted publishing, npm exchanges the OIDC token itself and attaches a
79
+ # provenance attestation; there is no NODE_AUTH_TOKEN.
80
+ - run: npm publish
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ build/
5
+ dist/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .env
10
+ .env.*
rithik-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rithik Krishna T
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.
rithik-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,253 @@
1
+ Metadata-Version: 2.5
2
+ Name: rithik
3
+ Version: 0.1.1
4
+ Summary: Rithik Krishna T's command-line card, plus an offline checker for Indian scam messages and links.
5
+ Project-URL: Homepage, https://github.com/Daemon-VI/rithik
6
+ Project-URL: Issues, https://github.com/Daemon-VI/rithik/issues
7
+ Author-email: Rithik Krishna T <trithikkrishna@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: cli,india,phishing,scam,security,sms,upi
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Communications
17
+ Classifier: Topic :: Security
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+
21
+ # rithik
22
+
23
+ [![ci](https://github.com/Daemon-VI/rithik/actions/workflows/ci.yml/badge.svg)](https://github.com/Daemon-VI/rithik/actions/workflows/ci.yml)
24
+
25
+ `rithik` prints my card in your terminal, and it checks suspicious messages and links for the
26
+ scam patterns common in India, entirely offline. Paste in an SMS, a WhatsApp forward or a link,
27
+ and it tells you which known patterns matched, why they matter, and what to do next.
28
+
29
+ ## Install
30
+
31
+ With Node.js 18 or newer, you can run it without installing anything:
32
+
33
+ ```sh
34
+ npx rithik # once it is published to npm
35
+ npx github:Daemon-VI/rithik # works now, straight from GitHub
36
+ npm install -g rithik # or keep the `rithik` command around
37
+ ```
38
+
39
+ npm 12 refuses packages fetched from git unless you allow it, so on npm 12 the GitHub form is
40
+ `npx --allow-git=root github:Daemon-VI/rithik`. The npm 10 and 11 releases that ship with
41
+ Node 18 to 24 need no flag.
42
+
43
+ With Python 3.9 or newer:
44
+
45
+ ```sh
46
+ pipx install git+https://github.com/Daemon-VI/rithik # works now
47
+ pipx install rithik # once it is published to PyPI
48
+ uvx rithik # run once without installing
49
+ ```
50
+
51
+ Neither version has any dependencies. If the `rithik` script is not on your `PATH`,
52
+ `python -m rithik` does the same thing.
53
+
54
+ ### One engine, two runtimes
55
+
56
+ The Python package is the reference implementation, and the npm package is a JavaScript port
57
+ of it. The port is tested against golden files generated from the Python code: every corpus
58
+ message and edge case must produce the same verdict, reasons and score, and the terminal output
59
+ must match byte for byte. CI fails if the Python code changes and the golden files are not
60
+ regenerated.
61
+
62
+ ## Usage
63
+
64
+ ```sh
65
+ rithik # the card
66
+ rithik card --json # the card as JSON
67
+ rithik scam "<message or link>"
68
+ rithik scam - # read the message from standard input
69
+ rithik --version
70
+ ```
71
+
72
+ `--no-color` turns colour off, and so does the `NO_COLOR` environment variable. Colour is also
73
+ off whenever the output is not a terminal. `--json` prints only JSON, so scripts can use it.
74
+
75
+ ### Checking a message
76
+
77
+ The quotes matter, because they keep your shell from interpreting the message.
78
+
79
+ ```sh
80
+ rithik scam "Dear customer, your SBI account will be blocked today. Update KYC now: https://sbi-kyc-help-desk.top/update"
81
+ ```
82
+
83
+ The output (real, from v0.1.0):
84
+
85
+ ```text
86
+ LIKELY SCAM (score 0.96 of 1.00)
87
+
88
+ Why
89
+ - Link uses a bank or brand name on a domain that brand does not own:
90
+ "hxxps://sbi-kyc-help-desk[.]top/update"
91
+ - Asks you to update or verify KYC, PAN or Aadhaar through a message: "KYC
92
+ now: hxxps://sbi-kyc-help-desk[.]top/update"
93
+ - Claims your bank account, card or UPI will be blocked unless you act:
94
+ "account will be blocked"
95
+ - Link uses a cheap domain ending that is common in phishing:
96
+ "hxxps://sbi-kyc-help-desk[.]top/update"
97
+ - Threatens a loss or penalty to rush you: "will be blocked"
98
+ - Asks for something and gives a link that is not an official site:
99
+ "hxxps://sbi-kyc-help-desk[.]top/update"
100
+
101
+ Links in the message
102
+ - hxxps://sbi-kyc-help-desk[.]top/update
103
+ (dots shown as [.] so the links cannot be opened by accident)
104
+
105
+ What to do
106
+ - Do not click the links, call the numbers or install anything the message
107
+ asks for.
108
+ - Never share an OTP, UPI PIN or card details, and remember that you never
109
+ need your UPI PIN to receive money.
110
+ - Verify through your bank's official app, or the number printed on your
111
+ card, not a number from the message.
112
+ - If money was taken, call 1930 at once or report it at
113
+ https://cybercrime.gov.in
114
+ - Report the fraud SMS or call through Chakshu at
115
+ https://sancharsaathi.gov.in
116
+ ```
117
+
118
+ The verdict is one of `LIKELY SCAM`, `SUSPICIOUS` or `NO KNOWN SCAM SIGNALS`. The last one is
119
+ worded that way on purpose, because it only means that none of the known patterns matched:
120
+
121
+ ```text
122
+ NO KNOWN SCAM SIGNALS (score 0.00 of 1.00)
123
+
124
+ None of the scam patterns this checker knows matched. That is not a guarantee
125
+ that the message is genuine: new scams appear all the time. Before you pay, or
126
+ share an OTP or PIN, verify through the official app or website.
127
+ ```
128
+
129
+ A long message is easier to pipe in:
130
+
131
+ ```sh
132
+ pbpaste | rithik scam - # macOS
133
+ Get-Clipboard | rithik scam - # PowerShell
134
+ rithik scam - < message.txt
135
+ ```
136
+
137
+ With `--json`, the report is printed as JSON, and links appear as the message wrote them.
138
+ This is the same check, trimmed to its first reason:
139
+
140
+ ```json
141
+ {
142
+ "verdict": "scam",
143
+ "score": 0.964,
144
+ "reasons": [
145
+ {
146
+ "code": "url_brand_impersonation",
147
+ "label": "Link uses a bank or brand name on a domain that brand does not own.",
148
+ "evidence": "https://sbi-kyc-help-desk.top/update",
149
+ "weight": 2.5
150
+ }
151
+ ],
152
+ "urls": [
153
+ "https://sbi-kyc-help-desk.top/update"
154
+ ]
155
+ }
156
+ ```
157
+
158
+ The exit code is 0 whenever a check ran, whatever the verdict. It is 2 for a usage error, such
159
+ as a missing or empty message.
160
+
161
+ ## What the checker looks for
162
+
163
+ The message text is checked for these families of signals:
164
+
165
+ - urgency and threats
166
+ - KYC, PAN and Aadhaar update requests
167
+ - blocked or suspended accounts
168
+ - electricity disconnection notices
169
+ - UPI PIN requests and UPI collect requests
170
+ - requests for an OTP
171
+ - prize and lottery wins
172
+ - task-based job offers
173
+ - parcels held by a courier or at customs
174
+ - "digital arrest" threats and impersonation of officials
175
+ - remote-access apps and APK files to install
176
+ - loan and investment offers
177
+ - reward points, tax refunds and traffic e-challans
178
+
179
+ Every link is checked for:
180
+
181
+ - URL shorteners, which hide where a link goes
182
+ - bare IP addresses used in place of a domain name
183
+ - punycode (`xn--`) domains, which can imitate other scripts
184
+ - lookalike domains that use a bank's or brand's name but are not on its list of official domains
185
+ - domain endings (TLDs) that are common in scam campaigns
186
+
187
+ Each signal carries a weight, and together they make the score that decides the verdict. Each
188
+ reason in the output names the words or the link that triggered it, so you can judge it for
189
+ yourself.
190
+
191
+ ## Accuracy
192
+
193
+ Measured results, and how the evaluation set was built, are in
194
+ [docs/EVAL.md](https://github.com/Daemon-VI/rithik/blob/main/docs/EVAL.md).
195
+ This README gives no figures, so that it cannot drift out of step with that file.
196
+
197
+ ## Limitations
198
+
199
+ - It is a heuristic. It recognises patterns that someone wrote down, and it will miss new or
200
+ unusual scams.
201
+ - It understands English and Hinglish (Hindi written in the Latin alphabet) only.
202
+ - A result of `NO KNOWN SCAM SIGNALS` is not a guarantee that a message is safe.
203
+ - It never opens links, so it judges a link by its address alone, not by the page behind it.
204
+ - The evaluation set is synthetic: messages written from public fraud advisories rather than
205
+ collected from real people. Its scores describe that set, not real-world accuracy.
206
+
207
+ ## Privacy
208
+
209
+ Everything runs on your machine. `rithik` makes no network requests, stores nothing, and
210
+ collects no telemetry. The message you check exists only in the running process.
211
+
212
+ ## Where to report fraud in India
213
+
214
+ - **Lost money?** Call the National Cyber Crime Helpline, **1930**, as soon as possible, or
215
+ file a complaint at <https://cybercrime.gov.in>.
216
+ - **Fraud calls, SMS or WhatsApp messages:** report them through Chakshu at
217
+ <https://sancharsaathi.gov.in>.
218
+ - Contact your bank directly, using the number printed on your card or the bank's official
219
+ app.
220
+
221
+ ## Contributing
222
+
223
+ To catch a new pattern, add a rule together with a unit test that shows it firing on a
224
+ message it should catch and staying quiet on a similar genuine one.
225
+
226
+ ```sh
227
+ uv sync
228
+ uv run pytest -q
229
+ uv run ruff check
230
+ uv run ruff format
231
+ uv run python scripts/export_golden.py # after any behaviour change
232
+ npm test # the JavaScript port must still match
233
+ ```
234
+
235
+ A rule change goes into both `src/rithik/scam/` and its mirror in `js/lib/scam/`. Change the
236
+ Python first, regenerate the golden files, then bring the port level until `npm test` passes.
237
+
238
+ Some constraints apply:
239
+
240
+ - The code must run on Python 3.9 and Node 18, and use only the standard library.
241
+ - It must never touch the network.
242
+ - Test messages must be invented. Never use a real person's message, number or account.
243
+ - Scam links in tests must use invented domains.
244
+
245
+ Releases are covered in [docs/RELEASING.md](https://github.com/Daemon-VI/rithik/blob/main/docs/RELEASING.md).
246
+
247
+ ## License
248
+
249
+ MIT. See [LICENSE](https://github.com/Daemon-VI/rithik/blob/main/LICENSE).
250
+
251
+ ---
252
+
253
+ Made by [Rithik Krishna T](https://github.com/Daemon-VI).
rithik-0.1.1/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # rithik
2
+
3
+ [![ci](https://github.com/Daemon-VI/rithik/actions/workflows/ci.yml/badge.svg)](https://github.com/Daemon-VI/rithik/actions/workflows/ci.yml)
4
+
5
+ `rithik` prints my card in your terminal, and it checks suspicious messages and links for the
6
+ scam patterns common in India, entirely offline. Paste in an SMS, a WhatsApp forward or a link,
7
+ and it tells you which known patterns matched, why they matter, and what to do next.
8
+
9
+ ## Install
10
+
11
+ With Node.js 18 or newer, you can run it without installing anything:
12
+
13
+ ```sh
14
+ npx rithik # once it is published to npm
15
+ npx github:Daemon-VI/rithik # works now, straight from GitHub
16
+ npm install -g rithik # or keep the `rithik` command around
17
+ ```
18
+
19
+ npm 12 refuses packages fetched from git unless you allow it, so on npm 12 the GitHub form is
20
+ `npx --allow-git=root github:Daemon-VI/rithik`. The npm 10 and 11 releases that ship with
21
+ Node 18 to 24 need no flag.
22
+
23
+ With Python 3.9 or newer:
24
+
25
+ ```sh
26
+ pipx install git+https://github.com/Daemon-VI/rithik # works now
27
+ pipx install rithik # once it is published to PyPI
28
+ uvx rithik # run once without installing
29
+ ```
30
+
31
+ Neither version has any dependencies. If the `rithik` script is not on your `PATH`,
32
+ `python -m rithik` does the same thing.
33
+
34
+ ### One engine, two runtimes
35
+
36
+ The Python package is the reference implementation, and the npm package is a JavaScript port
37
+ of it. The port is tested against golden files generated from the Python code: every corpus
38
+ message and edge case must produce the same verdict, reasons and score, and the terminal output
39
+ must match byte for byte. CI fails if the Python code changes and the golden files are not
40
+ regenerated.
41
+
42
+ ## Usage
43
+
44
+ ```sh
45
+ rithik # the card
46
+ rithik card --json # the card as JSON
47
+ rithik scam "<message or link>"
48
+ rithik scam - # read the message from standard input
49
+ rithik --version
50
+ ```
51
+
52
+ `--no-color` turns colour off, and so does the `NO_COLOR` environment variable. Colour is also
53
+ off whenever the output is not a terminal. `--json` prints only JSON, so scripts can use it.
54
+
55
+ ### Checking a message
56
+
57
+ The quotes matter, because they keep your shell from interpreting the message.
58
+
59
+ ```sh
60
+ rithik scam "Dear customer, your SBI account will be blocked today. Update KYC now: https://sbi-kyc-help-desk.top/update"
61
+ ```
62
+
63
+ The output (real, from v0.1.0):
64
+
65
+ ```text
66
+ LIKELY SCAM (score 0.96 of 1.00)
67
+
68
+ Why
69
+ - Link uses a bank or brand name on a domain that brand does not own:
70
+ "hxxps://sbi-kyc-help-desk[.]top/update"
71
+ - Asks you to update or verify KYC, PAN or Aadhaar through a message: "KYC
72
+ now: hxxps://sbi-kyc-help-desk[.]top/update"
73
+ - Claims your bank account, card or UPI will be blocked unless you act:
74
+ "account will be blocked"
75
+ - Link uses a cheap domain ending that is common in phishing:
76
+ "hxxps://sbi-kyc-help-desk[.]top/update"
77
+ - Threatens a loss or penalty to rush you: "will be blocked"
78
+ - Asks for something and gives a link that is not an official site:
79
+ "hxxps://sbi-kyc-help-desk[.]top/update"
80
+
81
+ Links in the message
82
+ - hxxps://sbi-kyc-help-desk[.]top/update
83
+ (dots shown as [.] so the links cannot be opened by accident)
84
+
85
+ What to do
86
+ - Do not click the links, call the numbers or install anything the message
87
+ asks for.
88
+ - Never share an OTP, UPI PIN or card details, and remember that you never
89
+ need your UPI PIN to receive money.
90
+ - Verify through your bank's official app, or the number printed on your
91
+ card, not a number from the message.
92
+ - If money was taken, call 1930 at once or report it at
93
+ https://cybercrime.gov.in
94
+ - Report the fraud SMS or call through Chakshu at
95
+ https://sancharsaathi.gov.in
96
+ ```
97
+
98
+ The verdict is one of `LIKELY SCAM`, `SUSPICIOUS` or `NO KNOWN SCAM SIGNALS`. The last one is
99
+ worded that way on purpose, because it only means that none of the known patterns matched:
100
+
101
+ ```text
102
+ NO KNOWN SCAM SIGNALS (score 0.00 of 1.00)
103
+
104
+ None of the scam patterns this checker knows matched. That is not a guarantee
105
+ that the message is genuine: new scams appear all the time. Before you pay, or
106
+ share an OTP or PIN, verify through the official app or website.
107
+ ```
108
+
109
+ A long message is easier to pipe in:
110
+
111
+ ```sh
112
+ pbpaste | rithik scam - # macOS
113
+ Get-Clipboard | rithik scam - # PowerShell
114
+ rithik scam - < message.txt
115
+ ```
116
+
117
+ With `--json`, the report is printed as JSON, and links appear as the message wrote them.
118
+ This is the same check, trimmed to its first reason:
119
+
120
+ ```json
121
+ {
122
+ "verdict": "scam",
123
+ "score": 0.964,
124
+ "reasons": [
125
+ {
126
+ "code": "url_brand_impersonation",
127
+ "label": "Link uses a bank or brand name on a domain that brand does not own.",
128
+ "evidence": "https://sbi-kyc-help-desk.top/update",
129
+ "weight": 2.5
130
+ }
131
+ ],
132
+ "urls": [
133
+ "https://sbi-kyc-help-desk.top/update"
134
+ ]
135
+ }
136
+ ```
137
+
138
+ The exit code is 0 whenever a check ran, whatever the verdict. It is 2 for a usage error, such
139
+ as a missing or empty message.
140
+
141
+ ## What the checker looks for
142
+
143
+ The message text is checked for these families of signals:
144
+
145
+ - urgency and threats
146
+ - KYC, PAN and Aadhaar update requests
147
+ - blocked or suspended accounts
148
+ - electricity disconnection notices
149
+ - UPI PIN requests and UPI collect requests
150
+ - requests for an OTP
151
+ - prize and lottery wins
152
+ - task-based job offers
153
+ - parcels held by a courier or at customs
154
+ - "digital arrest" threats and impersonation of officials
155
+ - remote-access apps and APK files to install
156
+ - loan and investment offers
157
+ - reward points, tax refunds and traffic e-challans
158
+
159
+ Every link is checked for:
160
+
161
+ - URL shorteners, which hide where a link goes
162
+ - bare IP addresses used in place of a domain name
163
+ - punycode (`xn--`) domains, which can imitate other scripts
164
+ - lookalike domains that use a bank's or brand's name but are not on its list of official domains
165
+ - domain endings (TLDs) that are common in scam campaigns
166
+
167
+ Each signal carries a weight, and together they make the score that decides the verdict. Each
168
+ reason in the output names the words or the link that triggered it, so you can judge it for
169
+ yourself.
170
+
171
+ ## Accuracy
172
+
173
+ Measured results, and how the evaluation set was built, are in
174
+ [docs/EVAL.md](https://github.com/Daemon-VI/rithik/blob/main/docs/EVAL.md).
175
+ This README gives no figures, so that it cannot drift out of step with that file.
176
+
177
+ ## Limitations
178
+
179
+ - It is a heuristic. It recognises patterns that someone wrote down, and it will miss new or
180
+ unusual scams.
181
+ - It understands English and Hinglish (Hindi written in the Latin alphabet) only.
182
+ - A result of `NO KNOWN SCAM SIGNALS` is not a guarantee that a message is safe.
183
+ - It never opens links, so it judges a link by its address alone, not by the page behind it.
184
+ - The evaluation set is synthetic: messages written from public fraud advisories rather than
185
+ collected from real people. Its scores describe that set, not real-world accuracy.
186
+
187
+ ## Privacy
188
+
189
+ Everything runs on your machine. `rithik` makes no network requests, stores nothing, and
190
+ collects no telemetry. The message you check exists only in the running process.
191
+
192
+ ## Where to report fraud in India
193
+
194
+ - **Lost money?** Call the National Cyber Crime Helpline, **1930**, as soon as possible, or
195
+ file a complaint at <https://cybercrime.gov.in>.
196
+ - **Fraud calls, SMS or WhatsApp messages:** report them through Chakshu at
197
+ <https://sancharsaathi.gov.in>.
198
+ - Contact your bank directly, using the number printed on your card or the bank's official
199
+ app.
200
+
201
+ ## Contributing
202
+
203
+ To catch a new pattern, add a rule together with a unit test that shows it firing on a
204
+ message it should catch and staying quiet on a similar genuine one.
205
+
206
+ ```sh
207
+ uv sync
208
+ uv run pytest -q
209
+ uv run ruff check
210
+ uv run ruff format
211
+ uv run python scripts/export_golden.py # after any behaviour change
212
+ npm test # the JavaScript port must still match
213
+ ```
214
+
215
+ A rule change goes into both `src/rithik/scam/` and its mirror in `js/lib/scam/`. Change the
216
+ Python first, regenerate the golden files, then bring the port level until `npm test` passes.
217
+
218
+ Some constraints apply:
219
+
220
+ - The code must run on Python 3.9 and Node 18, and use only the standard library.
221
+ - It must never touch the network.
222
+ - Test messages must be invented. Never use a real person's message, number or account.
223
+ - Scam links in tests must use invented domains.
224
+
225
+ Releases are covered in [docs/RELEASING.md](https://github.com/Daemon-VI/rithik/blob/main/docs/RELEASING.md).
226
+
227
+ ## License
228
+
229
+ MIT. See [LICENSE](https://github.com/Daemon-VI/rithik/blob/main/LICENSE).
230
+
231
+ ---
232
+
233
+ Made by [Rithik Krishna T](https://github.com/Daemon-VI).