clearfront 2.5.0__tar.gz → 2.6.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 (99) hide show
  1. {clearfront-2.5.0 → clearfront-2.6.0}/PKG-INFO +28 -28
  2. {clearfront-2.5.0 → clearfront-2.6.0}/README.md +1 -1
  3. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/agent.py +15 -1
  4. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/mcp_server.py +1 -1
  5. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/web/index.html +58 -5
  6. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/web_server.py +68 -12
  7. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront.egg-info/PKG-INFO +28 -28
  8. clearfront-2.6.0/clearfront.egg-info/requires.txt +53 -0
  9. {clearfront-2.5.0 → clearfront-2.6.0}/pyproject.toml +28 -28
  10. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_web_server.py +51 -0
  11. clearfront-2.5.0/clearfront.egg-info/requires.txt +0 -53
  12. {clearfront-2.5.0 → clearfront-2.6.0}/LICENSE +0 -0
  13. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/__init__.py +0 -0
  14. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/brightdata.py +0 -0
  15. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/cli.py +0 -0
  16. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/correlation.py +0 -0
  17. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/corroboration.py +0 -0
  18. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/exceptions.py +0 -0
  19. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/extractors.py +0 -0
  20. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/json_output.py +0 -0
  21. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/multi_target.py +0 -0
  22. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/pdf_report.py +0 -0
  23. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/pivot.py +0 -0
  24. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/prompts.py +0 -0
  25. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/regexes.py +0 -0
  26. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/repl.py +0 -0
  27. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/serp.py +0 -0
  28. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/session_history.py +0 -0
  29. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/sponsors.py +0 -0
  30. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tool_cache.py +0 -0
  31. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/__init__.py +0 -0
  32. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/data/NOTICE +0 -0
  33. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/data/wmn-data-unique.json +0 -0
  34. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/exceptions.py +0 -0
  35. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/generate_dorks.py +0 -0
  36. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/scrape_url.py +0 -0
  37. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_abuseipdb.py +0 -0
  38. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_breach.py +0 -0
  39. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_censys.py +0 -0
  40. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_crt.py +0 -0
  41. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_crypto.py +0 -0
  42. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_dns.py +0 -0
  43. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_domain.py +0 -0
  44. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_dorks_live.py +0 -0
  45. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_email.py +0 -0
  46. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_emailrep.py +0 -0
  47. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_exif.py +0 -0
  48. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_exposure.py +0 -0
  49. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_footprint.py +0 -0
  50. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_github.py +0 -0
  51. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_gravatar.py +0 -0
  52. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_greynoise.py +0 -0
  53. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_harvester.py +0 -0
  54. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_hudsonrock.py +0 -0
  55. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_ip.py +0 -0
  56. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_ip2location.py +0 -0
  57. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_maigret.py +0 -0
  58. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_paste.py +0 -0
  59. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_phone.py +0 -0
  60. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_shodan.py +0 -0
  61. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_username.py +0 -0
  62. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_virustotal.py +0 -0
  63. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_wayback.py +0 -0
  64. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/search_whois.py +0 -0
  65. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/tools/whatsmyname.py +0 -0
  66. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront/utils.py +0 -0
  67. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront.egg-info/SOURCES.txt +0 -0
  68. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront.egg-info/dependency_links.txt +0 -0
  69. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront.egg-info/entry_points.txt +0 -0
  70. {clearfront-2.5.0 → clearfront-2.6.0}/clearfront.egg-info/top_level.txt +0 -0
  71. {clearfront-2.5.0 → clearfront-2.6.0}/setup.cfg +0 -0
  72. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_authorized_use.py +0 -0
  73. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_brightdata.py +0 -0
  74. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_cli_openai.py +0 -0
  75. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_cli_output.py +0 -0
  76. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_corroboration.py +0 -0
  77. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_crt.py +0 -0
  78. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_dns.py +0 -0
  79. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_exif.py +0 -0
  80. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_footprint.py +0 -0
  81. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_github_code_search.py +0 -0
  82. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_graph_export.py +0 -0
  83. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_hudsonrock.py +0 -0
  84. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_json_export.py +0 -0
  85. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_mcp_authorized_use.py +0 -0
  86. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_openai_agent.py +0 -0
  87. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_pivot_freetier.py +0 -0
  88. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_regexes.py +0 -0
  89. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_repl_first_run.py +0 -0
  90. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_report_export.py +0 -0
  91. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_security.py +0 -0
  92. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_serp.py +0 -0
  93. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_tool_cache.py +0 -0
  94. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_tool_parity.py +0 -0
  95. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_tools.py +0 -0
  96. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_v2140.py +0 -0
  97. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_v240.py +0 -0
  98. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_wayback_greynoise.py +0 -0
  99. {clearfront-2.5.0 → clearfront-2.6.0}/tests/test_web_server_openai.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clearfront
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: AI-powered OSINT agent, MCP server, and CLI. Interactive REPL + 30 tools. Anthropic Claude or local Ollama. For authorized security research use only.
5
5
  Author: Scott Martin Anderson
6
6
  License-Expression: MIT
@@ -23,50 +23,50 @@ Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: mcp>=1.28.1
26
- Requires-Dist: anthropic>=0.40.0
27
- Requires-Dist: requests>=2.31.0
26
+ Requires-Dist: anthropic>=0.116.0
27
+ Requires-Dist: requests>=2.34.2
28
28
  Requires-Dist: python-whois>=0.9.6
29
- Requires-Dist: rich>=13.0.0
30
- Requires-Dist: prompt_toolkit>=3.0.0
31
- Requires-Dist: python-dotenv>=1.0.0
29
+ Requires-Dist: rich>=15.0.0
30
+ Requires-Dist: prompt_toolkit>=3.0.52
31
+ Requires-Dist: python-dotenv>=1.2.2
32
32
  Requires-Dist: aiohttp>=3.14.1
33
33
  Requires-Dist: dnspython>=2.8.0
34
- Requires-Dist: ddgs>=9.0.0
34
+ Requires-Dist: ddgs>=9.14.4
35
35
  Requires-Dist: fastapi>=0.139.0
36
- Requires-Dist: uvicorn[standard]>=0.29.0
37
- Requires-Dist: httpx>=0.27.0
36
+ Requires-Dist: uvicorn[standard]>=0.51.0
37
+ Requires-Dist: httpx>=0.28.1
38
38
  Provides-Extra: dev
39
- Requires-Dist: pytest>=7.0; extra == "dev"
40
- Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
39
+ Requires-Dist: pytest>=9.1.1; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=1.4.0; extra == "dev"
41
41
  Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
42
42
  Requires-Dist: pytest-freezegun>=0.4.2; extra == "dev"
43
- Requires-Dist: ruff>=0.1.0; extra == "dev"
43
+ Requires-Dist: ruff>=0.15.20; extra == "dev"
44
44
  Provides-Extra: shodan
45
45
  Requires-Dist: shodan>=1.31.0; extra == "shodan"
46
46
  Provides-Extra: ollama
47
- Requires-Dist: ollama>=0.3.0; extra == "ollama"
47
+ Requires-Dist: ollama>=0.6.2; extra == "ollama"
48
48
  Provides-Extra: openai
49
- Requires-Dist: openai>=1.40.0; extra == "openai"
49
+ Requires-Dist: openai>=2.44.0; extra == "openai"
50
50
  Provides-Extra: pdf
51
- Requires-Dist: reportlab>=4.0.0; extra == "pdf"
51
+ Requires-Dist: reportlab>=5.0.0; extra == "pdf"
52
52
  Provides-Extra: censys
53
- Requires-Dist: censys-platform>=0.14.0; extra == "censys"
53
+ Requires-Dist: censys-platform>=0.15.1; extra == "censys"
54
54
  Provides-Extra: web
55
55
  Requires-Dist: fastapi>=0.139.0; extra == "web"
56
- Requires-Dist: uvicorn[standard]>=0.29.0; extra == "web"
57
- Requires-Dist: sse-starlette>=1.8.0; extra == "web"
58
- Requires-Dist: python-dotenv>=1.0.0; extra == "web"
59
- Requires-Dist: anthropic>=0.25.0; extra == "web"
60
- Requires-Dist: playwright>=1.40.0; extra == "web"
56
+ Requires-Dist: uvicorn[standard]>=0.51.0; extra == "web"
57
+ Requires-Dist: sse-starlette>=3.4.5; extra == "web"
58
+ Requires-Dist: python-dotenv>=1.2.2; extra == "web"
59
+ Requires-Dist: anthropic>=0.116.0; extra == "web"
60
+ Requires-Dist: playwright>=1.61.0; extra == "web"
61
61
  Provides-Extra: all
62
62
  Requires-Dist: shodan>=1.31.0; extra == "all"
63
- Requires-Dist: ollama>=0.3.0; extra == "all"
64
- Requires-Dist: openai>=1.40.0; extra == "all"
65
- Requires-Dist: reportlab>=4.0.0; extra == "all"
66
- Requires-Dist: censys-platform>=0.14.0; extra == "all"
63
+ Requires-Dist: ollama>=0.6.2; extra == "all"
64
+ Requires-Dist: openai>=2.44.0; extra == "all"
65
+ Requires-Dist: reportlab>=5.0.0; extra == "all"
66
+ Requires-Dist: censys-platform>=0.15.1; extra == "all"
67
67
  Requires-Dist: fastapi>=0.139.0; extra == "all"
68
- Requires-Dist: uvicorn[standard]>=0.29.0; extra == "all"
69
- Requires-Dist: sse-starlette>=1.8.0; extra == "all"
68
+ Requires-Dist: uvicorn[standard]>=0.51.0; extra == "all"
69
+ Requires-Dist: sse-starlette>=3.4.5; extra == "all"
70
70
  Dynamic: license-file
71
71
 
72
72
  <div align="center">
