clearfront 2.7.0__tar.gz → 2.7.2__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 (102) hide show
  1. {clearfront-2.7.0 → clearfront-2.7.2}/PKG-INFO +26 -6
  2. {clearfront-2.7.0 → clearfront-2.7.2}/README.md +23 -3
  3. clearfront-2.7.2/clearfront/boards.py +250 -0
  4. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/mcp_server.py +6 -3
  5. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/web/index.html +349 -43
  6. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/web_server.py +86 -0
  7. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront.egg-info/PKG-INFO +26 -6
  8. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront.egg-info/SOURCES.txt +2 -0
  9. {clearfront-2.7.0 → clearfront-2.7.2}/pyproject.toml +5 -4
  10. clearfront-2.7.2/tests/test_boards.py +188 -0
  11. {clearfront-2.7.0 → clearfront-2.7.2}/LICENSE +0 -0
  12. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/__init__.py +0 -0
  13. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/agent.py +0 -0
  14. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/brightdata.py +0 -0
  15. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/cli.py +0 -0
  16. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/correlation.py +0 -0
  17. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/corroboration.py +0 -0
  18. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/effort.py +0 -0
  19. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/exceptions.py +0 -0
  20. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/extractors.py +0 -0
  21. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/json_output.py +0 -0
  22. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/multi_target.py +0 -0
  23. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/pdf_report.py +0 -0
  24. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/pivot.py +0 -0
  25. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/prompts.py +0 -0
  26. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/regexes.py +0 -0
  27. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/repl.py +0 -0
  28. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/serp.py +0 -0
  29. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/session_history.py +0 -0
  30. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/sponsors.py +0 -0
  31. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tool_cache.py +0 -0
  32. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/__init__.py +0 -0
  33. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/data/NOTICE +0 -0
  34. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/data/wmn-data-unique.json +0 -0
  35. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/exceptions.py +0 -0
  36. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/generate_dorks.py +0 -0
  37. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/scrape_url.py +0 -0
  38. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_abuseipdb.py +0 -0
  39. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_breach.py +0 -0
  40. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_censys.py +0 -0
  41. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_crt.py +0 -0
  42. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_crypto.py +0 -0
  43. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_dns.py +0 -0
  44. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_domain.py +0 -0
  45. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_dorks_live.py +0 -0
  46. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_email.py +0 -0
  47. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_emailrep.py +0 -0
  48. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_exif.py +0 -0
  49. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_exposure.py +0 -0
  50. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_footprint.py +0 -0
  51. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_github.py +0 -0
  52. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_gravatar.py +0 -0
  53. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_greynoise.py +0 -0
  54. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_harvester.py +0 -0
  55. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_hudsonrock.py +0 -0
  56. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_ip.py +0 -0
  57. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_ip2location.py +0 -0
  58. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_maigret.py +0 -0
  59. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_paste.py +0 -0
  60. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_phone.py +0 -0
  61. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_shodan.py +0 -0
  62. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_username.py +0 -0
  63. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_virustotal.py +0 -0
  64. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_wayback.py +0 -0
  65. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/search_whois.py +0 -0
  66. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/tools/whatsmyname.py +0 -0
  67. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront/utils.py +0 -0
  68. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront.egg-info/dependency_links.txt +0 -0
  69. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront.egg-info/entry_points.txt +0 -0
  70. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront.egg-info/requires.txt +0 -0
  71. {clearfront-2.7.0 → clearfront-2.7.2}/clearfront.egg-info/top_level.txt +0 -0
  72. {clearfront-2.7.0 → clearfront-2.7.2}/setup.cfg +0 -0
  73. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_authorized_use.py +0 -0
  74. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_brightdata.py +0 -0
  75. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_cli_openai.py +0 -0
  76. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_cli_output.py +0 -0
  77. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_corroboration.py +0 -0
  78. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_crt.py +0 -0
  79. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_dns.py +0 -0
  80. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_effort.py +0 -0
  81. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_exif.py +0 -0
  82. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_footprint.py +0 -0
  83. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_github_code_search.py +0 -0
  84. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_graph_export.py +0 -0
  85. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_hudsonrock.py +0 -0
  86. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_json_export.py +0 -0
  87. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_mcp_authorized_use.py +0 -0
  88. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_openai_agent.py +0 -0
  89. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_pivot_freetier.py +0 -0
  90. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_regexes.py +0 -0
  91. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_repl_first_run.py +0 -0
  92. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_report_export.py +0 -0
  93. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_security.py +0 -0
  94. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_serp.py +0 -0
  95. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_tool_cache.py +0 -0
  96. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_tool_parity.py +0 -0
  97. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_tools.py +0 -0
  98. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_v2140.py +0 -0
  99. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_v240.py +0 -0
  100. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_wayback_greynoise.py +0 -0
  101. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_web_server.py +0 -0
  102. {clearfront-2.7.0 → clearfront-2.7.2}/tests/test_web_server_openai.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clearfront
3
- Version: 2.7.0
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.
3
+ Version: 2.7.2
4
+ Summary: Clearfront OSINT: AI-powered OSINT agent, MCP server, and CLI that maps your digital footprint across 3,400+ public data sources. 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
7
7
  Project-URL: Homepage, https://github.com/scottmartinanderson/clearfront
8
8
  Project-URL: Repository, https://github.com/scottmartinanderson/clearfront
9
9
  Project-URL: Bug Tracker, https://github.com/scottmartinanderson/clearfront/issues
10
- Keywords: osint,security,mcp,cli,ai,agent,anthropic,claude,investigation,recon,cybersecurity
10
+ Keywords: clearfront,clearfront-osint,osint,open-source-intelligence,digital-footprint,security,mcp,cli,ai,agent,anthropic,claude,investigation,recon,cybersecurity,privacy
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Intended Audience :: Information Technology
@@ -69,10 +69,12 @@ Requires-Dist: uvicorn[standard]>=0.51.0; extra == "all"
69
69
  Requires-Dist: sse-starlette>=3.4.5; extra == "all"
70
70
  Dynamic: license-file
71
71
 
72
+ <!-- mcp-name: sh.clearfront/clearfront -->
73
+
72
74
  <div align="center">
73
75
  <h1>Clearfront</h1>
74
76
  <p><strong>Open-source AI intelligence on your digital footprint.</strong></p>
75
- <p>Give Clearfront an email, username, domain, IP, or name, and its AI security analyst scans public data sources in one sweep, then returns a calibrated report and an interactive evidence graph. Check your own exposure, or investigate an authorized target. It runs locally with your own API keys and sends nothing to us.</p>
77
+ <p>Clearfront, also known as Clearfront OSINT, is a free and open-source OSINT agent. Give it an email, username, domain, IP, or name, and its AI security analyst scans 3,400+ public data sources in one sweep, then returns a calibrated report and an interactive evidence graph. Check your own exposure, or investigate an authorized target. It runs locally with your own API keys and sends nothing to us.</p>
76
78
  <p>Interactive REPL · CLI · local web console · MCP server. Powered by Anthropic Claude, a local Ollama model, or any OpenAI-compatible endpoint.</p>
77
79
  </div>
78
80
 
