Contributing
import { Aside } from ‘@astrojs/starlight/components’;
Ways to contribute
- New OSINT tool: integrate a new service as an async function
- Bug fix: correct incorrect behavior
- Documentation improvement: clarify, complete, translate
- Tests: increase test coverage
- New entity type: extend the FTM ontology
- Skill translation: adapt for new languages
Workflow
# 1. Fork the repository on GitHub
# 2. Clone your forkgit clone https://github.com/your-username/osint-ai-one.gitcd osint-ai-one
# 3. Create a branch for your changegit checkout -b feature/my-new-tool
# 4. Install in development modepython -m venv .venv && source .venv/bin/activatepip install -e ".[dev]"pre-commit install
# 5. Make your changes# ...
# 6. Run testspytest tests/ -vruff check src/ tests/mypy src/
# 7. Commit and pushgit add .git commit -m "feat: add tool X to query Y"git push origin feature/my-new-tool
# 8. Open a Pull Request on GitHubAdding a new OSINT tool
Function structure
All tools follow the same pattern:
import osimport httpxfrom src.cache import cached
@cached(ttl_seconds=86400)async def my_service_lookup(target: str) -> dict: """ Query My Service for information about target.
Args: target: IP, domain or other identifier
Returns: dict with normalized fields from the service """ api_key = os.getenv("MY_SERVICE_API_KEY") if not api_key: return {"error": "MY_SERVICE_API_KEY not configured"}
async with httpx.AsyncClient(timeout=30) as client: response = await client.get( f"https://api.myservice.com/v1/lookup/{target}", headers={"Authorization": f"Bearer {api_key}"} ) response.raise_for_status() data = response.json()
return { "source": "myservice", "target": target, "result_field": data.get("field"), # ... normalize relevant fields }Register in the agent
In src/agent/__init__.py, add the tool to the agent’s ReAct tools list.
Register in the MCP Server
In src/mcp_server/server.py, add an MCP endpoint for the tool.
Required tests
import pytestimport respxfrom httpx import Responsefrom src.tools.my_service import my_service_lookup
@pytest.mark.asyncio@respx.mockasync def test_my_service_lookup_success(): respx.get("https://api.myservice.com/v1/lookup/8.8.8.8").mock( return_value=Response(200, json={"field": "value"}) ) result = await my_service_lookup("8.8.8.8") assert result["result_field"] == "value" assert result["source"] == "myservice"
@pytest.mark.asyncio@respx.mockasync def test_my_service_lookup_error(): respx.get("https://api.myservice.com/v1/lookup/8.8.8.8").mock( return_value=Response(404) ) result = await my_service_lookup("8.8.8.8") assert "error" in resultDocument in .env.example
# My Service — https://myservice.com/registerMY_SERVICE_API_KEY=Code conventions
| Element | Convention |
|---|---|
| Tool functions | service_name_type_lookup |
| Environment variables | SERVICE_API_KEY in uppercase |
| Imports | Ordered: stdlib → third-party → local |
| Format | ruff format (line length: 88) |
| Type hints | Mandatory in public functions |
| Docstrings | Google style, in English |
Commit conventions
feat: new tool for Xfix: fixes error in Y when Zdocs: adds documentation for Wtest: tests for tool Vrefactor: extracts shared cache logicchore: updates dependenciesCode of Conduct
Respectful and constructive contributions. Critique the code, not the people.