@@ -81,7 +81,7 @@ Dynamic: license-file
81
81
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue?style=flat-square)](https://www.python.org/)
82
82
  [![License MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
83
83
  [![MCP](https://img.shields.io/badge/protocol-MCP-blueviolet?style=flat-square)](https://modelcontextprotocol.io/)
84
- [![Version](https://img.shields.io/badge/version-2.5.0-informational?style=flat-square)](CHANGELOG.md)
84
+ [![Version](https://img.shields.io/badge/version-2.6.0-informational?style=flat-square)](CHANGELOG.md)
85
85
 
86
86
  **[clearfront.sh](https://clearfront.sh)** · [Disclaimer](./DISCLAIMER.md)
87
87
 
@@ -10,7 +10,7 @@
10
10
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue?style=flat-square)](https://www.python.org/)
11
11
  [![License MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
12
12
  [![MCP](https://img.shields.io/badge/protocol-MCP-blueviolet?style=flat-square)](https://modelcontextprotocol.io/)
13
- [![Version](https://img.shields.io/badge/version-2.5.0-informational?style=flat-square)](CHANGELOG.md)
13
+ [![Version](https://img.shields.io/badge/version-2.6.0-informational?style=flat-square)](CHANGELOG.md)
14
14
 
15
15
  **[clearfront.sh](https://clearfront.sh)** · [Disclaimer](./DISCLAIMER.md)
16
16
 
@@ -827,12 +827,26 @@ class OISAgent:
827
827
  )
828
828
  try:
829
829
  while True:
830
+ # Prompt caching: a breakpoint on the (single) system block caches the
831
+ # system prompt + all tool definitions together (tools render before
832
+ # system), so that large stable prefix is re-billed at ~0.1x on every
833
+ # round instead of full price. Top-level cache_control auto-places a
834
+ # second breakpoint on the last message block, so the growing tool-output
835
+ # history is cached incrementally across rounds. Caching changes billing
836
+ # only; the model sees identical content and produces identical output.
830
837
  response = await self.client.messages.create(
831
838
  model=self.model,
832
839
  max_tokens=_MAX_TOKENS,
833
- system=SYSTEM_PROMPT,
840
+ system=[
841
+ {
842
+ "type": "text",
843
+ "text": SYSTEM_PROMPT,
844
+ "cache_control": {"type": "ephemeral"},
845
+ }
846
+ ],
834
847
  tools=TOOL_DEFINITIONS, # type: ignore[arg-type]
835
848
  messages=ctx.messages, # type: ignore[arg-type]
849
+ cache_control={"type": "ephemeral"},
836
850
  )
837
851
  if response.stop_reason == "end_turn":
838
852
  text = _extract_first_text(response.content)
@@ -1,6 +1,6 @@
1
1
  # clearfront/mcp_server.py
2
2
  """
3
- Clearfront MCP Server, v2.5.0
3
+ Clearfront MCP Server, v2.6.0
4
4
 
5
5
  Exposes all 30 OSINT tool capabilities plus multi-target investigation
6
6
  to MCP-compliant AI clients over standard I/O. Tools include:
@@ -514,15 +514,51 @@
514
514
 
515
515
  <!-- right: backend status readout + controls -->
516
516
  <div class="flex items-center gap-1.5 flex-none">
517
+ <!-- sweep-depth selector: no-noun chip, opens a level menu whose footer explains the cost -->
518
+ <div class="relative hidden sm:block" @keydown.escape.window="depthOpen = false">
519
+ <button @click="depthOpen = !depthOpen"
520
+ :aria-expanded="depthOpen"
521
+ aria-haspopup="true"
522
+ aria-label="Sweep depth"
523
+ title="Sweep depth: how far each investigation reaches. Trades time and API budget, never analyst quality."
524
+ class="flex items-center gap-1.5 font-mono text-xs px-2.5 py-1 rounded border border-app text-primary bg-transparent hover:border-strong transition-colors cursor-pointer whitespace-nowrap">
525
+ <span x-text="depthCurrent().name"></span>
526
+ <svg width="9" height="9" viewBox="0 0 10 10" fill="none" stroke="currentColor"
527
+ class="transition-transform" :class="depthOpen && 'rotate-180'">
528
+ <path d="M2.5 4 L5 6.5 L7.5 4" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
529
+ </svg>
530
+ </button>
531
+ <div x-show="depthOpen" x-cloak
532
+ x-transition:enter="transition ease-out duration-100" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
533
+ @click.outside="depthOpen = false" @mouseleave="depthHover = null"
534
+ role="radiogroup" aria-label="Sweep depth"
535
+ class="absolute right-0 mt-2 w-60 p-1.5 rounded-lg border border-app bg-surface z-50"
536
+ style="box-shadow:0 8px 24px rgba(0,0,0,0.28);">
537
+ <template x-for="l in depthLevels" :key="l.v">
538
+ <button @click="setDepth(l.v)" @mouseenter="depthHover = l.v" @focus="depthHover = l.v"
539
+ role="radio" :aria-checked="settings.depth === l.v"
540
+ class="flex items-center justify-between gap-2 w-full text-left font-mono text-xs px-2.5 py-2 rounded transition-colors"
541
+ :class="settings.depth === l.v ? 'bg-accent text-bg' : 'text-secondary hover:text-primary'">
542
+ <span x-text="l.name"></span>
543
+ <svg x-show="settings.depth === l.v" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor">
544
+ <path d="M2.5 6.5 L4.8 8.8 L9.5 3.5" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
545
+ </svg>
546
+ </button>
547
+ </template>
548
+ <div class="mt-1 pt-2 px-2 border-t border-app text-secondary"
549
+ style="font-size:12px;line-height:1.42;" x-text="depthExplained().desc"></div>
550
+ </div>
551
+ </div>
552
+ <div class="hidden sm:block w-px h-5 bg-border mx-0.5"></div>
517
553
  <!-- backend status readout -->
518
554
  <button @click="showSettings = true"
519
555
  :title="aiBackend === 'none' ? 'No AI backend configured, click to set up' : 'AI backend, click to change'"
520
556
  class="hidden sm:flex items-center gap-2 font-mono text-xs px-2.5 py-1 rounded border border-app text-secondary bg-transparent hover:text-primary hover:border-strong transition-colors cursor-pointer whitespace-nowrap">
521
557
  <span class="w-1.5 h-1.5 rounded-full flex-none"
522
- :style="aiBackend === 'none' ? 'background-color:var(--red)' : 'background-color:var(--text-muted)'"></span>
523
- <span x-show="aiBackend === 'claude'" x-text="claudeModelLabel"></span>
524
- <span x-show="aiBackend === 'ollama'">Ollama <span x-text="settings.ollamaModel"></span></span>
525
- <span x-show="aiBackend === 'openai'">OpenAI <span x-text="settings.openaiModel || 'model'"></span></span>
558
+ :style="aiBackend === 'none' ? 'background-color:var(--red)' : 'background-color:var(--accent)'"></span>
559
+ <span x-show="aiBackend !== 'none' && settings.chatModel === 'claude'" x-text="claudeModelLabel"></span>
560
+ <span x-show="aiBackend !== 'none' && settings.chatModel === 'ollama'">Ollama <span x-text="settings.ollamaModel || 'model'"></span></span>
561
+ <span x-show="aiBackend !== 'none' && settings.chatModel === 'openai'">OpenAI <span x-text="settings.openaiModel || 'model'"></span></span>
526
562
  <span x-show="aiBackend === 'none'">Not configured</span>
527
563
  </button>
528
564
  <div class="hidden sm:block w-px h-5 bg-border mx-0.5"></div>
@@ -884,7 +920,7 @@ OPEN-SOURCE INTELLIGENCE AUTHORIZED USE · v
884
920
  <script>
885
921
  document.addEventListener('alpine:init', () => {
886
922
  Alpine.data('clearfront', () => ({
887
- version: '2.5.0',
923
+ version: '2.6.0',
888
924
  booting: false,
889
925
  bootLines: [],
890
926
  bootAnim: false,
@@ -919,6 +955,16 @@ document.addEventListener('alpine:init', () => {
919
955
  setupData: {},
920
956
  isLight: false,
921
957
 
958
+ // Sweep depth: header control for how far each investigation fans out. Trades
959
+ // collection breadth (tool rounds), never analyst quality. Persisted in settings.
960
+ depthOpen: false,
961
+ depthHover: null,
962
+ depthLevels: [
963
+ { v: 'faster', name: 'Faster', desc: 'Checks fewer sources for a quick sweep. Finishes fast and uses less of your API budget.' },
964
+ { v: 'balanced', name: 'Balanced', desc: 'Covers the main sources. A middle ground on time and cost.' },
965
+ { v: 'deeper', name: 'Deeper', desc: 'Follows every lead for the most complete map, but takes longer and uses more of your API budget.' },
966
+ ],
967
+
922
968
  settings: {
923
969
  chatModel: 'claude',
924
970
  ollamaModel: 'llama3.2',
@@ -926,6 +972,7 @@ document.addEventListener('alpine:init', () => {
926
972
  openaiBaseUrl: '',
927
973
  openaiModel: '',
928
974
  openaiApiKey: '',
975
+ depth: 'deeper',
929
976
  },
930
977
 
931
978
  // one-click self-checks (no input needed), the thesis-core actions
@@ -1192,6 +1239,11 @@ document.addEventListener('alpine:init', () => {
1192
1239
  try { localStorage.setItem('clearfront_settings', JSON.stringify(this.settings)); } catch {}
1193
1240
  },
1194
1241
 
1242
+ // sweep-depth control
1243
+ depthCurrent() { return this.depthLevels.find(l => l.v === this.settings.depth) || this.depthLevels[2]; },
1244
+ depthExplained() { return this.depthLevels.find(l => l.v === (this.depthHover || this.settings.depth)) || this.depthCurrent(); },
1245
+ setDepth(v) { this.settings.depth = v; this.saveLocalSettings(); this.depthOpen = false; },
1246
+
1195
1247
  // health / tools
1196
1248
  async fetchHealth() {
1197
1249
  try {
@@ -1281,6 +1333,7 @@ document.addEventListener('alpine:init', () => {
1281
1333
  openai_base_url: this.settings.openaiBaseUrl,
1282
1334
  openai_model: this.settings.openaiModel,
1283
1335
  openai_api_key: this.settings.openaiApiKey,
1336
+ depth: this.settings.depth,
1284
1337
  };
1285
1338
 
1286
1339
  let fullText = '';
@@ -670,6 +670,7 @@ class ChatRequest(BaseModel):
670
670
  message: str
671
671
  history: list[dict] = []
672
672
  model: str = "claude"
673
+ depth: str = "deeper"
673
674
  ollama_model: str = "llama3.2"
674
675
  ollama_host: str = "http://localhost:11434"
675
676
  openai_base_url: str = ""
@@ -800,7 +801,64 @@ _GRAPH_SYSTEM_PROMPT = (
800
801
  )
801
802
 
802
803
 
803
- async def _stream_claude(messages: list[dict]) -> AsyncIterator[dict]:
804
+ # ---------------------------------------------------------------------------
805
+ # Sweep depth: how far the analyst fans out per investigation. It trades collection
806
+ # breadth (number of tool rounds) and the matching enrichment instruction, never the
807
+ # analyst's reasoning or output quality. "deeper" is the full default fan-out and is
808
+ # byte-identical to the historical prompt (no mode preamble, unchanged enrichment
809
+ # clause); "balanced" and "faster" run fewer rounds with a softened enrichment clause
810
+ # so the report reads deliberate, not truncated at the cap. The Deeper ceiling stays
811
+ # env-tunable via OIS_MAX_TOOL_ROUNDS; the lighter levels are capped relative to it.
812
+ # ---------------------------------------------------------------------------
813
+ _GRAPH_SUFFIX = (
814
+ "Do not emit any graph block yourself; the evidence graph is generated separately "
815
+ "after your report."
816
+ )
817
+ _DEPTH_ENRICH = {
818
+ "deeper": (
819
+ "- Enrich aggressively before finalising. The goal is a complete map of how the target's "
820
+ "footprint connects, not a minimal answer. Every time a pivot surfaces (an email, domain, "
821
+ "company, real name, phone, or an additional handle), expand it with the applicable tools, "
822
+ "then expand the new entities those reveal, chaining outward until the tool budget is "
823
+ "reached. Do not stop at one or two pivots. The more real, connected entities you surface, "
824
+ "the stronger the report and the denser the evidence graph. "
825
+ ),
826
+ "balanced": (
827
+ "- Enrich the strongest pivots before finalising. When a high-value pivot surfaces (a "
828
+ "confirmed email, domain, real name, or primary handle), expand it with the applicable "
829
+ "tools, then expand the entities those reveal. Prioritise the connections most likely to "
830
+ "be real over exhaustively chasing every lead, and deliver a solid map of the main "
831
+ "footprint within the tool budget. "
832
+ ),
833
+ "faster": (
834
+ "- This is a focused first-look sweep. Run the highest-signal tools for this target type, "
835
+ "expand only the single strongest pivot if one clearly surfaces, and do not chase secondary "
836
+ "leads. Deliver a tight, accurate report from what the priority sources return, and note "
837
+ "that a deeper sweep is available if the user wants the full map. "
838
+ ),
839
+ }
840
+ # Mode preamble is added ONLY for the lighter levels so the opening tasking line scales
841
+ # its scope and completion estimate. Deeper gets no preamble, keeping its prompt unchanged.
842
+ _DEPTH_MODE_LINE = {
843
+ "balanced": (
844
+ "COLLECTION MODE: Balanced. Run a moderate sweep of the main sources. Scale the opening "
845
+ "completion estimate down accordingly, not to the full deep-sweep timing."
846
+ ),
847
+ "faster": (
848
+ "COLLECTION MODE: Faster. Run a quick focused pass over priority sources. Keep the opening "
849
+ "completion estimate short and do not promise mapping every reachable data point."
850
+ ),
851
+ }
852
+
853
+
854
+ def _depth_rounds(depth: str) -> int:
855
+ """Tool-round ceiling for a depth level. Deeper stays env-tunable (default 12); the
856
+ lighter levels are capped below it and never exceed it."""
857
+ ceiling = int(os.environ.get("OIS_MAX_TOOL_ROUNDS", "12"))
858
+ return {"faster": min(4, ceiling), "balanced": min(8, ceiling)}.get(depth, ceiling)
859
+
860
+
861
+ async def _stream_claude(messages: list[dict], depth: str = "deeper") -> AsyncIterator[dict]:
804
862
  """Yield SSE event dicts while running an agentic Claude loop with tool_use."""
805
863
  try:
806
864
  import anthropic as _anthropic
@@ -816,12 +874,13 @@ async def _stream_claude(messages: list[dict]) -> AsyncIterator[dict]:
816
874
  yield {"type": "error", "message": "ANTHROPIC_API_KEY not set."}
817
875
  return
818
876
 
877
+ depth = depth if depth in _DEPTH_ENRICH else "deeper"
819
878
  client = _anthropic.AsyncAnthropic(api_key=api_key)
820
879
  msgs = list(messages)
821
880
  # Tool-calling rounds drive how far the analyst fans out across pivots, which is what
822
881
  # makes the evidence graph dense (more enriched entities = more nodes and connectors).
823
- # Env-tunable so depth vs speed/cost can be dialled without a code change.
824
- _MAX_TOOL_ROUNDS = int(os.environ.get("OIS_MAX_TOOL_ROUNDS", "12"))
882
+ # Scaled by the selected sweep depth; the Deeper ceiling stays env-tunable.
883
+ _MAX_TOOL_ROUNDS = _depth_rounds(depth)
825
884
  _tool_rounds = 0
826
885
  tool_ran = False
827
886
  # Memoize (tool, args) for this one investigation so the multi-round loop does not
@@ -830,7 +889,9 @@ async def _stream_claude(messages: list[dict]) -> AsyncIterator[dict]:
830
889
  # Cross-tool corroboration for this one investigation (additive notes only).
831
890
  corroboration_ledger = CorroborationLedger()
832
891
 
833
- system_prompt = (
892
+ # Deeper gets no preamble (prompt unchanged); lighter levels get a mode line up top.
893
+ _preamble = f"{_DEPTH_MODE_LINE[depth]}\n\n" if depth in _DEPTH_MODE_LINE else ""
894
+ system_prompt = _preamble + (
834
895
  "You are CLEARFRONT, an open-source intelligence (OSINT) analyst. You write to the analytic "
835
896
  "tradecraft of the intelligence community, the standard behind platforms like Palantir "
836
897
  "Gotham. When the user gives a target, use the available tools to gather intelligence, then "
@@ -899,13 +960,8 @@ async def _stream_claude(messages: list[dict]) -> AsyncIterator[dict]:
899
960
  "'- Sherlock: 8 accounts verified. Reliability: high.', "
900
961
  "'- Pastebin: unavailable (network error).'\n"
901
962
  "- Use '- ' for every bullet. Never leave a blank line between consecutive bullets.\n\n"
902
- "- Enrich aggressively before finalising. The goal is a complete map of how the target's "
903
- "footprint connects, not a minimal answer. Every time a pivot surfaces (an email, domain, "
904
- "company, real name, phone, or an additional handle), expand it with the applicable tools, "
905
- "then expand the new entities those reveal, chaining outward until the tool budget is "
906
- "reached. Do not stop at one or two pivots. The more real, connected entities you surface, "
907
- "the stronger the report and the denser the evidence graph. Do not emit any graph block "
908
- "yourself; the evidence graph is generated separately after your report."
963
+ + _DEPTH_ENRICH[depth]
964
+ + _GRAPH_SUFFIX
909
965
  )
910
966
 
911
967
  while True:
@@ -1722,7 +1778,7 @@ def create_app(*, host_guard: bool = False) -> FastAPI:
1722
1778
  elif backend == "ollama":
1723
1779
  gen = _stream_ollama(messages, req.ollama_host, req.ollama_model)
1724
1780
  else:
1725
- gen = _stream_claude(messages)
1781
+ gen = _stream_claude(messages, depth=req.depth)
1726
1782
 
1727
1783
  async for event in gen:
1728
1784
  yield f"data: {json.dumps(event)}\n\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clearfront
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: AI-powered OSINT agent, MCP server, and CLI. Interactive REPL + 30 tools. Anthropic Claude or local Ollama. For authorized security research use only.
5
5
  Author: Scott Martin Anderson
6
6
  License-Expression: MIT
@@ -23,50 +23,50 @@ Requires-Python: >=3.10
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: mcp>=1.28.1
26
- Requires-Dist: anthropic>=0.40.0
27
- Requires-Dist: requests>=2.31.0
26
+ Requires-Dist: anthropic>=0.116.0
27
+ Requires-Dist: requests>=2.34.2
28
28
  Requires-Dist: python-whois>=0.9.6
29
- Requires-Dist: rich>=13.0.0
30
- Requires-Dist: prompt_toolkit>=3.0.0
31
- Requires-Dist: python-dotenv>=1.0.0
29
+ Requires-Dist: rich>=15.0.0
30
+ Requires-Dist: prompt_toolkit>=3.0.52
31
+ Requires-Dist: python-dotenv>=1.2.2
32
32
  Requires-Dist: aiohttp>=3.14.1
33
33
  Requires-Dist: dnspython>=2.8.0
34
- Requires-Dist: ddgs>=9.0.0
34
+ Requires-Dist: ddgs>=9.14.4
35
35
  Requires-Dist: fastapi>=0.139.0
36
- Requires-Dist: uvicorn[standard]>=0.29.0
37
- Requires-Dist: httpx>=0.27.0
36
+ Requires-Dist: uvicorn[standard]>=0.51.0
37
+ Requires-Dist: httpx>=0.28.1
38
38
  Provides-Extra: dev
39
- Requires-Dist: pytest>=7.0; extra == "dev"
40
- Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
39
+ Requires-Dist: pytest>=9.1.1; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=1.4.0; extra == "dev"
41
41
  Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
42
42
  Requires-Dist: pytest-freezegun>=0.4.2; extra == "dev"
43
- Requires-Dist: ruff>=0.1.0; extra == "dev"
43
+ Requires-Dist: ruff>=0.15.20; extra == "dev"
44
44
  Provides-Extra: shodan
45
45
  Requires-Dist: shodan>=1.31.0; extra == "shodan"
46
46
  Provides-Extra: ollama
47
- Requires-Dist: ollama>=0.3.0; extra == "ollama"
47
+ Requires-Dist: ollama>=0.6.2; extra == "ollama"
48
48
  Provides-Extra: openai
49
- Requires-Dist: openai>=1.40.0; extra == "openai"
49
+ Requires-Dist: openai>=2.44.0; extra == "openai"
50
50
  Provides-Extra: pdf
51
- Requires-Dist: reportlab>=4.0.0; extra == "pdf"
51
+ Requires-Dist: reportlab>=5.0.0; extra == "pdf"
52
52
  Provides-Extra: censys
53
- Requires-Dist: censys-platform>=0.14.0; extra == "censys"
53
+ Requires-Dist: censys-platform>=0.15.1; extra == "censys"
54
54
  Provides-Extra: web
55
55
  Requires-Dist: fastapi>=0.139.0; extra == "web"
56
- Requires-Dist: uvicorn[standard]>=0.29.0; extra == "web"
57
- Requires-Dist: sse-starlette>=1.8.0; extra == "web"
58
- Requires-Dist: python-dotenv>=1.0.0; extra == "web"
59
- Requires-Dist: anthropic>=0.25.0; extra == "web"
60
- Requires-Dist: playwright>=1.40.0; extra == "web"
56
+ Requires-Dist: uvicorn[standard]>=0.51.0; extra == "web"
57
+ Requires-Dist: sse-starlette>=3.4.5; extra == "web"
58
+ Requires-Dist: python-dotenv>=1.2.2; extra == "web"
59
+ Requires-Dist: anthropic>=0.116.0; extra == "web"
60
+ Requires-Dist: playwright>=1.61.0; extra == "web"
61
61
  Provides-Extra: all
62
62
  Requires-Dist: shodan>=1.31.0; extra == "all"
63
- Requires-Dist: ollama>=0.3.0; extra == "all"
64
- Requires-Dist: openai>=1.40.0; extra == "all"
65
- Requires-Dist: reportlab>=4.0.0; extra == "all"
66
- Requires-Dist: censys-platform>=0.14.0; extra == "all"
63
+ Requires-Dist: ollama>=0.6.2; extra == "all"
64
+ Requires-Dist: openai>=2.44.0; extra == "all"
65
+ Requires-Dist: reportlab>=5.0.0; extra == "all"
66
+ Requires-Dist: censys-platform>=0.15.1; extra == "all"
67
67
  Requires-Dist: fastapi>=0.139.0; extra == "all"
68
- Requires-Dist: uvicorn[standard]>=0.29.0; extra == "all"
69
- Requires-Dist: sse-starlette>=1.8.0; extra == "all"
68
+ Requires-Dist: uvicorn[standard]>=0.51.0; extra == "all"
69
+ Requires-Dist: sse-starlette>=3.4.5; extra == "all"
70
70
  Dynamic: license-file
71
71
 
72
72
  <div align="center">
@@ -81,7 +81,7 @@ Dynamic: license-file
81
81
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue?style=flat-square)](https://www.python.org/)
82
82
  [![License MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
83
83
  [![MCP](https://img.shields.io/badge/protocol-MCP-blueviolet?style=flat-square)](https://modelcontextprotocol.io/)
84
- [![Version](https://img.shields.io/badge/version-2.5.0-informational?style=flat-square)](CHANGELOG.md)
84
+ [![Version](https://img.shields.io/badge/version-2.6.0-informational?style=flat-square)](CHANGELOG.md)
85
85
 
86
86
  **[clearfront.sh](https://clearfront.sh)** · [Disclaimer](./DISCLAIMER.md)
87
87
 
@@ -0,0 +1,53 @@
1
+ mcp>=1.28.1
2
+ anthropic>=0.116.0
3
+ requests>=2.34.2
4
+ python-whois>=0.9.6
5
+ rich>=15.0.0
6
+ prompt_toolkit>=3.0.52
7
+ python-dotenv>=1.2.2
8
+ aiohttp>=3.14.1
9
+ dnspython>=2.8.0
10
+ ddgs>=9.14.4
11
+ fastapi>=0.139.0
12
+ uvicorn[standard]>=0.51.0
13
+ httpx>=0.28.1
14
+
15
+ [all]
16
+ shodan>=1.31.0
17
+ ollama>=0.6.2
18
+ openai>=2.44.0
19
+ reportlab>=5.0.0
20
+ censys-platform>=0.15.1
21
+ fastapi>=0.139.0
22
+ uvicorn[standard]>=0.51.0
23
+ sse-starlette>=3.4.5
24
+
25
+ [censys]
26
+ censys-platform>=0.15.1
27
+
28
+ [dev]
29
+ pytest>=9.1.1
30
+ pytest-asyncio>=1.4.0
31
+ pytest-cov>=7.1.0
32
+ pytest-freezegun>=0.4.2
33
+ ruff>=0.15.20
34
+
35
+ [ollama]
36
+ ollama>=0.6.2
37
+
38
+ [openai]
39
+ openai>=2.44.0
40
+
41
+ [pdf]
42
+ reportlab>=5.0.0
43
+
44
+ [shodan]
45
+ shodan>=1.31.0
46
+
47
+ [web]
48
+ fastapi>=0.139.0
49
+ uvicorn[standard]>=0.51.0
50
+ sse-starlette>=3.4.5
51
+ python-dotenv>=1.2.2
52
+ anthropic>=0.116.0
53
+ playwright>=1.61.0
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools>=61.0"]
2
+ requires = ["setuptools>=83.0.0"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "clearfront"
7
- version = "2.5.0"
7
+ version = "2.6.0"
8
8
  description = "AI-powered OSINT agent, MCP server, and CLI. Interactive REPL + 30 tools. Anthropic Claude or local Ollama. For authorized security research use only."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -31,18 +31,18 @@ classifiers = [
31
31
  ]
32
32
  dependencies = [
33
33
  "mcp>=1.28.1",
34
- "anthropic>=0.40.0",
35
- "requests>=2.31.0",
34
+ "anthropic>=0.116.0",
35
+ "requests>=2.34.2",
36
36
  "python-whois>=0.9.6",
37
- "rich>=13.0.0",
38
- "prompt_toolkit>=3.0.0",
39
- "python-dotenv>=1.0.0",
37
+ "rich>=15.0.0",
38
+ "prompt_toolkit>=3.0.52",
39
+ "python-dotenv>=1.2.2",
40
40
  "aiohttp>=3.14.1",
41
41
  "dnspython>=2.8.0",
42
- "ddgs>=9.0.0",
42
+ "ddgs>=9.14.4",
43
43
  "fastapi>=0.139.0",
44
- "uvicorn[standard]>=0.29.0",
45
- "httpx>=0.27.0",
44
+ "uvicorn[standard]>=0.51.0",
45
+ "httpx>=0.28.1",
46
46
  ]
47
47
 
48
48
  [project.urls]
@@ -56,44 +56,44 @@ clearfront-mcp = "clearfront.mcp_server:main"
56
56
 
57
57
  [project.optional-dependencies]
58
58
  dev = [
59
- "pytest>=7.0",
60
- "pytest-asyncio>=0.21",
59
+ "pytest>=9.1.1",
60
+ "pytest-asyncio>=1.4.0",
61
61
  "pytest-cov>=7.1.0",
62
62
  "pytest-freezegun>=0.4.2",
63
- "ruff>=0.1.0",
63
+ "ruff>=0.15.20",
64
64
  ]
65
65
  shodan = [
66
66
  "shodan>=1.31.0",
67
67
  ]
68
68
  ollama = [
69
- "ollama>=0.3.0",
69
+ "ollama>=0.6.2",
70
70
  ]
71
71
  openai = [
72
- "openai>=1.40.0",
72
+ "openai>=2.44.0",
73
73
  ]
74
74
  pdf = [
75
- "reportlab>=4.0.0",
75
+ "reportlab>=5.0.0",
76
76
  ]
77
77
  censys = [
78
- "censys-platform>=0.14.0",
78
+ "censys-platform>=0.15.1",
79
79
  ]
80
80
  web = [
81
81
  "fastapi>=0.139.0",
82
- "uvicorn[standard]>=0.29.0",
83
- "sse-starlette>=1.8.0",
84
- "python-dotenv>=1.0.0",
85
- "anthropic>=0.25.0",
86
- "playwright>=1.40.0",
82
+ "uvicorn[standard]>=0.51.0",
83
+ "sse-starlette>=3.4.5",
84
+ "python-dotenv>=1.2.2",
85
+ "anthropic>=0.116.0",
86
+ "playwright>=1.61.0",
87
87
  ]
88
88
  all = [
89
89
  "shodan>=1.31.0",
90
- "ollama>=0.3.0",
91
- "openai>=1.40.0",
92
- "reportlab>=4.0.0",
93
- "censys-platform>=0.14.0",
90
+ "ollama>=0.6.2",
91
+ "openai>=2.44.0",
92
+ "reportlab>=5.0.0",
93
+ "censys-platform>=0.15.1",
94
94
  "fastapi>=0.139.0",
95
- "uvicorn[standard]>=0.29.0",
96
- "sse-starlette>=1.8.0",
95
+ "uvicorn[standard]>=0.51.0",
96
+ "sse-starlette>=3.4.5",
97
97
  ]
98
98
 
99
99
  [tool.setuptools.packages.find]
@@ -493,3 +493,54 @@ class TestRunToolSelfLookup:
493
493
  assert by_name["search_ip"]["input_schema"]["required"] == []
494
494
  assert by_name["search_exposure"]["input_schema"]["required"] == []
495
495
  assert by_name["search_shodan"]["input_schema"]["required"] == ["input"]
496
+
497
+
498
+ # ---------------------------------------------------------------------------
499
+ # Sweep-depth control
500
+ # ---------------------------------------------------------------------------
501
+
502
+
503
+ class TestSweepDepth:
504
+ def test_round_caps_per_level(self):
505
+ from clearfront.web_server import _depth_rounds
506
+
507
+ assert _depth_rounds("faster") == 4
508
+ assert _depth_rounds("balanced") == 8
509
+ assert _depth_rounds("deeper") == 12
510
+
511
+ def test_unknown_depth_falls_back_to_deeper(self):
512
+ from clearfront.web_server import _depth_rounds
513
+
514
+ assert _depth_rounds("bogus") == _depth_rounds("deeper")
515
+
516
+ def test_env_override_sets_deeper_ceiling_and_caps_lighter_levels(self, monkeypatch):
517
+ from clearfront.web_server import _depth_rounds
518
+
519
+ monkeypatch.setenv("OIS_MAX_TOOL_ROUNDS", "6")
520
+ assert _depth_rounds("deeper") == 6
521
+ # lighter levels are capped below the ceiling, never above it
522
+ assert _depth_rounds("balanced") == 6
523
+ assert _depth_rounds("faster") == 4
524
+
525
+ def test_deeper_prompt_unchanged(self):
526
+ # Deeper must stay byte-identical to the historical prompt: no mode preamble,
527
+ # and its enrichment clause plus the graph suffix reproduce the original paragraph.
528
+ from clearfront.web_server import _DEPTH_ENRICH, _DEPTH_MODE_LINE, _GRAPH_SUFFIX
529
+
530
+ assert "deeper" not in _DEPTH_MODE_LINE
531
+ original = (
532
+ "- Enrich aggressively before finalising. The goal is a complete map of how the "
533
+ "target's footprint connects, not a minimal answer. Every time a pivot surfaces "
534
+ "(an email, domain, company, real name, phone, or an additional handle), expand "
535
+ "it with the applicable tools, then expand the new entities those reveal, chaining "
536
+ "outward until the tool budget is reached. Do not stop at one or two pivots. The "
537
+ "more real, connected entities you surface, the stronger the report and the denser "
538
+ "the evidence graph. Do not emit any graph block yourself; the evidence graph is "
539
+ "generated separately after your report."
540
+ )
541
+ assert _DEPTH_ENRICH["deeper"] + _GRAPH_SUFFIX == original
542
+
543
+ def test_chat_request_defaults_to_deeper(self):
544
+ from clearfront.web_server import ChatRequest
545
+
546
+ assert ChatRequest(message="hi").depth == "deeper"
@@ -1,53 +0,0 @@
1
- mcp>=1.28.1
2
- anthropic>=0.40.0
3
- requests>=2.31.0
4
- python-whois>=0.9.6
5
- rich>=13.0.0
6
- prompt_toolkit>=3.0.0
7
- python-dotenv>=1.0.0
8
- aiohttp>=3.14.1
9
- dnspython>=2.8.0
10
- ddgs>=9.0.0
11
- fastapi>=0.139.0
12
- uvicorn[standard]>=0.29.0
13
- httpx>=0.27.0
14
-
15
- [all]
16
- shodan>=1.31.0
17
- ollama>=0.3.0
18
- openai>=1.40.0
19
- reportlab>=4.0.0
20
- censys-platform>=0.14.0
21
- fastapi>=0.139.0
22
- uvicorn[standard]>=0.29.0
23
- sse-starlette>=1.8.0
24
-
25
- [censys]
26
- censys-platform>=0.14.0
27
-
28
- [dev]
29
- pytest>=7.0
30
- pytest-asyncio>=0.21
31
- pytest-cov>=7.1.0
32
- pytest-freezegun>=0.4.2
33
- ruff>=0.1.0
34
-
35
- [ollama]
36
- ollama>=0.3.0
37
-
38
- [openai]
39
- openai>=1.40.0
40
-
41
- [pdf]
42
- reportlab>=4.0.0
43
-
44
- [shodan]
45
- shodan>=1.31.0
46
-
47
- [web]
48
- fastapi>=0.139.0
49
- uvicorn[standard]>=0.29.0
50
- sse-starlette>=1.8.0
51
- python-dotenv>=1.0.0
52
- anthropic>=0.25.0
53
- playwright>=1.40.0
File without changes
File without changes
File without changes
File without changes
File without changes