@@ -81,7 +83,7 @@ Dynamic: license-file
81
83
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue?style=flat-square)](https://www.python.org/)
82
84
  [![License MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
83
85
  [![MCP](https://img.shields.io/badge/protocol-MCP-blueviolet?style=flat-square)](https://modelcontextprotocol.io/)
84
- [![Version](https://img.shields.io/badge/version-2.7.0-informational?style=flat-square)](CHANGELOG.md)
86
+ [![Version](https://img.shields.io/badge/version-2.7.2-informational?style=flat-square)](CHANGELOG.md)
85
87
 
86
88
  **[clearfront.sh](https://clearfront.sh)** · [Disclaimer](./DISCLAIMER.md)
87
89
 
@@ -226,7 +228,7 @@ clearfront web
226
228
 
227
229
  Browser-based AI chat with streaming tool output, inline result cards, and a light/dark theme toggle. Supports fully local inference via Ollama or any OpenAI-compatible endpoint (no Anthropic API key required when using a local backend).
228
230
 
229
- The console runs entirely on your machine and binds to `127.0.0.1` by default. Choose your backend and paste your own key in Settings; your keys and the targets you investigate never touch our servers. Screenshots are in [`media/screenshots/`](media/screenshots/).
231
+ The console runs entirely on your machine and binds to `127.0.0.1` by default. Choose your backend and paste your own key in Settings; your keys and the targets you investigate never touch our servers. Screenshots are in [`media/`](media/).
230
232
 
231
233
  ### MCP Server
232
234
 
@@ -252,6 +254,24 @@ claude mcp list
252
254
  }
253
255
  ```
254
256
 
257
+ ### Agent Skill
258
+
259
+ [`skills/clearfront-osint`](skills/clearfront-osint/SKILL.md) teaches an agent
260
+ how to drive Clearfront: which approach fits a given request, the direct
261
+ subcommands, how to read the confidence ratings, and the authorized-use rules.
262
+ It follows the [Agent Skills](https://agentskills.io) open standard, so it works
263
+ in Claude Code, Cursor, Gemini CLI, Copilot, Goose, OpenCode and the other
264
+ skills-compatible clients.
265
+
266
+ ```bash
267
+ npx skills add scottmartinanderson/clearfront
268
+ ```
269
+
270
+ Install Clearfront itself as well (`pip install clearfront`); the skill is the
271
+ instructions, not the tool. Use the skill when you want the agent to decide how
272
+ to run a sweep, and the MCP server above when you want it calling the 30 tools
273
+ directly.
274
+
255
275
  ## Docker
256
276
 
257
277
  ```bash
@@ -1,7 +1,9 @@
1
+ <!-- mcp-name: sh.clearfront/clearfront -->
2
+
1
3
  <div align="center">
2
4
  <h1>Clearfront</h1>
3
5
  <p><strong>Open-source AI intelligence on your digital footprint.</strong></p>
4
- <p>Give Clearfront an email, username, domain, IP, or name, and its AI security analyst scans public data sources in one sweep, then returns a calibrated report and an interactive evidence graph. Check your own exposure, or investigate an authorized target. It runs locally with your own API keys and sends nothing to us.</p>
6
+ <p>Clearfront, also known as Clearfront OSINT, is a free and open-source OSINT agent. Give it an email, username, domain, IP, or name, and its AI security analyst scans 3,400+ public data sources in one sweep, then returns a calibrated report and an interactive evidence graph. Check your own exposure, or investigate an authorized target. It runs locally with your own API keys and sends nothing to us.</p>
5
7
  <p>Interactive REPL · CLI · local web console · MCP server. Powered by Anthropic Claude, a local Ollama model, or any OpenAI-compatible endpoint.</p>
6
8
  </div>
7
9
 
@@ -10,7 +12,7 @@
10
12
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue?style=flat-square)](https://www.python.org/)
11
13
  [![License MIT](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)
12
14
  [![MCP](https://img.shields.io/badge/protocol-MCP-blueviolet?style=flat-square)](https://modelcontextprotocol.io/)
13
- [![Version](https://img.shields.io/badge/version-2.7.0-informational?style=flat-square)](CHANGELOG.md)
15
+ [![Version](https://img.shields.io/badge/version-2.7.2-informational?style=flat-square)](CHANGELOG.md)
14
16
 
15
17
  **[clearfront.sh](https://clearfront.sh)** · [Disclaimer](./DISCLAIMER.md)
16
18
 
@@ -155,7 +157,7 @@ clearfront web
155
157
 
156
158
  Browser-based AI chat with streaming tool output, inline result cards, and a light/dark theme toggle. Supports fully local inference via Ollama or any OpenAI-compatible endpoint (no Anthropic API key required when using a local backend).
157
159
 
158
- The console runs entirely on your machine and binds to `127.0.0.1` by default. Choose your backend and paste your own key in Settings; your keys and the targets you investigate never touch our servers. Screenshots are in [`media/screenshots/`](media/screenshots/).
160
+ The console runs entirely on your machine and binds to `127.0.0.1` by default. Choose your backend and paste your own key in Settings; your keys and the targets you investigate never touch our servers. Screenshots are in [`media/`](media/).
159
161
 
160
162
  ### MCP Server
161
163
 
@@ -181,6 +183,24 @@ claude mcp list
181
183
  }
182
184
  ```
183
185
 
186
+ ### Agent Skill
187
+
188
+ [`skills/clearfront-osint`](skills/clearfront-osint/SKILL.md) teaches an agent
189
+ how to drive Clearfront: which approach fits a given request, the direct
190
+ subcommands, how to read the confidence ratings, and the authorized-use rules.
191
+ It follows the [Agent Skills](https://agentskills.io) open standard, so it works
192
+ in Claude Code, Cursor, Gemini CLI, Copilot, Goose, OpenCode and the other
193
+ skills-compatible clients.
194
+
195
+ ```bash
196
+ npx skills add scottmartinanderson/clearfront
197
+ ```
198
+
199
+ Install Clearfront itself as well (`pip install clearfront`); the skill is the
200
+ instructions, not the tool. Use the skill when you want the agent to decide how
201
+ to run a sweep, and the MCP server above when you want it calling the 30 tools
202
+ directly.
203
+
184
204
  ## Docker
185
205
 
186
206
  ```bash
@@ -0,0 +1,250 @@
1
+ # clearfront/boards.py
2
+ """
3
+ Persistent investigation boards for Clearfront.
4
+
5
+ A *board* is a saved evidence-graph investigation: a title, an optional
6
+ description and colour tag, and the D3 node-link graph the web console renders.
7
+ Boards are stored as individual JSON files in ``~/.clearfront/boards/``, one
8
+ file per board keyed by a filesystem-safe slug id.
9
+
10
+ This is the local-first store, holding only what the analyst already sees in
11
+ their own browser; nothing is uploaded anywhere. A future cloud-hosted store can
12
+ implement the same board shape behind the same web routes.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ import re
20
+ import uuid
21
+ from datetime import datetime, timezone
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ BOARDS_DIR = Path.home() / ".clearfront" / "boards"
28
+ MAX_BOARDS = 200
29
+
30
+ # Colour tags the UI offers; a board's colour is validated to this set so a
31
+ # malformed value can never reach the stored file or the client.
32
+ BOARD_COLORS = (
33
+ "blue", "purple", "pink", "red", "amber",
34
+ "green", "cyan", "indigo", "teal", "grey",
35
+ )
36
+ DEFAULT_COLOR = "blue"
37
+
38
+ # A board id is only ever a lowercase slug. This regex also blocks path
39
+ # traversal (no dots, no slashes) when an id from a URL is turned into a path.
40
+ _SAFE_ID_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,63}$")
41
+ _SLUG_STRIP_RE = re.compile(r"[^a-z0-9]+")
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Helpers
46
+ # ---------------------------------------------------------------------------
47
+
48
+
49
+ def _now_iso() -> str:
50
+ return datetime.now(timezone.utc).isoformat(timespec="seconds")
51
+
52
+
53
+ def _ensure_dir() -> None:
54
+ BOARDS_DIR.mkdir(parents=True, exist_ok=True)
55
+
56
+
57
+ def _slugify(title: str) -> str:
58
+ """Turn a human title into a short filesystem-safe slug stem (no id suffix)."""
59
+ return _SLUG_STRIP_RE.sub("-", (title or "").lower()).strip("-")[:40].strip("-")
60
+
61
+
62
+ def _new_id(title: str) -> str:
63
+ """Generate a unique board id: title slug + short random suffix."""
64
+ stem = _slugify(title) or "board"
65
+ return f"{stem}-{uuid.uuid4().hex[:6]}"
66
+
67
+
68
+ def _board_path(board_id: str) -> Path | None:
69
+ """Return the file path for a validated id, or None if the id is unsafe."""
70
+ if not board_id or not _SAFE_ID_RE.match(board_id):
71
+ return None
72
+ return BOARDS_DIR / f"{board_id}.json"
73
+
74
+
75
+ def _clean_graph(graph: Any) -> dict[str, list]:
76
+ """Keep only the node/link fields we persist; drop force-layout state.
77
+
78
+ Link endpoints may be node objects after the client's force layout runs, so
79
+ they are normalised back to plain ids. ``label``/``kind`` are preserved when
80
+ present (forward compatibility with typed relationships).
81
+ """
82
+ if not isinstance(graph, dict):
83
+ return {"nodes": [], "links": []}
84
+
85
+ nodes: list[dict] = []
86
+ for n in graph.get("nodes") or []:
87
+ if not isinstance(n, dict) or n.get("id") is None:
88
+ continue
89
+ nodes.append({
90
+ "id": n.get("id"),
91
+ "label": n.get("label", ""),
92
+ "type": n.get("type", ""),
93
+ "value": n.get("value", ""),
94
+ "source": n.get("source", ""),
95
+ "confidence": n.get("confidence", ""),
96
+ "severity": n.get("severity", ""),
97
+ })
98
+
99
+ links: list[dict] = []
100
+ for l in graph.get("links") or []:
101
+ if not isinstance(l, dict):
102
+ continue
103
+ src, tgt = l.get("source"), l.get("target")
104
+ if isinstance(src, dict):
105
+ src = src.get("id")
106
+ if isinstance(tgt, dict):
107
+ tgt = tgt.get("id")
108
+ if src is None or tgt is None:
109
+ continue
110
+ link: dict[str, Any] = {"source": src, "target": tgt}
111
+ if l.get("label"):
112
+ link["label"] = l["label"]
113
+ if l.get("kind"):
114
+ link["kind"] = l["kind"]
115
+ links.append(link)
116
+
117
+ return {"nodes": nodes, "links": links}
118
+
119
+
120
+ def _summary(board: dict[str, Any]) -> dict[str, Any]:
121
+ """A light board descriptor for the list view (no full graph payload)."""
122
+ graph = board.get("graph") or {}
123
+ return {
124
+ "id": board.get("id", ""),
125
+ "title": board.get("title", ""),
126
+ "description": board.get("description", ""),
127
+ "color": board.get("color", DEFAULT_COLOR),
128
+ "created_at": board.get("created_at", ""),
129
+ "updated_at": board.get("updated_at", ""),
130
+ "node_count": len(graph.get("nodes") or []),
131
+ "link_count": len(graph.get("links") or []),
132
+ }
133
+
134
+
135
+ # ---------------------------------------------------------------------------
136
+ # Public API
137
+ # ---------------------------------------------------------------------------
138
+
139
+
140
+ def list_boards() -> list[dict[str, Any]]:
141
+ """Return board summaries, newest-updated first (no graph payloads)."""
142
+ if not BOARDS_DIR.exists():
143
+ return []
144
+ boards: list[dict[str, Any]] = []
145
+ for f in BOARDS_DIR.glob("*.json"):
146
+ try:
147
+ boards.append(_summary(json.loads(f.read_text(encoding="utf-8"))))
148
+ except Exception:
149
+ logger.debug("Failed to read board file: %s", f, exc_info=True)
150
+ boards.sort(key=lambda b: b.get("updated_at", ""), reverse=True)
151
+ return boards
152
+
153
+
154
+ def load_board(board_id: str) -> dict[str, Any] | None:
155
+ """Return the full board (including graph), or None if missing/invalid id."""
156
+ path = _board_path(board_id)
157
+ if path is None or not path.exists():
158
+ return None
159
+ try:
160
+ return json.loads(path.read_text(encoding="utf-8"))
161
+ except Exception:
162
+ logger.debug("Failed to read board file: %s", path, exc_info=True)
163
+ return None
164
+
165
+
166
+ def save_board(
167
+ *,
168
+ board_id: str = "",
169
+ title: str = "",
170
+ description: str = "",
171
+ color: str = DEFAULT_COLOR,
172
+ graph: Any = None,
173
+ notes: str = "",
174
+ ) -> dict[str, Any] | None:
175
+ """Create or update a board. Returns the saved board, or None on bad input.
176
+
177
+ A blank/unknown ``board_id`` creates a new board; a valid existing id updates
178
+ it in place, preserving the original ``created_at``. Requires a non-empty
179
+ ``title``.
180
+ """
181
+ title = (title or "").strip()
182
+ if not title:
183
+ return None
184
+ color = color if color in BOARD_COLORS else DEFAULT_COLOR
185
+
186
+ existing = load_board(board_id) if board_id else None
187
+ now = _now_iso()
188
+ if existing is not None:
189
+ bid = existing["id"]
190
+ created = existing.get("created_at", now)
191
+ else:
192
+ bid = _new_id(title)
193
+ p = _board_path(bid)
194
+ while p is not None and p.exists(): # astronomically unlikely, but cheap
195
+ bid = _new_id(title)
196
+ p = _board_path(bid)
197
+ created = now
198
+
199
+ path = _board_path(bid)
200
+ if path is None:
201
+ return None
202
+
203
+ board = {
204
+ "id": bid,
205
+ "title": title[:200],
206
+ "description": (description or "").strip()[:2000],
207
+ "color": color,
208
+ "created_at": created,
209
+ "updated_at": now,
210
+ "notes": (notes or "")[:20000],
211
+ "graph": _clean_graph(graph),
212
+ }
213
+
214
+ _ensure_dir()
215
+ path.write_text(json.dumps(board, indent=2), encoding="utf-8")
216
+ _prune()
217
+ return board
218
+
219
+
220
+ def delete_board(board_id: str) -> bool:
221
+ """Delete a board by id. Returns True if a file was removed."""
222
+ path = _board_path(board_id)
223
+ if path is None or not path.exists():
224
+ return False
225
+ try:
226
+ path.unlink()
227
+ return True
228
+ except Exception:
229
+ logger.debug("Failed to delete board file: %s", path, exc_info=True)
230
+ return False
231
+
232
+
233
+ def _prune() -> None:
234
+ """Keep at most MAX_BOARDS files, deleting the oldest-updated first."""
235
+ files = list(BOARDS_DIR.glob("*.json"))
236
+ if len(files) <= MAX_BOARDS:
237
+ return
238
+
239
+ def _updated(f: Path) -> str:
240
+ try:
241
+ return json.loads(f.read_text(encoding="utf-8")).get("updated_at", "")
242
+ except Exception:
243
+ return ""
244
+
245
+ files.sort(key=_updated) # oldest first
246
+ for f in files[: len(files) - MAX_BOARDS]:
247
+ try:
248
+ f.unlink()
249
+ except Exception:
250
+ logger.debug("Failed to prune board file: %s", f, exc_info=True)
@@ -1,9 +1,12 @@
1
1
  # clearfront/mcp_server.py
2
2
  """
3
- Clearfront MCP Server, v2.7.0
3
+ Clearfront OSINT MCP Server, v2.7.1
4
4
 
5
- Exposes all 30 OSINT tool capabilities plus multi-target investigation
6
- to MCP-compliant AI clients over standard I/O. Tools include:
5
+ Clearfront (Clearfront OSINT) is an open-source AI OSINT agent that maps a
6
+ digital footprint across 3,400+ public data sources and returns a calibrated
7
+ intelligence report. This server exposes all 30 OSINT tool capabilities plus
8
+ multi-target investigation to MCP-compliant AI clients over standard I/O.
9
+ Tools include:
7
10
  search_email, search_username, search_maigret, search_breach, search_whois,
8
11
  search_ip, search_domain, generate_dorks, search_paste, search_phone,
9
12
  search_shodan, search_virustotal, search_censys, search_ip2location,