scamscript 0.2.0__tar.gz → 0.3.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 (61) hide show
  1. {scamscript-0.2.0 → scamscript-0.3.0}/CHANGELOG.md +15 -0
  2. {scamscript-0.2.0 → scamscript-0.3.0}/PKG-INFO +22 -38
  3. {scamscript-0.2.0 → scamscript-0.3.0}/README.md +20 -16
  4. {scamscript-0.2.0 → scamscript-0.3.0}/pyproject.toml +3 -2
  5. {scamscript-0.2.0 → scamscript-0.3.0}/scripts/calibrate.py +5 -3
  6. {scamscript-0.2.0 → scamscript-0.3.0}/scripts/injection_eval.py +5 -3
  7. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/__init__.py +1 -1
  8. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/cli.py +11 -8
  9. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/client.py +3 -5
  10. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/config.py +19 -7
  11. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_cli.py +31 -1
  12. {scamscript-0.2.0 → scamscript-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {scamscript-0.2.0 → scamscript-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  14. {scamscript-0.2.0 → scamscript-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  15. {scamscript-0.2.0 → scamscript-0.3.0}/.github/SECURITY.md +0 -0
  16. {scamscript-0.2.0 → scamscript-0.3.0}/.github/pull_request_template.md +0 -0
  17. {scamscript-0.2.0 → scamscript-0.3.0}/.github/workflows/ci.yml +0 -0
  18. {scamscript-0.2.0 → scamscript-0.3.0}/.github/workflows/release.yml +0 -0
  19. {scamscript-0.2.0 → scamscript-0.3.0}/.gitignore +0 -0
  20. {scamscript-0.2.0 → scamscript-0.3.0}/CITATION.cff +0 -0
  21. {scamscript-0.2.0 → scamscript-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  22. {scamscript-0.2.0 → scamscript-0.3.0}/CONTRIBUTING.md +0 -0
  23. {scamscript-0.2.0 → scamscript-0.3.0}/LICENSE +0 -0
  24. {scamscript-0.2.0 → scamscript-0.3.0}/docs/banner.svg +0 -0
  25. {scamscript-0.2.0 → scamscript-0.3.0}/docs/calibration.md +0 -0
  26. {scamscript-0.2.0 → scamscript-0.3.0}/docs/demo.gif +0 -0
  27. {scamscript-0.2.0 → scamscript-0.3.0}/docs/sample-report.md +0 -0
  28. {scamscript-0.2.0 → scamscript-0.3.0}/docs/social-preview.png +0 -0
  29. {scamscript-0.2.0 → scamscript-0.3.0}/examples/adversarial/fake_output_json.txt +0 -0
  30. {scamscript-0.2.0 → scamscript-0.3.0}/examples/adversarial/fence_escape.txt +0 -0
  31. {scamscript-0.2.0 → scamscript-0.3.0}/examples/adversarial/fixed_attack_type.txt +0 -0
  32. {scamscript-0.2.0 → scamscript-0.3.0}/examples/adversarial/non_json_demand.txt +0 -0
  33. {scamscript-0.2.0 → scamscript-0.3.0}/examples/adversarial/print_system_prompt.txt +0 -0
  34. {scamscript-0.2.0 → scamscript-0.3.0}/examples/adversarial/role_switch.txt +0 -0
  35. {scamscript-0.2.0 → scamscript-0.3.0}/examples/adversarial/score_override.txt +0 -0
  36. {scamscript-0.2.0 → scamscript-0.3.0}/examples/crude_robocall.txt +0 -0
  37. {scamscript-0.2.0 → scamscript-0.3.0}/examples/gov_impersonation_vishing.txt +0 -0
  38. {scamscript-0.2.0 → scamscript-0.3.0}/examples/prompt_injection_script.txt +0 -0
  39. {scamscript-0.2.0 → scamscript-0.3.0}/examples/romance_scam_sms.txt +0 -0
  40. {scamscript-0.2.0 → scamscript-0.3.0}/examples/sophisticated_wire_fraud.txt +0 -0
  41. {scamscript-0.2.0 → scamscript-0.3.0}/examples/tech_support_scam.txt +0 -0
  42. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/__main__.py +0 -0
  43. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/analysis.py +0 -0
  44. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/ingest.py +0 -0
  45. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/models.py +0 -0
  46. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/prompts.py +0 -0
  47. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/redact.py +0 -0
  48. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/render.py +0 -0
  49. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/report.py +0 -0
  50. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/store.py +0 -0
  51. {scamscript-0.2.0 → scamscript-0.3.0}/src/scamscript/verify.py +0 -0
  52. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_analysis.py +0 -0
  53. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_client.py +0 -0
  54. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_ingest.py +0 -0
  55. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_models.py +0 -0
  56. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_prompts.py +0 -0
  57. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_redact.py +0 -0
  58. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_render.py +0 -0
  59. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_report.py +0 -0
  60. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_store.py +0 -0
  61. {scamscript-0.2.0 → scamscript-0.3.0}/tests/test_verify.py +0 -0
@@ -5,6 +5,21 @@ schema are the things that count as public API.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.3.0 - 2026-11-02
9
+
10
+ ### Changed
11
+
12
+ - Breaking: no default provider or model. `--base-url` and `--model` (or `SCAMSCRIPT_BASE_URL` /
13
+ `SCAMSCRIPT_MODEL`) are required for API calls; `--dry-run` works without them.
14
+
15
+ ## 0.2.1 - 2026-11-02
16
+
17
+ ### Fixed
18
+
19
+ - PyPI project page: absolute image and link URLs so the banner, demo and docs links resolve.
20
+ - License metadata now uses the SPDX expression `MIT` plus `license-files` instead of embedding the
21
+ full license text in the sidebar.
22
+
8
23
  ### Added
9
24
 
10
25
  - Terminal demo GIF in the README, recorded against a local stub endpoint.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scamscript
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Reverse-engineer scam scripts and vishing transcripts into structured counter-playbooks
5
5
  Project-URL: Homepage, https://github.com/sjdsjadjajdjsa/scamscript
6
6
  Project-URL: Repository, https://github.com/sjdsjadjajdjsa/scamscript
@@ -8,27 +8,7 @@ Project-URL: Issues, https://github.com/sjdsjadjajdjsa/scamscript/issues
8
8
  Project-URL: Changelog, https://github.com/sjdsjadjajdjsa/scamscript/blob/master/CHANGELOG.md
9
9
  Author-email: scamscript contributors <313729361+sjdsjadjajdjsa@users.noreply.github.com>
10
10
  Maintainer-email: scamscript contributors <313729361+sjdsjadjajdjsa@users.noreply.github.com>
11
- License: MIT License
12
-
13
- Copyright (c) 2026 scamscript contributors
14
-
15
- Permission is hereby granted, free of charge, to any person obtaining a copy
16
- of this software and associated documentation files (the "Software"), to deal
17
- in the Software without restriction, including without limitation the rights
18
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
- copies of the Software, and to permit persons to whom the Software is
20
- furnished to do so, subject to the following conditions:
21
-
22
- The above copyright notice and this permission notice shall be included in all
23
- copies or substantial portions of the Software.
24
-
25
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
- SOFTWARE.
11
+ License-Expression: MIT
32
12
  License-File: LICENSE
33
13
  Keywords: cli,fraud,osint,phishing,scam,security,smishing,social-engineering,transcripts,vishing
34
14
  Classifier: Development Status :: 4 - Beta
@@ -55,11 +35,11 @@ Requires-Dist: twine==6.1.0; extra == 'dev'
55
35
  Description-Content-Type: text/markdown
56
36
 
57
37
  <p align="center">
58
- <img src="docs/banner.svg" alt="scamscript: reverse-engineer the playbook, not the story" width="100%">
38
+ <img src="https://raw.githubusercontent.com/sjdsjadjajdjsa/scamscript/master/docs/banner.svg" alt="scamscript: reverse-engineer the playbook, not the story" width="100%">
59
39
  </p>
60
40
 
61
41
  <p align="center">
62
- <img src="docs/demo.gif" alt="terminal demo: analyzing a script, then ranking a directory" width="760">
42
+ <img src="https://raw.githubusercontent.com/sjdsjadjajdjsa/scamscript/master/docs/demo.gif" alt="terminal demo: analyzing a script, then ranking a directory" width="760">
63
43
  <br>
64
44
  <sub>Recorded against a local stub endpoint, so no API spend.</sub>
65
45
  </p>
@@ -68,7 +48,7 @@ Description-Content-Type: text/markdown
68
48
 
69
49
  [![CI](https://github.com/sjdsjadjajdjsa/scamscript/actions/workflows/ci.yml/badge.svg)](https://github.com/sjdsjadjajdjsa/scamscript/actions/workflows/ci.yml)
70
50
  [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-3776ab)](pyproject.toml)
71
- [![License](https://img.shields.io/badge/license-MIT-2a9d8f)](LICENSE)
51
+ [![License](https://img.shields.io/badge/license-MIT-2a9d8f)](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/LICENSE)
72
52
  [![Scope](https://img.shields.io/badge/scope-security%20research-e63946)](#scope-and-disclaimer)
73
53
 
74
54
  A reverse-engineering analyzer for leaked or public scam call scripts and vishing transcripts.
@@ -96,18 +76,22 @@ mechanics instead of general advice.
96
76
  pip install scamscript # or, for development:
97
77
  # git clone https://github.com/sjdsjadjajdjsa/scamscript && cd scamscript && pip install -e ".[dev]"
98
78
 
99
- export API_KEY=... # never committed, never logged
79
+ export API_KEY=...
80
+ export SCAMSCRIPT_BASE_URL=https://openrouter.ai/api/v1
81
+ export SCAMSCRIPT_MODEL=deepseek/deepseek-v4.1-flash-1017
100
82
 
101
83
  scamscript --dry-run analyze --file examples/tech_support_scam.txt # inspect the prompt, spend nothing
102
84
  scamscript analyze --file examples/tech_support_scam.txt
103
85
  ```
104
86
 
105
- Any OpenAI-compatible `/chat/completions` endpoint works. Defaults are Venice with
106
- `deepseek-v4-1-flash-1017-uncensored`; OpenRouter needs only the model slug:
87
+ Any OpenAI-compatible `/chat/completions` endpoint works. There is no baked-in provider or model:
88
+ pass `--base-url` and `--model`, or set `SCAMSCRIPT_BASE_URL` and `SCAMSCRIPT_MODEL`.
107
89
 
108
90
  ```bash
109
91
  scamscript --base-url https://openrouter.ai/api/v1 \
110
92
  --model deepseek/deepseek-v4.1-flash-1017 analyze --file script.txt
93
+
94
+ scamscript --base-url http://localhost:11434/v1 --model llama3.2 analyze --file script.txt
111
95
  ```
112
96
 
113
97
  ## What it produces
@@ -135,7 +119,7 @@ compared against the source, and lines that are paraphrased or simply not there
135
119
  `--strict-quotes` to make a missing line fail the run, and `--no-verify` to skip it.
136
120
 
137
121
  Batch mode ranks a whole directory into one markdown report. A redacted report from the sample
138
- corpus is at [`docs/sample-report.md`](docs/sample-report.md).
122
+ corpus is at [`docs/sample-report.md`](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/docs/sample-report.md).
139
123
 
140
124
  ## Commands
141
125
 
@@ -197,13 +181,13 @@ bounded.
197
181
  ## Install and configure
198
182
 
199
183
  Python 3.12+. `pip install scamscript` for use, `pip install -e ".[dev]"` from a clone for tests.
200
- Release history is in [CHANGELOG.md](CHANGELOG.md).
184
+ Release history is in [CHANGELOG.md](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/CHANGELOG.md).
201
185
 
202
186
  | Setting | Flag | Env | Default |
203
187
  | --- | --- | --- | --- |
204
188
  | API key | `--api-key` | `API_KEY` | none, required unless `--dry-run` |
205
- | Base URL | `--base-url` | `SCAMSCRIPT_BASE_URL` | `https://api.venice.ai/api/v1` |
206
- | Model | `--model` | `SCAMSCRIPT_MODEL` | `deepseek-v4-1-flash-1017-uncensored` |
189
+ | Base URL | `--base-url` | `SCAMSCRIPT_BASE_URL` | none, required |
190
+ | Model | `--model` | `SCAMSCRIPT_MODEL` | none, required |
207
191
  | State dir | `--home` | `SCAMSCRIPT_HOME` | `~/.scamscript` |
208
192
 
209
193
  ## Tests, CI and calibration
@@ -219,12 +203,12 @@ Calibration is evidence, not vibes. `scripts/calibrate.py` measures first-attemp
219
203
  adherence, repair rate, latency, score spread and billed cost against a live endpoint:
220
204
 
221
205
  ```bash
222
- API_KEY=... python scripts/calibrate.py --dir examples --repeat 3 \
223
- --model deepseek/deepseek-v4.1-flash-1017 \
224
- --base-url https://openrouter.ai/api/v1
206
+ API_KEY=... SCAMSCRIPT_BASE_URL=https://openrouter.ai/api/v1 \
207
+ python scripts/calibrate.py --dir examples --repeat 3 \
208
+ --model deepseek/deepseek-v4.1-flash-1017
225
209
  ```
226
210
 
227
- Recorded passes with their numbers are in [`docs/calibration.md`](docs/calibration.md): three rubric
211
+ Recorded passes with their numbers are in [`docs/calibration.md`](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/docs/calibration.md): three rubric
228
212
  repeats, a three-model comparison, an adversarial set and end-case stability runs. The current rubric
229
213
  reaches both ends of the scale, and quote checking flags the invented lines a plain reading would
230
214
  take as evidence.
@@ -232,7 +216,7 @@ take as evidence.
232
216
  The adversarial set lives in `examples/adversarial/` and has its own harness:
233
217
 
234
218
  ```bash
235
- API_KEY=... python scripts/injection_eval.py --model ... --base-url ...
219
+ API_KEY=... SCAMSCRIPT_BASE_URL=... python scripts/injection_eval.py --model ...
236
220
  ```
237
221
 
238
222
  Format held in 7/7 cases; embedded demands were obeyed in 1 of 6. The fence protects the schema, not
@@ -274,5 +258,5 @@ docs/ calibration record, sample report, banner, demo
274
258
  For security research and fraud-awareness training only. Not legal advice, not an attribution
275
259
  tool, no warranty. Keep API keys out of commits; the key is read from the environment.
276
260
 
277
- MIT licensed, see [LICENSE](LICENSE). Contribution notes in [CONTRIBUTING.md](CONTRIBUTING.md),
261
+ MIT licensed, see [LICENSE](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/LICENSE). Contribution notes in [CONTRIBUTING.md](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/CONTRIBUTING.md),
278
262
  vulnerability reports in [SECURITY.md](.github/SECURITY.md).
@@ -1,9 +1,9 @@
1
1
  <p align="center">
2
- <img src="docs/banner.svg" alt="scamscript: reverse-engineer the playbook, not the story" width="100%">
2
+ <img src="https://raw.githubusercontent.com/sjdsjadjajdjsa/scamscript/master/docs/banner.svg" alt="scamscript: reverse-engineer the playbook, not the story" width="100%">
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <img src="docs/demo.gif" alt="terminal demo: analyzing a script, then ranking a directory" width="760">
6
+ <img src="https://raw.githubusercontent.com/sjdsjadjajdjsa/scamscript/master/docs/demo.gif" alt="terminal demo: analyzing a script, then ranking a directory" width="760">
7
7
  <br>
8
8
  <sub>Recorded against a local stub endpoint, so no API spend.</sub>
9
9
  </p>
@@ -12,7 +12,7 @@
12
12
 
13
13
  [![CI](https://github.com/sjdsjadjajdjsa/scamscript/actions/workflows/ci.yml/badge.svg)](https://github.com/sjdsjadjajdjsa/scamscript/actions/workflows/ci.yml)
14
14
  [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13-3776ab)](pyproject.toml)
15
- [![License](https://img.shields.io/badge/license-MIT-2a9d8f)](LICENSE)
15
+ [![License](https://img.shields.io/badge/license-MIT-2a9d8f)](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/LICENSE)
16
16
  [![Scope](https://img.shields.io/badge/scope-security%20research-e63946)](#scope-and-disclaimer)
17
17
 
18
18
  A reverse-engineering analyzer for leaked or public scam call scripts and vishing transcripts.
@@ -40,18 +40,22 @@ mechanics instead of general advice.
40
40
  pip install scamscript # or, for development:
41
41
  # git clone https://github.com/sjdsjadjajdjsa/scamscript && cd scamscript && pip install -e ".[dev]"
42
42
 
43
- export API_KEY=... # never committed, never logged
43
+ export API_KEY=...
44
+ export SCAMSCRIPT_BASE_URL=https://openrouter.ai/api/v1
45
+ export SCAMSCRIPT_MODEL=deepseek/deepseek-v4.1-flash-1017
44
46
 
45
47
  scamscript --dry-run analyze --file examples/tech_support_scam.txt # inspect the prompt, spend nothing
46
48
  scamscript analyze --file examples/tech_support_scam.txt
47
49
  ```
48
50
 
49
- Any OpenAI-compatible `/chat/completions` endpoint works. Defaults are Venice with
50
- `deepseek-v4-1-flash-1017-uncensored`; OpenRouter needs only the model slug:
51
+ Any OpenAI-compatible `/chat/completions` endpoint works. There is no baked-in provider or model:
52
+ pass `--base-url` and `--model`, or set `SCAMSCRIPT_BASE_URL` and `SCAMSCRIPT_MODEL`.
51
53
 
52
54
  ```bash
53
55
  scamscript --base-url https://openrouter.ai/api/v1 \
54
56
  --model deepseek/deepseek-v4.1-flash-1017 analyze --file script.txt
57
+
58
+ scamscript --base-url http://localhost:11434/v1 --model llama3.2 analyze --file script.txt
55
59
  ```
56
60
 
57
61
  ## What it produces
@@ -79,7 +83,7 @@ compared against the source, and lines that are paraphrased or simply not there
79
83
  `--strict-quotes` to make a missing line fail the run, and `--no-verify` to skip it.
80
84
 
81
85
  Batch mode ranks a whole directory into one markdown report. A redacted report from the sample
82
- corpus is at [`docs/sample-report.md`](docs/sample-report.md).
86
+ corpus is at [`docs/sample-report.md`](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/docs/sample-report.md).
83
87
 
84
88
  ## Commands
85
89
 
@@ -141,13 +145,13 @@ bounded.
141
145
  ## Install and configure
142
146
 
143
147
  Python 3.12+. `pip install scamscript` for use, `pip install -e ".[dev]"` from a clone for tests.
144
- Release history is in [CHANGELOG.md](CHANGELOG.md).
148
+ Release history is in [CHANGELOG.md](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/CHANGELOG.md).
145
149
 
146
150
  | Setting | Flag | Env | Default |
147
151
  | --- | --- | --- | --- |
148
152
  | API key | `--api-key` | `API_KEY` | none, required unless `--dry-run` |
149
- | Base URL | `--base-url` | `SCAMSCRIPT_BASE_URL` | `https://api.venice.ai/api/v1` |
150
- | Model | `--model` | `SCAMSCRIPT_MODEL` | `deepseek-v4-1-flash-1017-uncensored` |
153
+ | Base URL | `--base-url` | `SCAMSCRIPT_BASE_URL` | none, required |
154
+ | Model | `--model` | `SCAMSCRIPT_MODEL` | none, required |
151
155
  | State dir | `--home` | `SCAMSCRIPT_HOME` | `~/.scamscript` |
152
156
 
153
157
  ## Tests, CI and calibration
@@ -163,12 +167,12 @@ Calibration is evidence, not vibes. `scripts/calibrate.py` measures first-attemp
163
167
  adherence, repair rate, latency, score spread and billed cost against a live endpoint:
164
168
 
165
169
  ```bash
166
- API_KEY=... python scripts/calibrate.py --dir examples --repeat 3 \
167
- --model deepseek/deepseek-v4.1-flash-1017 \
168
- --base-url https://openrouter.ai/api/v1
170
+ API_KEY=... SCAMSCRIPT_BASE_URL=https://openrouter.ai/api/v1 \
171
+ python scripts/calibrate.py --dir examples --repeat 3 \
172
+ --model deepseek/deepseek-v4.1-flash-1017
169
173
  ```
170
174
 
171
- Recorded passes with their numbers are in [`docs/calibration.md`](docs/calibration.md): three rubric
175
+ Recorded passes with their numbers are in [`docs/calibration.md`](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/docs/calibration.md): three rubric
172
176
  repeats, a three-model comparison, an adversarial set and end-case stability runs. The current rubric
173
177
  reaches both ends of the scale, and quote checking flags the invented lines a plain reading would
174
178
  take as evidence.
@@ -176,7 +180,7 @@ take as evidence.
176
180
  The adversarial set lives in `examples/adversarial/` and has its own harness:
177
181
 
178
182
  ```bash
179
- API_KEY=... python scripts/injection_eval.py --model ... --base-url ...
183
+ API_KEY=... SCAMSCRIPT_BASE_URL=... python scripts/injection_eval.py --model ...
180
184
  ```
181
185
 
182
186
  Format held in 7/7 cases; embedded demands were obeyed in 1 of 6. The fence protects the schema, not
@@ -218,5 +222,5 @@ docs/ calibration record, sample report, banner, demo
218
222
  For security research and fraud-awareness training only. Not legal advice, not an attribution
219
223
  tool, no warranty. Keep API keys out of commits; the key is read from the environment.
220
224
 
221
- MIT licensed, see [LICENSE](LICENSE). Contribution notes in [CONTRIBUTING.md](CONTRIBUTING.md),
225
+ MIT licensed, see [LICENSE](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/LICENSE). Contribution notes in [CONTRIBUTING.md](https://github.com/sjdsjadjajdjsa/scamscript/blob/master/CONTRIBUTING.md),
222
226
  vulnerability reports in [SECURITY.md](.github/SECURITY.md).
@@ -4,11 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "scamscript"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Reverse-engineer scam scripts and vishing transcripts into structured counter-playbooks"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
11
- license = { file = "LICENSE" }
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
12
13
  authors = [
13
14
  { name = "scamscript contributors", email = "313729361+sjdsjadjajdjsa@users.noreply.github.com" },
14
15
  ]
@@ -21,7 +21,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
21
21
 
22
22
  from scamscript.analysis import analyze_script # noqa: E402
23
23
  from scamscript.client import ChatClient # noqa: E402
24
- from scamscript.config import DEFAULT_BASE_URL, DEFAULT_MODEL, Settings # noqa: E402
24
+ from scamscript.config import Settings # noqa: E402
25
25
  from scamscript.ingest import collect_scripts # noqa: E402
26
26
  from scamscript.store import RunLogger # noqa: E402
27
27
 
@@ -29,13 +29,15 @@ from scamscript.store import RunLogger # noqa: E402
29
29
  def main(
30
30
  directory: Path = typer.Option(Path("examples"), "--dir", "-d"),
31
31
  repeat: int = typer.Option(2, "--repeat", "-n", min=1),
32
- model: str = typer.Option(DEFAULT_MODEL, "--model", "-m"),
33
- base_url: str = typer.Option(DEFAULT_BASE_URL, "--base-url"),
32
+ model: str = typer.Option("", "--model", "-m", envvar="SCAMSCRIPT_MODEL"),
33
+ base_url: str = typer.Option("", "--base-url", envvar="SCAMSCRIPT_BASE_URL"),
34
34
  prompt_price: float = typer.Option(0.035, help="USD per million prompt tokens."),
35
35
  completion_price: float = typer.Option(0.5, help="USD per million completion tokens."),
36
36
  max_tokens: int = typer.Option(4096, "--max-tokens"),
37
37
  ) -> None:
38
38
  settings = Settings(base_url=base_url, model=model, max_tokens=max_tokens)
39
+ settings.require_base_url()
40
+ settings.require_model()
39
41
  api_key = settings.require_api_key()
40
42
  scripts = collect_scripts(directory)
41
43
  runs = [script for _ in range(repeat) for script in scripts]
@@ -22,7 +22,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
22
22
 
23
23
  from scamscript.analysis import analyze_script # noqa: E402
24
24
  from scamscript.client import ChatClient # noqa: E402
25
- from scamscript.config import DEFAULT_BASE_URL, DEFAULT_MODEL, Settings # noqa: E402
25
+ from scamscript.config import Settings # noqa: E402
26
26
  from scamscript.ingest import ScriptFile # noqa: E402
27
27
  from scamscript.store import RunLogger # noqa: E402
28
28
 
@@ -82,10 +82,12 @@ CASES: dict[str, dict[str, object]] = {
82
82
 
83
83
  def main(
84
84
  directory: Path = typer.Option(Path("examples/adversarial"), "--dir", "-d"),
85
- model: str = typer.Option(DEFAULT_MODEL, "--model", "-m"),
86
- base_url: str = typer.Option(DEFAULT_BASE_URL, "--base-url"),
85
+ model: str = typer.Option("", "--model", "-m", envvar="SCAMSCRIPT_MODEL"),
86
+ base_url: str = typer.Option("", "--base-url", envvar="SCAMSCRIPT_BASE_URL"),
87
87
  ) -> None:
88
88
  settings = Settings(base_url=base_url, model=model)
89
+ settings.require_base_url()
90
+ settings.require_model()
89
91
  api_key = settings.require_api_key()
90
92
  cases = [(name, spec) for name, spec in CASES.items() if (directory / f"{name}.txt").is_file()]
91
93
 
@@ -1,3 +1,3 @@
1
1
  """scamscript: reverse-engineering analyzer for scam call scripts."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
@@ -15,9 +15,7 @@ from scamscript import report as reporting
15
15
  from scamscript.analysis import Outcome, analyze_script
16
16
  from scamscript.client import ChatClient
17
17
  from scamscript.config import (
18
- DEFAULT_BASE_URL,
19
18
  DEFAULT_MAX_TOKENS,
20
- DEFAULT_MODEL,
21
19
  DEFAULT_RETRIES,
22
20
  DEFAULT_TIMEOUT,
23
21
  ConfigError,
@@ -66,19 +64,17 @@ def version_callback(value: bool) -> None:
66
64
  def main(
67
65
  ctx: typer.Context,
68
66
  base_url: str = typer.Option(
69
- DEFAULT_BASE_URL,
67
+ "",
70
68
  "--base-url",
71
69
  envvar="SCAMSCRIPT_BASE_URL",
72
- help="OpenAI-compatible API base URL.",
73
- show_default=True,
70
+ help="OpenAI-compatible API base URL. Falls back to SCAMSCRIPT_BASE_URL.",
74
71
  ),
75
72
  model: str = typer.Option(
76
- DEFAULT_MODEL,
73
+ "",
77
74
  "--model",
78
75
  "-m",
79
76
  envvar="SCAMSCRIPT_MODEL",
80
- help="Model identifier sent to the API.",
81
- show_default=True,
77
+ help="Model identifier sent to the API. Falls back to SCAMSCRIPT_MODEL.",
82
78
  ),
83
79
  api_key: str | None = typer.Option(
84
80
  None,
@@ -219,6 +215,11 @@ def _run(
219
215
  )
220
216
 
221
217
  if state.dry_run:
218
+ if not settings.base_url or not settings.model:
219
+ console.print(
220
+ "[yellow]note[/yellow] --base-url and --model are unset; "
221
+ "the prompt below is complete but unaddressed"
222
+ )
222
223
  for script in scripts:
223
224
  text, counts = _maybe_redact(script.text, settings.redact)
224
225
  messages = prompts.build_messages(script.name, text)
@@ -246,6 +247,8 @@ def _run(
246
247
  return
247
248
 
248
249
  try:
250
+ settings.require_base_url()
251
+ settings.require_model()
249
252
  api_key = settings.require_api_key()
250
253
  except ConfigError as exc:
251
254
  _fail(console, str(exc))
@@ -8,9 +8,7 @@ from typing import Any, Callable
8
8
  import httpx
9
9
 
10
10
  from scamscript.config import (
11
- DEFAULT_BASE_URL,
12
11
  DEFAULT_MAX_TOKENS,
13
- DEFAULT_MODEL,
14
12
  DEFAULT_RETRIES,
15
13
  DEFAULT_TIMEOUT,
16
14
  )
@@ -42,8 +40,8 @@ class ChatClient:
42
40
  def __init__(
43
41
  self,
44
42
  api_key: str,
45
- base_url: str = DEFAULT_BASE_URL,
46
- model: str = DEFAULT_MODEL,
43
+ base_url: str = "",
44
+ model: str = "",
47
45
  timeout: float = DEFAULT_TIMEOUT,
48
46
  retries: int = DEFAULT_RETRIES,
49
47
  max_tokens: int = DEFAULT_MAX_TOKENS,
@@ -64,7 +62,7 @@ class ChatClient:
64
62
  "Authorization": f"Bearer {api_key}",
65
63
  "Content-Type": "application/json",
66
64
  "Accept": "application/json",
67
- "User-Agent": "scamscript/0.2",
65
+ "User-Agent": "scamscript/0.3.0",
68
66
  },
69
67
  )
70
68
 
@@ -5,8 +5,6 @@ from __future__ import annotations
5
5
  import os
6
6
  from dataclasses import dataclass, field
7
7
 
8
- DEFAULT_BASE_URL = "https://api.venice.ai/api/v1"
9
- DEFAULT_MODEL = "deepseek-v4-1-flash-1017-uncensored"
10
8
  DEFAULT_TIMEOUT = 120.0
11
9
  DEFAULT_RETRIES = 2
12
10
  DEFAULT_MAX_TOKENS = 4096
@@ -20,8 +18,8 @@ class ConfigError(Exception):
20
18
 
21
19
  @dataclass(slots=True)
22
20
  class Settings:
23
- base_url: str = DEFAULT_BASE_URL
24
- model: str = DEFAULT_MODEL
21
+ base_url: str = ""
22
+ model: str = ""
25
23
  api_key: str | None = None
26
24
  timeout: float = DEFAULT_TIMEOUT
27
25
  retries: int = DEFAULT_RETRIES
@@ -30,9 +28,8 @@ class Settings:
30
28
  home: str = field(default_factory=lambda: os.environ.get(ENV_HOME, ""))
31
29
 
32
30
  def __post_init__(self) -> None:
33
- self.base_url = (self.base_url or DEFAULT_BASE_URL).rstrip("/")
34
- if not self.model:
35
- self.model = DEFAULT_MODEL
31
+ self.base_url = (self.base_url or "").strip().rstrip("/")
32
+ self.model = (self.model or "").strip()
36
33
  home = self.home or os.path.join(os.path.expanduser("~"), ".scamscript")
37
34
  self.home = home
38
35
 
@@ -42,6 +39,21 @@ class Settings:
42
39
  key = key.strip()
43
40
  return key or None
44
41
 
42
+ def require_base_url(self) -> str:
43
+ if not self.base_url:
44
+ raise ConfigError(
45
+ "missing API base URL: pass --base-url or set the SCAMSCRIPT_BASE_URL "
46
+ "environment variable"
47
+ )
48
+ return self.base_url
49
+
50
+ def require_model(self) -> str:
51
+ if not self.model:
52
+ raise ConfigError(
53
+ "missing model: pass --model or set the SCAMSCRIPT_MODEL environment variable"
54
+ )
55
+ return self.model
56
+
45
57
  def require_api_key(self) -> str:
46
58
  key = self.resolve_api_key()
47
59
  if key is None:
@@ -50,6 +50,8 @@ class FakeClient:
50
50
  @pytest.fixture()
51
51
  def home(tmp_path, monkeypatch):
52
52
  monkeypatch.setenv("SCAMSCRIPT_HOME", str(tmp_path))
53
+ monkeypatch.setenv("SCAMSCRIPT_BASE_URL", "https://example.test/v1")
54
+ monkeypatch.setenv("SCAMSCRIPT_MODEL", "test-model")
53
55
  monkeypatch.delenv("API_KEY", raising=False)
54
56
  return tmp_path
55
57
 
@@ -194,10 +196,38 @@ def test_history_empty(home):
194
196
  assert "no runs logged" in result.stdout
195
197
 
196
198
 
199
+ def test_missing_model(home, tmp_path, monkeypatch):
200
+ monkeypatch.delenv("SCAMSCRIPT_MODEL")
201
+ script = tmp_path / "a.txt"
202
+ script.write_text("CALLER: hello", encoding="utf-8")
203
+ result = runner.invoke(cli.app, ["--api-key", "k", "analyze", "--file", str(script)])
204
+ assert result.exit_code == 1
205
+ assert "missing model" in result.stdout
206
+
207
+
208
+ def test_missing_base_url(home, tmp_path, monkeypatch):
209
+ monkeypatch.delenv("SCAMSCRIPT_BASE_URL")
210
+ script = tmp_path / "a.txt"
211
+ script.write_text("CALLER: hello", encoding="utf-8")
212
+ result = runner.invoke(cli.app, ["--api-key", "k", "analyze", "--file", str(script)])
213
+ assert result.exit_code == 1
214
+ assert "missing API base URL" in result.stdout
215
+
216
+
217
+ def test_dry_run_without_endpoint(home, tmp_path, monkeypatch):
218
+ monkeypatch.delenv("SCAMSCRIPT_MODEL")
219
+ monkeypatch.delenv("SCAMSCRIPT_BASE_URL")
220
+ script = tmp_path / "a.txt"
221
+ script.write_text("CALLER: hello", encoding="utf-8")
222
+ result = runner.invoke(cli.app, ["--dry-run", "analyze", "--file", str(script)])
223
+ assert result.exit_code == 0
224
+ assert "unaddressed" in result.stdout
225
+
226
+
197
227
  def test_version(home):
198
228
  result = runner.invoke(cli.app, ["--version"])
199
229
  assert result.exit_code == 0
200
- assert result.stdout.strip() == "scamscript 0.2.0"
230
+ assert result.stdout.strip() == "scamscript 0.3.0"
201
231
 
202
232
 
203
233
  def test_json_output_is_one_object_per_script(home, tmp_path, fake):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes