agentic-threat-hunting-framework 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-threat-hunting-framework
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Agentic Threat Hunting Framework - Memory and AI for threat hunters
5
5
  Author-email: Sydney Marrone <athf@nebulock.io>
6
6
  Maintainer-email: Sydney Marrone <athf@nebulock.io>
@@ -46,30 +46,23 @@ Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
46
46
  Provides-Extra: docs
47
47
  Requires-Dist: mkdocs>=1.5.0; extra == "docs"
48
48
  Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
49
+ Provides-Extra: similarity
50
+ Requires-Dist: scikit-learn>=1.0.0; extra == "similarity"
49
51
  Dynamic: license-file
50
52
 
51
- <p align="center">
52
- <img src="assets/athf_logo.png" alt="ATHF Logo" width="400"/>
53
- </p>
53
+ # Agentic Threat Hunting Framework (ATHF)
54
54
 
55
- <h1 align="center">Agentic Threat Hunting Framework (ATHF)</h1>
55
+ ![ATHF Logo](https://raw.githubusercontent.com/Nebulock-Inc/agentic-threat-hunting-framework/main/assets/athf_logo.png)
56
56
 
57
- <p align="center">
58
- <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.8%2B-blue" alt="Python Version"></a>
59
- <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
60
- <a href="https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/stargazers"><img src="https://img.shields.io/github/stars/Nebulock-Inc/agentic-threat-hunting-framework?style=social" alt="GitHub stars"></a>
61
- </p>
57
+ [![PyPI version](https://img.shields.io/pypi/v/agentic-threat-hunting-framework)](https://pypi.org/project/agentic-threat-hunting-framework/)
58
+ [![PyPI downloads](https://img.shields.io/pypi/dm/agentic-threat-hunting-framework)](https://pypi.org/project/agentic-threat-hunting-framework/)
59
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org/downloads/)
60
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/LICENSE)
61
+ [![GitHub stars](https://img.shields.io/github/stars/Nebulock-Inc/agentic-threat-hunting-framework?style=social)](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/stargazers)
62
62
 
63
- <p align="center">
64
- <strong><a href="#-quick-start">Quick Start</a></strong> •
65
- <strong><a href="#installation">Installation</a></strong> •
66
- <strong><a href="#documentation">Documentation</a></strong> •
67
- <strong><a href="SHOWCASE.md">Examples</a></strong>
68
- </p>
63
+ **[Quick Start](#-quick-start)** • **[Installation](#installation)** • **[Documentation](#documentation)** • **[Examples](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/SHOWCASE.md)**
69
64
 
70
- <p align="center">
71
- <em>Give your threat hunting program memory and agency.</em>
72
- </p>
65
+ *Give your threat hunting program memory and agency.*
73
66
 
74
67
  The **Agentic Threat Hunting Framework (ATHF)** is the memory and automation layer for your threat hunting program. It gives your hunts structure, persistence, and context - making every past investigation accessible to both humans and AI.
75
68
 
@@ -92,13 +85,13 @@ Even AI tools start from zero every time without access to your environment, you
92
85
 
93
86
  ATHF changes that by giving your hunts structure, persistence, and context.
94
87
 
95
- **Read more:** [docs/why-athf.md](docs/why-athf.md)
88
+ **Read more:** [docs/why-athf.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/why-athf.md)
96
89
 
97
90
  ## The LOCK Pattern
98
91
 
99
92
  Every threat hunt follows the same basic loop: **Learn → Observe → Check → Keep**.
100
93
 
101
- ![The LOCK Pattern](assets/athf_lock.png)
94
+ ![The LOCK Pattern](https://raw.githubusercontent.com/Nebulock-Inc/agentic-threat-hunting-framework/main/assets/athf_lock.png)
102
95
 
103
96
  - **Learn:** Gather context from threat intel, alerts, or anomalies
104
97
  - **Observe:** Form a hypothesis about adversary behavior
@@ -107,7 +100,7 @@ Every threat hunt follows the same basic loop: **Learn → Observe → Check →
107
100
 
108
101
  **Why LOCK?** It's small enough to use and strict enough for agents to interpret. By capturing every hunt in this format, ATHF makes it possible for AI assistants to recall prior work and suggest refined queries based on past results.
109
102
 
110
- **Read more:** [docs/lock-pattern.md](docs/lock-pattern.md)
103
+ **Read more:** [docs/lock-pattern.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/lock-pattern.md)
111
104
 
112
105
  ## The Five Levels of Agentic Hunting
113
106
 
@@ -115,7 +108,7 @@ ATHF defines a simple maturity model. Each level builds on the previous one.
115
108
 
116
109
  **Most teams will live at Levels 1–2. Everything beyond that is optional maturity.**
117
110
 
118
- ![The Five Levels](assets/athf_fivelevels.png)
111
+ ![The Five Levels](https://raw.githubusercontent.com/Nebulock-Inc/agentic-threat-hunting-framework/main/assets/athf_fivelevels.png)
119
112
 
120
113
  | Level | Capability | What You Get |
121
114
  |-------|-----------|--------------|
@@ -130,17 +123,15 @@ ATHF defines a simple maturity model. Each level builds on the previous one.
130
123
  **Level 3:** 2-4 weeks (optional)
131
124
  **Level 4:** 1-3 months (optional)
132
125
 
133
- **Read more:** [docs/maturity-model.md](docs/maturity-model.md)
126
+ **Read more:** [docs/maturity-model.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/maturity-model.md)
134
127
 
135
128
  ## 🚀 Quick Start
136
129
 
137
- ### Option 1: Python CLI (Recommended)
130
+ ### Option 1: Install from PyPI (Recommended)
138
131
 
139
132
  ```bash
140
- # Clone and install from source
141
- git clone https://github.com/Nebulock-Inc/agentic-threat-hunting-framework
142
- cd agentic-threat-hunting-framework
143
- pip install -e .
133
+ # Install ATHF
134
+ pip install agentic-threat-hunting-framework
144
135
 
145
136
  # Initialize your hunt program
146
137
  athf init
@@ -149,7 +140,20 @@ athf init
149
140
  athf hunt new --technique T1003.001 --title "LSASS Credential Dumping"
150
141
  ```
151
142
 
152
- ### Option 2: Pure Markdown (No Installation)
143
+ ### Option 2: Install from Source (Development)
144
+
145
+ ```bash
146
+ # Clone and install from source
147
+ git clone https://github.com/Nebulock-Inc/agentic-threat-hunting-framework
148
+ cd agentic-threat-hunting-framework
149
+ pip install -e .
150
+
151
+ # Initialize and start hunting
152
+ athf init
153
+ athf hunt new --technique T1003.001
154
+ ```
155
+
156
+ ### Option 3: Pure Markdown (No Installation)
153
157
 
154
158
  ```bash
155
159
  # Clone the repository
@@ -165,7 +169,7 @@ cp templates/HUNT_LOCK.md hunts/H-0001.md
165
169
 
166
170
  **Choose your AI assistant:** Claude Code, GitHub Copilot, or Cursor - any tool that can read your repository files.
167
171
 
168
- **Full guide:** [docs/getting-started.md](docs/getting-started.md)
172
+ **Full guide:** [docs/getting-started.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/getting-started.md)
169
173
 
170
174
  ## 🔧 CLI Commands
171
175
 
@@ -206,32 +210,39 @@ athf hunt stats # Show statistics
206
210
  athf hunt coverage # MITRE ATT&CK coverage
207
211
  ```
208
212
 
209
- **Full documentation:** [CLI Reference](docs/CLI_REFERENCE.md)
213
+ **Full documentation:** [CLI Reference](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/CLI_REFERENCE.md)
210
214
 
211
215
  ## 📺 See It In Action
212
216
 
213
- ![ATHF Demo](assets/athf-cli-workflow.gif)
217
+ ![ATHF Demo](https://raw.githubusercontent.com/Nebulock-Inc/agentic-threat-hunting-framework/main/assets/athf-cli-workflow.gif)
214
218
 
215
219
  Watch ATHF in action: initialize a workspace, create hunts, and explore your threat hunting catalog in under 60 seconds.
216
220
 
217
- **[View example hunts →](SHOWCASE.md)**
221
+ **[View example hunts →](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/SHOWCASE.md)**
218
222
 
219
223
  ## Installation
220
224
 
221
225
  ### Prerequisites
222
226
  - Python 3.8-3.13 (for CLI option)
223
- - Git
224
227
  - Your favorite AI code assistant
225
228
 
226
- ### CLI Installation
229
+ ### From PyPI (Recommended)
230
+
231
+ ```bash
232
+ pip install agentic-threat-hunting-framework
233
+ athf init
234
+ ```
235
+
236
+ ### From Source (Development)
227
237
 
228
238
  ```bash
229
239
  git clone https://github.com/Nebulock-Inc/agentic-threat-hunting-framework
230
240
  cd agentic-threat-hunting-framework
231
241
  pip install -e .
242
+ athf init
232
243
  ```
233
244
 
234
- ### Markdown-Only Setup (No CLI)
245
+ ### Markdown-Only Setup (No Installation)
235
246
 
236
247
  ```bash
237
248
  git clone https://github.com/Nebulock-Inc/agentic-threat-hunting-framework
@@ -244,24 +255,24 @@ Start documenting hunts in the `hunts/` directory using the LOCK pattern.
244
255
 
245
256
  ### Core Concepts
246
257
 
247
- - [Why ATHF Exists](docs/why-athf.md) - The problem and solution
248
- - [The LOCK Pattern](docs/lock-pattern.md) - Structure for all hunts
249
- - [Maturity Model](docs/maturity-model.md) - The five levels explained
250
- - [Getting Started](docs/getting-started.md) - Step-by-step onboarding
258
+ - [Why ATHF Exists](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/why-athf.md) - The problem and solution
259
+ - [The LOCK Pattern](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/lock-pattern.md) - Structure for all hunts
260
+ - [Maturity Model](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/maturity-model.md) - The five levels explained
261
+ - [Getting Started](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/getting-started.md) - Step-by-step onboarding
251
262
 
252
263
  ### Level-Specific Guides
253
264
 
254
- - [Level 1: Documented Hunts](docs/maturity-model.md#level-1-documented-hunts)
255
- - [Level 2: Searchable Memory](docs/maturity-model.md#level-2-searchable-memory)
256
- - [Level 3: Generative Capabilities](docs/level4-agentic-workflows.md)
257
- - [Level 4: Agentic Workflows](docs/level4-agentic-workflows.md)
265
+ - [Level 1: Documented Hunts](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/maturity-model.md#level-1-documented-hunts)
266
+ - [Level 2: Searchable Memory](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/maturity-model.md#level-2-searchable-memory)
267
+ - [Level 3: Generative Capabilities](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/level4-agentic-workflows.md)
268
+ - [Level 4: Agentic Workflows](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/level4-agentic-workflows.md)
258
269
 
259
270
  ### Integration & Customization
260
271
 
261
- - [Installation & Development](docs/INSTALL.md) - Setup, fork customization, testing
262
- - [MCP Catalog](integrations/MCP_CATALOG.md) - Available tool integrations
263
- - [Quickstart Guides](integrations/quickstart/) - Setup for specific tools
264
- - [Using ATHF](USING_ATHF.md) - Adoption and customization
272
+ - [Installation & Development](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/INSTALL.md) - Setup, fork customization, testing
273
+ - [MCP Catalog](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/integrations/MCP_CATALOG.md) - Available tool integrations
274
+ - [Quickstart Guides](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/tree/main/integrations/quickstart/) - Setup for specific tools
275
+ - [Using ATHF](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/USING_ATHF.md) - Adoption and customization
265
276
 
266
277
  ## 🎖️ Featured Hunts
267
278
 
@@ -272,7 +283,7 @@ Detected Atomic Stealer collecting Safari cookies via AppleScript.
272
283
 
273
284
  **Key Insight:** Behavior-based detection outperformed signature-based approaches. Process signature validation identified unsigned malware attempting data collection.
274
285
 
275
- [View full hunt →](hunts/H-0001.md) | [See more examples →](SHOWCASE.md)
286
+ [View full hunt →](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/hunts/H-0001.md) | [See more examples →](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/SHOWCASE.md)
276
287
 
277
288
  ## Why This Matters
278
289
 
@@ -290,7 +301,7 @@ When your framework has memory, you stop losing knowledge to turnover or forgott
290
301
 
291
302
  - **GitHub Discussions:** [Ask questions, share hunts](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/discussions)
292
303
  - **Issues:** [Report bugs or request features](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/issues)
293
- - **Adoption Guide:** See [USING_ATHF.md](USING_ATHF.md) for how to use ATHF in your organization
304
+ - **Adoption Guide:** See [USING_ATHF.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/USING_ATHF.md) for how to use ATHF in your organization
294
305
  - **LinkedIn:** [Nebulock Inc.](https://www.linkedin.com/company/nebulock-inc) - Follow for updates
295
306
 
296
307
  ## 📖 Using ATHF
@@ -299,7 +310,7 @@ ATHF is a framework to internalize, not a platform to extend. Fork it, customize
299
310
 
300
311
  **Repository:** [https://github.com/Nebulock-Inc/agentic-threat-hunting-framework](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework)
301
312
 
302
- See [USING_ATHF.md](USING_ATHF.md) for adoption guidance. Your hunts stay yours—sharing back is optional but appreciated ([Discussions](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/discussions)).
313
+ See [USING_ATHF.md](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/USING_ATHF.md) for adoption guidance. Your hunts stay yours—sharing back is optional but appreciated ([Discussions](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/discussions)).
303
314
 
304
315
  The goal is to help every threat hunting team move from ad-hoc memory to structured, agentic capability.
305
316
 
@@ -309,7 +320,7 @@ The goal is to help every threat hunting team move from ad-hoc memory to structu
309
320
 
310
321
  ATHF is designed to be forked and customized for your organization.
311
322
 
312
- **See [docs/INSTALL.md#development--customization](docs/INSTALL.md#development--customization) for:**
323
+ **See [docs/INSTALL.md#development--customization](https://github.com/Nebulock-Inc/agentic-threat-hunting-framework/blob/main/docs/INSTALL.md#development--customization) for:**
313
324
  - Setting up your fork for development
314
325
  - Pre-commit hooks for code quality
315
326
  - Testing and type checking
@@ -0,0 +1,23 @@
1
+ agentic_threat_hunting_framework-0.2.1.dist-info/licenses/LICENSE,sha256=_KObErRfiKoolznt-DF0nJnr3U9Rdh7Z4Ba7G5qqckk,1071
2
+ athf/__init__.py,sha256=OrjZe8P97_BTEkscapnwSsqKSjwXNP9d8-HtGr19Ni0,241
3
+ athf/__version__.py,sha256=JMSRfysx6Kj8rOPSF3-d9x_3-QAfDP47yKOeqdgkH1M,59
4
+ athf/cli.py,sha256=XLNRXEs9kHPH6utJ7_SnzLFcldbGAnACPMTe0xMOkhQ,4492
5
+ athf/commands/__init__.py,sha256=uDyr0bz-agpGO8fraXQl24wuQCxqbeCevZsJ2bDK29s,25
6
+ athf/commands/context.py,sha256=nWETwEqPMTxxkUdsfVwH-K3Td41_EKQkxutdPbbIwos,11908
7
+ athf/commands/env.py,sha256=AisRllJXbyCjK_2ii21qBBmCz9raxhBUemwM7BxqIYg,11859
8
+ athf/commands/hunt.py,sha256=2KORNWAqEvLY-Wc1q-a894g8kOpcqw_iJfnenKJeTDI,23019
9
+ athf/commands/init.py,sha256=L_29fvZF8SZ1BKh2D6NyDuacCC5JXOTezIxdBnnK88E,10941
10
+ athf/commands/investigate.py,sha256=WjwPtafs9bOSu09RC1QW4CVFYJjdn2C96wRa9M_o2PI,24650
11
+ athf/commands/similar.py,sha256=sgCNwI1Ru0lbavNm_3fjOdcJjeRPVD827vGdMF2LMBM,11761
12
+ athf/core/__init__.py,sha256=yG7C8ljx3UW4QZoYvDjUxsWHlbS8M-GLGB7Je7rRfqo,31
13
+ athf/core/attack_matrix.py,sha256=ayr9StkOskgFTsNum8MwTuCd4Z7Rhy5NNCNx1ncLYKs,3232
14
+ athf/core/hunt_manager.py,sha256=5fxGXbtRGfUR8B0E2jb62peSQhwISmim71SZPRrJRr0,11361
15
+ athf/core/hunt_parser.py,sha256=FUj0yyBIcZnaS9aItMImeBDhegQwpkewIwUMNXW_ZWU,5122
16
+ athf/core/investigation_parser.py,sha256=tZnUqrFGLMUif9rayu7hgb6sKBWIvui46siUdDokAAA,6797
17
+ athf/core/template_engine.py,sha256=vNTVhlxIXZpxU7VmQyrqCSt6ORS0IVjAV54TOmUDMTE,5636
18
+ athf/utils/__init__.py,sha256=aEAPI1xnAsowOtc036cCb9ZOek5nrrfevu8PElhbNgk,30
19
+ agentic_threat_hunting_framework-0.2.1.dist-info/METADATA,sha256=WMcuK9M13SlUPyRtud9frS8t4_7lFeRXiwevmicJMgk,15472
20
+ agentic_threat_hunting_framework-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ agentic_threat_hunting_framework-0.2.1.dist-info/entry_points.txt,sha256=GopR2iTiBs-yNMWiUZ2DaFIFglXxWJx1XPjTa3ePtfE,39
22
+ agentic_threat_hunting_framework-0.2.1.dist-info/top_level.txt,sha256=Cxxg6SMLfawDJWBITsciRzq27XV8fiaAor23o9Byoes,5
23
+ agentic_threat_hunting_framework-0.2.1.dist-info/RECORD,,
athf/__version__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Version information for ATHF."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.2.1"
athf/cli.py CHANGED
@@ -6,7 +6,7 @@ import click
6
6
  from rich.console import Console
7
7
 
8
8
  from athf.__version__ import __version__
9
- from athf.commands import hunt, init
9
+ from athf.commands import context, env, hunt, init, investigate, similar
10
10
 
11
11
  console = Console()
12
12
 
@@ -79,6 +79,12 @@ def cli() -> None:
79
79
  # Register command groups
80
80
  cli.add_command(init.init)
81
81
  cli.add_command(hunt.hunt)
82
+ cli.add_command(investigate.investigate)
83
+
84
+ # Phase 1 commands (env, context, similar)
85
+ cli.add_command(env.env)
86
+ cli.add_command(context.context)
87
+ cli.add_command(similar.similar)
82
88
 
83
89
 
84
90
  @cli.command(hidden=True)
@@ -0,0 +1,358 @@
1
+ """Context export command for AI-optimized context loading."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import click
8
+ import yaml
9
+ from rich.console import Console
10
+
11
+ console = Console()
12
+
13
+ CONTEXT_EPILOG = """
14
+ \b
15
+ Examples:
16
+ # Export context for specific hunt
17
+ athf context --hunt H-0013
18
+
19
+ # Export context for all credential access hunts
20
+ athf context --tactic credential-access
21
+
22
+ # Export context for macOS platform hunts
23
+ athf context --platform macos
24
+
25
+ # Export full repository context (large output)
26
+ athf context --full
27
+
28
+ # Export as JSON (default)
29
+ athf context --hunt H-0013 --format json
30
+
31
+ # Export as markdown
32
+ athf context --hunt H-0013 --format markdown
33
+
34
+ \b
35
+ Why This Helps AI:
36
+ • Single tool call instead of 5+ Read operations
37
+ • Pre-filtered, relevant content only
38
+ • Structured format (easier to parse)
39
+ • Token optimization (strips unnecessary formatting)
40
+ • Saves ~2,000 tokens per hunt
41
+ """
42
+
43
+
44
+ @click.command(epilog=CONTEXT_EPILOG)
45
+ @click.option("--hunt", help="Hunt ID to export context for (e.g., H-0013)")
46
+ @click.option(
47
+ "--tactic",
48
+ help="MITRE tactic to filter hunts (e.g., credential-access)",
49
+ )
50
+ @click.option("--platform", help="Platform to filter hunts (e.g., macos, windows, linux)")
51
+ @click.option("--full", is_flag=True, help="Export full repository context (use sparingly)")
52
+ @click.option(
53
+ "--format",
54
+ "output_format",
55
+ type=click.Choice(["json", "markdown", "yaml"]),
56
+ default="json",
57
+ help="Output format (default: json)",
58
+ )
59
+ @click.option("--output", type=click.Path(), help="Output file path (default: stdout)")
60
+ def context(
61
+ hunt: Optional[str],
62
+ tactic: Optional[str],
63
+ platform: Optional[str],
64
+ full: bool,
65
+ output_format: str,
66
+ output: Optional[str],
67
+ ) -> None:
68
+ """Export AI-optimized context bundle.
69
+
70
+ Combines relevant files into single structured output:
71
+ - environment.md (tech stack, data sources)
72
+ - hunts/INDEX.md (hunt metadata index)
73
+ - Hunt files (filtered by hunt ID, tactic, or platform)
74
+ - Domain knowledge (if relevant)
75
+
76
+ \b
77
+ Use Cases:
78
+ • AI assistants: Reduce context-loading from ~5 tool calls to 1
79
+ • Token optimization: Pre-filtered, structured content only
80
+ • Hunt planning: Get all relevant context in one shot
81
+ • Query generation: Include past hunt lessons and data sources
82
+
83
+ \b
84
+ Token Savings:
85
+ • Without context: ~5 Read operations, ~3,000 tokens
86
+ • With context: 1 command, ~1,000 tokens
87
+ • Savings: ~2,000 tokens per hunt (~$0.03 per hunt)
88
+ """
89
+ # Validate mutually exclusive options
90
+ exclusive_options = sum([bool(hunt), bool(tactic), bool(platform), full])
91
+ if exclusive_options == 0:
92
+ console.print("[red]Error: Must specify one of: --hunt, --tactic, --platform, or --full[/red]")
93
+ console.print("\n[dim]Examples:[/dim]")
94
+ console.print(" athf context --hunt H-0013")
95
+ console.print(" athf context --tactic credential-access")
96
+ console.print(" athf context --platform macos")
97
+ raise click.Abort()
98
+
99
+ if exclusive_options > 1:
100
+ console.print("[red]Error: Only one filter option allowed at a time[/red]")
101
+ raise click.Abort()
102
+
103
+ # Build context bundle
104
+ context_data = _build_context(hunt=hunt, tactic=tactic, platform=platform, full=full)
105
+
106
+ # Format output
107
+ if output_format == "json":
108
+ # Use ensure_ascii=True to force proper escaping of all special characters
109
+ # This fixes issues with unescaped control characters and newlines
110
+ formatted_output = json.dumps(context_data, indent=2, ensure_ascii=True)
111
+ elif output_format == "yaml":
112
+ formatted_output = yaml.dump(context_data, default_flow_style=False, sort_keys=False, allow_unicode=True)
113
+ else: # markdown
114
+ formatted_output = _format_as_markdown(context_data)
115
+
116
+ # Write to file or stdout
117
+ if output:
118
+ Path(output).write_text(formatted_output, encoding='utf-8')
119
+ console.print(f"[green]✅ Context exported to: {output}[/green]")
120
+ else:
121
+ # Use plain print() for JSON/YAML to avoid Rich formatting issues
122
+ if output_format in ("json", "yaml"):
123
+ print(formatted_output)
124
+ else:
125
+ console.print(formatted_output)
126
+
127
+
128
+ def _build_context(
129
+ hunt: Optional[str] = None,
130
+ tactic: Optional[str] = None,
131
+ platform: Optional[str] = None,
132
+ full: bool = False,
133
+ ) -> Dict[str, Any]:
134
+ """Build context bundle based on filters."""
135
+ context: Dict[str, Any] = {
136
+ "metadata": {
137
+ "generated_by": "athf context",
138
+ "filters": {
139
+ "hunt": hunt,
140
+ "tactic": tactic,
141
+ "platform": platform,
142
+ "full": full,
143
+ },
144
+ },
145
+ "environment": None,
146
+ "hunt_index": None,
147
+ "hunts": [],
148
+ "domain_knowledge": [],
149
+ }
150
+
151
+ # Always include environment.md
152
+ env_path = Path("environment.md")
153
+ if env_path.exists():
154
+ context["environment"] = _read_and_optimize(env_path)
155
+
156
+ # Always include hunts/INDEX.md
157
+ index_path = Path("hunts/INDEX.md")
158
+ if index_path.exists():
159
+ context["hunt_index"] = _read_and_optimize(index_path)
160
+
161
+ # Load hunts based on filter
162
+ if hunt:
163
+ hunt_files = [Path(f"hunts/{hunt}.md")]
164
+ elif tactic:
165
+ hunt_files = _find_hunts_by_tactic(tactic)
166
+ elif platform:
167
+ hunt_files = _find_hunts_by_platform(platform)
168
+ elif full:
169
+ hunt_files = list(Path("hunts").glob("H-*.md"))
170
+ else:
171
+ hunt_files = []
172
+
173
+ # Load hunt content
174
+ for hunt_file in hunt_files:
175
+ if hunt_file.exists():
176
+ context["hunts"].append(
177
+ {
178
+ "hunt_id": hunt_file.stem,
179
+ "content": _read_and_optimize(hunt_file),
180
+ }
181
+ )
182
+
183
+ # Load relevant domain knowledge
184
+ if tactic or full:
185
+ domain_files = _get_relevant_domain_files(tactic)
186
+ for domain_file in domain_files:
187
+ if domain_file.exists():
188
+ context["domain_knowledge"].append(
189
+ {
190
+ "file": str(domain_file),
191
+ "content": _read_and_optimize(domain_file),
192
+ }
193
+ )
194
+
195
+ return context
196
+
197
+
198
+ def _read_and_optimize(file_path: Path) -> str:
199
+ """Read file and optimize for token efficiency."""
200
+ content = file_path.read_text()
201
+
202
+ # First pass: Remove all control characters except tabs and newlines
203
+ # Control characters are U+0000 through U+001F (0-31), except tab (9), LF (10), CR (13)
204
+ cleaned_content = "".join(
205
+ char for char in content
206
+ if ord(char) >= 32 or char in "\t\n\r"
207
+ )
208
+
209
+ # Token optimization:
210
+ # 1. Strip excessive whitespace (but preserve single newlines)
211
+ lines = cleaned_content.split("\n")
212
+ optimized_lines = []
213
+ prev_empty = False
214
+
215
+ for line in lines:
216
+ stripped = line.strip()
217
+ if not stripped:
218
+ if not prev_empty:
219
+ optimized_lines.append("")
220
+ prev_empty = True
221
+ else:
222
+ optimized_lines.append(line.rstrip())
223
+ prev_empty = False
224
+
225
+ return "\n".join(optimized_lines)
226
+
227
+
228
+ def _find_hunts_by_tactic(tactic: str) -> List[Path]:
229
+ """Find hunt files matching MITRE tactic."""
230
+ hunts_dir = Path("hunts")
231
+ matching_hunts = []
232
+
233
+ # Normalize tactic name (e.g., "credential-access" -> "credential access")
234
+ normalized_tactic = tactic.replace("-", " ").lower()
235
+
236
+ for hunt_file in hunts_dir.glob("H-*.md"):
237
+ content = hunt_file.read_text()
238
+
239
+ # Check YAML frontmatter for tactics field
240
+ if content.startswith("---"):
241
+ try:
242
+ # Extract YAML frontmatter
243
+ yaml_end = content.find("---", 3)
244
+ if yaml_end > 0:
245
+ frontmatter = content[3:yaml_end]
246
+ metadata = yaml.safe_load(frontmatter)
247
+
248
+ if metadata and "tactics" in metadata:
249
+ hunt_tactics = [t.lower().replace("-", " ") for t in metadata["tactics"]]
250
+ if normalized_tactic in hunt_tactics:
251
+ matching_hunts.append(hunt_file)
252
+ except yaml.YAMLError:
253
+ continue
254
+
255
+ return matching_hunts
256
+
257
+
258
+ def _find_hunts_by_platform(platform: str) -> List[Path]:
259
+ """Find hunt files matching platform."""
260
+ hunts_dir = Path("hunts")
261
+ matching_hunts = []
262
+
263
+ normalized_platform = platform.lower()
264
+
265
+ for hunt_file in hunts_dir.glob("H-*.md"):
266
+ content = hunt_file.read_text()
267
+
268
+ # Check YAML frontmatter for platform field
269
+ if content.startswith("---"):
270
+ try:
271
+ yaml_end = content.find("---", 3)
272
+ if yaml_end > 0:
273
+ frontmatter = content[3:yaml_end]
274
+ metadata = yaml.safe_load(frontmatter)
275
+
276
+ if metadata and "platform" in metadata:
277
+ hunt_platforms = [p.lower() for p in metadata["platform"]]
278
+ if normalized_platform in hunt_platforms:
279
+ matching_hunts.append(hunt_file)
280
+ except yaml.YAMLError:
281
+ continue
282
+
283
+ return matching_hunts
284
+
285
+
286
+ def _get_relevant_domain_files(tactic: Optional[str] = None) -> List[Path]:
287
+ """Get relevant domain knowledge files based on tactic."""
288
+ domain_files = []
289
+
290
+ # Always include core hunting knowledge
291
+ domain_files.append(Path("knowledge/hunting-knowledge.md"))
292
+
293
+ # Add tactic-specific domain files
294
+ if tactic:
295
+ tactic_lower = tactic.lower().replace("-", " ")
296
+
297
+ # Map tactics to domain files
298
+ tactic_domain_map = {
299
+ "credential access": [Path("knowledge/domains/iam-security.md")],
300
+ "persistence": [Path("knowledge/domains/endpoint-security.md")],
301
+ "privilege escalation": [Path("knowledge/domains/endpoint-security.md")],
302
+ "defense evasion": [Path("knowledge/domains/endpoint-security.md")],
303
+ "execution": [Path("knowledge/domains/endpoint-security.md")],
304
+ "initial access": [
305
+ Path("knowledge/domains/endpoint-security.md"),
306
+ Path("knowledge/domains/iam-security.md"),
307
+ ],
308
+ "collection": [Path("knowledge/domains/insider-threat.md")],
309
+ "exfiltration": [Path("knowledge/domains/insider-threat.md")],
310
+ "impact": [Path("knowledge/domains/insider-threat.md")],
311
+ }
312
+
313
+ if tactic_lower in tactic_domain_map:
314
+ domain_files.extend(tactic_domain_map[tactic_lower])
315
+
316
+ return list(set(domain_files)) # Remove duplicates
317
+
318
+
319
+ def _format_as_markdown(context_data: Dict[str, Any]) -> str:
320
+ """Format context data as markdown."""
321
+ md = "# ATHF Context Export\n\n"
322
+
323
+ # Metadata
324
+ filters = context_data["metadata"]["filters"]
325
+ active_filters = [f"{k}={v}" for k, v in filters.items() if v]
326
+ md += f"**Filters:** {', '.join(active_filters)}\n\n"
327
+
328
+ md += "---\n\n"
329
+
330
+ # Environment
331
+ if context_data.get("environment"):
332
+ md += "## Environment\n\n"
333
+ md += context_data["environment"]
334
+ md += "\n\n---\n\n"
335
+
336
+ # Hunt Index
337
+ if context_data.get("hunt_index"):
338
+ md += "## Hunt Index\n\n"
339
+ md += context_data["hunt_index"]
340
+ md += "\n\n---\n\n"
341
+
342
+ # Hunts
343
+ if context_data.get("hunts"):
344
+ md += "## Hunts\n\n"
345
+ for hunt in context_data["hunts"]:
346
+ md += f"### {hunt['hunt_id']}\n\n"
347
+ md += hunt["content"]
348
+ md += "\n\n---\n\n"
349
+
350
+ # Domain Knowledge
351
+ if context_data.get("domain_knowledge"):
352
+ md += "## Domain Knowledge\n\n"
353
+ for domain in context_data["domain_knowledge"]:
354
+ md += f"### {domain['file']}\n\n"
355
+ md += domain["content"]
356
+ md += "\n\n---\n\n"
357
+
358
+ return md