openhire 0.1.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.
- openhire-0.1.0/.claude/settings.local.json +84 -0
- openhire-0.1.0/.env.example +22 -0
- openhire-0.1.0/.gitignore +42 -0
- openhire-0.1.0/CLAUDE.md +41 -0
- openhire-0.1.0/PKG-INFO +151 -0
- openhire-0.1.0/PROGRESS.md +136 -0
- openhire-0.1.0/README.md +125 -0
- openhire-0.1.0/design_handoff_openhire_v01/README.md +166 -0
- openhire-0.1.0/design_handoff_openhire_v01/design_refs/OpenHire Landing Page.dc.html +118 -0
- openhire-0.1.0/design_handoff_openhire_v01/design_refs/OpenHire Protocol Spec.dc.html +239 -0
- openhire-0.1.0/design_handoff_openhire_v01/design_refs/OpenHire v0.1 /345/223/250/345/205/265.dc.html" +216 -0
- openhire-0.1.0/design_handoff_openhire_v01/design_refs/OpenHire /344/272/244/344/272/222/345/216/237/345/236/213.dc.html" +319 -0
- openhire-0.1.0/design_handoff_openhire_v01/design_refs/OpenHire /345/274/200/350/201/230/345/215/217/350/256/256.dc.html" +823 -0
- openhire-0.1.0/design_handoff_openhire_v01/design_refs/deck-stage.js +2008 -0
- openhire-0.1.0/design_handoff_openhire_v01/design_refs/openhire-mcp README.dc.html +127 -0
- openhire-0.1.0/design_handoff_openhire_v01/design_refs/support.js +1687 -0
- openhire-0.1.0/docs/maintainer-snapshot-refresh.md +39 -0
- openhire-0.1.0/pyproject.toml +45 -0
- openhire-0.1.0/reports/001-/347/234/237/346/234/272/351/252/214/346/224/266/344/270/211/344/273/266/345/245/227.md +116 -0
- openhire-0.1.0/reports/002-ClaudeDesktop-MCP/351/205/215/347/275/256/346/263/250/345/205/245.md +69 -0
- openhire-0.1.0/reports/004-P0/344/277/256/345/244/215-/345/206/231/350/267/257/345/276/204/346/214/202/346/255/273.md +184 -0
- openhire-0.1.0/reports/005-CLI/347/234/237/346/234/272/344/275/223/351/252/2147/346/255/245/345/215/225.md +192 -0
- openhire-0.1.0/reports/006-M4/346/211/223/345/214/205/344/270/216/345/217/221/345/270/203/345/207/206/345/244/207.md +118 -0
- openhire-0.1.0/reports/007-/345/217/221/345/270/203/346/227/245/346/223/215/344/275/234/345/215/225.md +82 -0
- openhire-0.1.0/smithery.yaml +21 -0
- openhire-0.1.0/src/openhire/__init__.py +7 -0
- openhire-0.1.0/src/openhire/ats/__init__.py +49 -0
- openhire-0.1.0/src/openhire/ats/ashby.py +78 -0
- openhire-0.1.0/src/openhire/ats/base.py +202 -0
- openhire-0.1.0/src/openhire/ats/greenhouse.py +57 -0
- openhire-0.1.0/src/openhire/ats/lever.py +68 -0
- openhire-0.1.0/src/openhire/cli.py +931 -0
- openhire-0.1.0/src/openhire/client.py +192 -0
- openhire-0.1.0/src/openhire/config.py +80 -0
- openhire-0.1.0/src/openhire/console.py +98 -0
- openhire-0.1.0/src/openhire/db/__init__.py +13 -0
- openhire-0.1.0/src/openhire/db/migrate.py +49 -0
- openhire-0.1.0/src/openhire/db/models.py +116 -0
- openhire-0.1.0/src/openhire/db/schema.sql +76 -0
- openhire-0.1.0/src/openhire/db/session.py +92 -0
- openhire-0.1.0/src/openhire/db/types.py +77 -0
- openhire-0.1.0/src/openhire/errors.py +15 -0
- openhire-0.1.0/src/openhire/mcp_server.py +159 -0
- openhire-0.1.0/src/openhire/pipeline/__init__.py +42 -0
- openhire-0.1.0/src/openhire/pipeline/apply_channels.py +52 -0
- openhire-0.1.0/src/openhire/pipeline/backfill.py +90 -0
- openhire-0.1.0/src/openhire/pipeline/crawler.py +58 -0
- openhire-0.1.0/src/openhire/pipeline/extract.py +476 -0
- openhire-0.1.0/src/openhire/pipeline/ghost_score.py +50 -0
- openhire-0.1.0/src/openhire/pipeline/hashing.py +28 -0
- openhire-0.1.0/src/openhire/pipeline/ingest.py +299 -0
- openhire-0.1.0/src/openhire/pipeline/ranking.py +58 -0
- openhire-0.1.0/src/openhire/pipeline/rebuild.py +396 -0
- openhire-0.1.0/src/openhire/pipeline/seed_runner.py +74 -0
- openhire-0.1.0/src/openhire/pipeline/snapshot.py +214 -0
- openhire-0.1.0/src/openhire/seed/__init__.py +3 -0
- openhire-0.1.0/src/openhire/seed/candidates.py +143 -0
- openhire-0.1.0/src/openhire/service.py +418 -0
- openhire-0.1.0/tests/conftest.py +19 -0
- openhire-0.1.0/tests/test_appendix_b.py +123 -0
- openhire-0.1.0/tests/test_apply_channel.py +103 -0
- openhire-0.1.0/tests/test_cli_journey.py +118 -0
- openhire-0.1.0/tests/test_client.py +78 -0
- openhire-0.1.0/tests/test_concurrency.py +78 -0
- openhire-0.1.0/tests/test_extract.py +73 -0
- openhire-0.1.0/tests/test_ghost_score.py +94 -0
- openhire-0.1.0/tests/test_ingest.py +187 -0
- openhire-0.1.0/tests/test_mcp_acceptance.py +157 -0
- openhire-0.1.0/tests/test_posting_dates.py +74 -0
- openhire-0.1.0/tests/test_privacy.py +202 -0
- openhire-0.1.0/tests/test_ranking.py +79 -0
- openhire-0.1.0/tests/test_rebuild.py +152 -0
- openhire-0.1.0/tests/test_service.py +249 -0
- openhire-0.1.0/tests/test_snapshot.py +127 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(py --version)",
|
|
5
|
+
"Bash(python -m pip --version)",
|
|
6
|
+
"Bash(git --version)",
|
|
7
|
+
"Bash(psql --version)",
|
|
8
|
+
"Bash(curl -s -m 20 \"https://api.lever.co/v0/postings/ramp?mode=json\" -w \"\\\\nHTTP %{http_code}\\\\n\" -o /tmp/lever.json)",
|
|
9
|
+
"Read(//tmp/**)",
|
|
10
|
+
"Bash(python -c \"import json;d=json.load\\(open\\('/tmp/lever.json'\\)\\);print\\('jobs:',len\\(d\\)\\)\")",
|
|
11
|
+
"Bash(curl -s -m 20 \"https://api.ashbyhq.com/posting-api/job-board/openai\" -w \"\\\\nHTTP %{http_code}\\\\n\" -o /tmp/ashby.json)",
|
|
12
|
+
"Bash(python -c \"import json;d=json.load\\(open\\('/tmp/ashby.json'\\)\\);print\\('jobs:',len\\(d.get\\('jobs',[]\\)\\)\\)\")",
|
|
13
|
+
"Bash(python -m venv .venv)",
|
|
14
|
+
"Bash(./.venv/Scripts/python.exe -m pip install --quiet --upgrade pip)",
|
|
15
|
+
"Bash(./.venv/Scripts/python.exe -m pip install --quiet -e \".[dev]\")",
|
|
16
|
+
"Bash(./.venv/Scripts/python.exe -m pip install -e \".[dev]\")",
|
|
17
|
+
"Bash(export PYTHONIOENCODING=utf-8)",
|
|
18
|
+
"Bash(./.venv/Scripts/python.exe -c \"import openhire; from openhire.db import init_db; init_db\\(\\); print\\('DB init OK, v'+openhire.__version__\\)\")",
|
|
19
|
+
"Bash(rm -f ~/.openhire/openhire.db)",
|
|
20
|
+
"Bash(./.venv/Scripts/ohp.exe seed *)",
|
|
21
|
+
"Bash(./.venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/verify_m1.py\")",
|
|
22
|
+
"Bash(./.venv/Scripts/ohp.exe ingest *)",
|
|
23
|
+
"Bash(./.venv/Scripts/ohp.exe status *)",
|
|
24
|
+
"Bash(./.venv/Scripts/python.exe -m pytest -q)",
|
|
25
|
+
"Bash(./.venv/Scripts/python.exe -)",
|
|
26
|
+
"Bash(./.venv/Scripts/ohp.exe fix-apply-channels *)",
|
|
27
|
+
"Bash(./.venv/Scripts/python.exe -c \"import importlib.metadata as m; print\\('mcp', m.version\\('mcp'\\)\\)\")",
|
|
28
|
+
"Bash(./.venv/Scripts/python.exe -c \"import openhire.mcp_server, openhire.service, openhire.cli; print\\('imports OK'\\)\")",
|
|
29
|
+
"Bash(./.venv/Scripts/ohp.exe search *)",
|
|
30
|
+
"Bash(./.venv/Scripts/python.exe -m pytest tests/test_mcp_acceptance.py::test_all_five_acceptance_scripts -vv)",
|
|
31
|
+
"Bash(unset OPENHIRE_DATABASE_URL)",
|
|
32
|
+
"Bash(./.venv/Scripts/python.exe -m pytest --co -q)",
|
|
33
|
+
"Bash(export OPENHIRE_DATABASE_URL=\"sqlite+pysqlite:///C:/Users/gdche/.openhire/openhire.db\")",
|
|
34
|
+
"Bash(./.venv/Scripts/python.exe -c ' *)",
|
|
35
|
+
"Bash(xxd)",
|
|
36
|
+
"Bash(python -c ' *)",
|
|
37
|
+
"Bash(cd *)",
|
|
38
|
+
"Bash(./.venv/Scripts/ohp.exe extract-sample *)",
|
|
39
|
+
"Bash(./.venv/Scripts/python.exe -m pytest tests/test_rebuild.py tests/test_extract.py -q)",
|
|
40
|
+
"Bash(./.venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/verify_rebuild.py\")",
|
|
41
|
+
"Bash(./.venv/Scripts/python.exe -c \"import openhire.cli, openhire.client; print\\('import OK'\\)\")",
|
|
42
|
+
"Bash(./.venv/Scripts/ohp.exe --help)",
|
|
43
|
+
"Bash(./.venv/Scripts/python.exe -m pytest tests/test_client.py tests/test_cli_journey.py -q)",
|
|
44
|
+
"Bash(.venv/Scripts/python.exe -c 'from openhire.db import session_scope, Job; from sqlalchemy import select, func; import openhire.config as c; print\\('\\\\''DB URL:'\\\\'', c.DATABASE_URL\\); *)",
|
|
45
|
+
"Bash(.venv/Scripts/ohp.exe --help)",
|
|
46
|
+
"Bash(.venv/Scripts/ohp.exe status *)",
|
|
47
|
+
"Bash(.venv/Scripts/ohp.exe search *)",
|
|
48
|
+
"Bash(.venv/Scripts/python.exe -m pytest -q)",
|
|
49
|
+
"Bash(timeout 200 .venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/repro.py\")",
|
|
50
|
+
"Bash(.venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/repro2.py\")",
|
|
51
|
+
"Bash(.venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/repro3.py\")",
|
|
52
|
+
"Bash(.venv/Scripts/python.exe -m pytest tests/test_concurrency.py -v)",
|
|
53
|
+
"Bash(timeout 120 .venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/repro_t3.py\")",
|
|
54
|
+
"Bash(.venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/diag.py\")",
|
|
55
|
+
"Bash(.venv/Scripts/python.exe -m pytest tests/test_posting_dates.py -q)",
|
|
56
|
+
"Bash(.venv/Scripts/python.exe -c \"import sqlite3; c=sqlite3.connect\\('C:/Users/gdche/.openhire/openhire.db'\\); c.execute\\('PRAGMA wal_checkpoint\\(TRUNCATE\\)'\\); c.close\\(\\); print\\('checkpointed'\\)\")",
|
|
57
|
+
"Bash(cp \"C:/Users/gdche/.openhire/openhire.db\" \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/pre_backfill_backup.db\")",
|
|
58
|
+
"Bash(timeout 300 .venv/Scripts/ohp.exe backfill-dates)",
|
|
59
|
+
"Bash(.venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/regress.py\")",
|
|
60
|
+
"Bash(.venv/Scripts/python.exe -c \"import sqlite3; c=sqlite3.connect\\('C:/Users/gdche/.openhire/openhire.db'\\); c.execute\\('PRAGMA wal_checkpoint\\(TRUNCATE\\)'\\); c.close\\(\\); print\\('real DB checkpointed'\\)\")",
|
|
61
|
+
"Bash(.venv/Scripts/python.exe -c ' *)",
|
|
62
|
+
"Bash(timeout 120 .venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/rf_probe.py\")",
|
|
63
|
+
"Bash(.venv/Scripts/ohp.exe extract-role-family *)",
|
|
64
|
+
"Bash(echo \"EXIT $?\")",
|
|
65
|
+
"Bash(.venv/Scripts/python.exe -m pytest tests/test_appendix_b.py -q)",
|
|
66
|
+
"Bash(.venv/Scripts/python.exe \"C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/rf_verify.py\")",
|
|
67
|
+
"Bash(.venv/Scripts/python.exe -m pytest tests/test_snapshot.py -q)",
|
|
68
|
+
"Bash(.venv/Scripts/python.exe -c \"import sqlite3;c=sqlite3.connect\\('C:/Users/gdche/.openhire/openhire.db'\\);print\\('watches',c.execute\\('SELECT count\\(*\\) FROM watches'\\).fetchone\\(\\)[0],'applications',c.execute\\('SELECT count\\(*\\) FROM applications'\\).fetchone\\(\\)[0],'jobs',c.execute\\('SELECT count\\(*\\) FROM jobs'\\).fetchone\\(\\)[0]\\)\")",
|
|
69
|
+
"Bash(.venv/Scripts/ohp.exe snapshot-build *)",
|
|
70
|
+
"Bash(C:/Users/gdche/AppData/Local/Temp/claude/C--openhire/dc7ce18d-7256-41ad-8535-30df941b25d2/scratchpad/freshvenv/Scripts/ohp.exe --help)",
|
|
71
|
+
"Bash(.venv/Scripts/python.exe -m pytest tests/test_privacy.py tests/test_ranking.py tests/test_snapshot.py -q)",
|
|
72
|
+
"Bash(.venv/Scripts/python.exe -c \"import tomllib; d=tomllib.load\\(open\\('pyproject.toml','rb'\\)\\); print\\('name:', d['project']['name']\\); print\\('urls:', d['project'].get\\('urls'\\)\\); print\\('scripts:', d['project']['scripts']\\)\")",
|
|
73
|
+
"Bash(rm -f __TRACKED_VAR__/dist/*.whl __TRACKED_VAR__/dist/*.tar.gz)",
|
|
74
|
+
"Bash(.venv/Scripts/python.exe -m build --outdir __TRACKED_VAR__/dist)",
|
|
75
|
+
"Bash(.venv/Scripts/python.exe -m pip install -q twine)",
|
|
76
|
+
"Bash(git init *)",
|
|
77
|
+
"Bash(git config *)",
|
|
78
|
+
"Bash(git add *)",
|
|
79
|
+
"Bash(grep -iE \"\\\\.env$|\\\\.pypirc|token|\\\\.db$|\\\\.db\\\\.gz$\")",
|
|
80
|
+
"Bash(winget install *)",
|
|
81
|
+
"Bash(echo \"EXIT=$?\")"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# OpenHire configuration — copy to .env (never commit real secrets).
|
|
2
|
+
|
|
3
|
+
# --- Database ---
|
|
4
|
+
# Default: local SQLite at ~/.openhire/openhire.db. For production use Postgres:
|
|
5
|
+
# OPENHIRE_DATABASE_URL=postgresql+psycopg://openhire:secret@localhost:5432/openhire
|
|
6
|
+
|
|
7
|
+
# --- LLM extraction ---
|
|
8
|
+
# Extractor: auto (Anthropic if a key is present, else heuristic) | anthropic | heuristic
|
|
9
|
+
OPENHIRE_EXTRACTOR=auto
|
|
10
|
+
# Key is injected only at extraction time; nothing is ever sent about the user.
|
|
11
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
12
|
+
# OPENHIRE_EXTRACTION_MODEL=claude-haiku-4-5-20251001
|
|
13
|
+
|
|
14
|
+
# --- Crawl politeness (defaults shown) ---
|
|
15
|
+
OPENHIRE_MIN_TENANT_INTERVAL_MIN=30
|
|
16
|
+
OPENHIRE_MAX_CONCURRENCY=5
|
|
17
|
+
OPENHIRE_HTTP_TIMEOUT=30
|
|
18
|
+
|
|
19
|
+
# --- Freshness tiers ---
|
|
20
|
+
OPENHIRE_HOT_WINDOW_DAYS=7
|
|
21
|
+
OPENHIRE_HOT_INTERVAL_HOURS=6
|
|
22
|
+
OPENHIRE_COLD_INTERVAL_HOURS=24
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
|
|
11
|
+
# Env / secrets — NEVER commit API keys or PyPI tokens
|
|
12
|
+
.env
|
|
13
|
+
*.env
|
|
14
|
+
!.env.example
|
|
15
|
+
.pypirc
|
|
16
|
+
*.pem
|
|
17
|
+
*.key
|
|
18
|
+
*_token
|
|
19
|
+
*.token
|
|
20
|
+
|
|
21
|
+
# Local state / data
|
|
22
|
+
*.db
|
|
23
|
+
*.db-wal
|
|
24
|
+
*.db-shm
|
|
25
|
+
*.db.gz
|
|
26
|
+
*.sqlite
|
|
27
|
+
*.sqlite3
|
|
28
|
+
.openhire/
|
|
29
|
+
~/.openhire/
|
|
30
|
+
|
|
31
|
+
# Test / tooling
|
|
32
|
+
.pytest_cache/
|
|
33
|
+
.ruff_cache/
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
.coverage
|
|
36
|
+
htmlcov/
|
|
37
|
+
|
|
38
|
+
# OS / editor
|
|
39
|
+
.DS_Store
|
|
40
|
+
Thumbs.db
|
|
41
|
+
.idea/
|
|
42
|
+
.vscode/
|
openhire-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# CLAUDE.md — OpenHire 工作约定(每个会话必读)
|
|
2
|
+
|
|
3
|
+
## 新会话恢复指引(开工前先做这三步)
|
|
4
|
+
|
|
5
|
+
1. **先读 `README.md`** — 了解产品定位、五个协议字段、三条隐私红线、安装与工具(公开版 README)。
|
|
6
|
+
2. **再读 `PROGRESS.md`** — 了解已完成到哪一步、关键决策与理由、下一步、待用户确认事项。
|
|
7
|
+
3. **禁止重做已完成的工作。** M1/M2/M3 已验收通过(见 PROGRESS.md 的验收证据)。除非用户明确要求返工,不要重建已完成的里程碑。
|
|
8
|
+
|
|
9
|
+
## 常设工作制度(持续遵守)
|
|
10
|
+
|
|
11
|
+
1. **进度记录:** 每完成一个里程碑、或每个工作日结束时,更新 `PROGRESS.md`:
|
|
12
|
+
- 日期
|
|
13
|
+
- 已完成(含验收证据:测试数、实测输出、花费等可核对的事实)
|
|
14
|
+
- 关键决策及理由
|
|
15
|
+
- 下一步
|
|
16
|
+
- 待用户确认事项
|
|
17
|
+
控制在一页以内;新进展追加在顶部(倒序),旧条目保留。
|
|
18
|
+
2. **恢复指引常驻:** 本文件顶部的「新会话恢复指引」始终保留并保持最新。
|
|
19
|
+
3. **每次会话结束前主动提醒用户:** 「今日请备份 `C:\openhire` 到 U 盘。」
|
|
20
|
+
4. **里程碑节奏:** 每完成一个里程碑停下,对照验收标准向用户汇报后再继续。
|
|
21
|
+
5. **汇报归档:** 每次完成任务后的完整汇报,除在终端显示外,**同时写入 `C:\openhire\reports\`**。
|
|
22
|
+
- 文件名 = 递增编号 + 主题,如 `001-真机验收三件套.md`、`002-M4打包.md`。
|
|
23
|
+
- Markdown 格式,**自足完整**:不依赖终端上下文,单独打开也能看懂(含背景、做了什么、证据、结论、下一步)。
|
|
24
|
+
- 每次干完活的**最后一行**告诉用户:「汇报已写入 C:\openhire\reports\xxx.md」。
|
|
25
|
+
- 编号取 `reports\` 里现有最大编号 +1(补零三位)。
|
|
26
|
+
|
|
27
|
+
## 三条隐私红线(CI 强制,永不可破)
|
|
28
|
+
|
|
29
|
+
1. 简历或任何 PII **绝不**经过服务端 —— 只有匿名指纹过网。
|
|
30
|
+
2. 排序**绝不**是付费参数 —— 只是 f(匹配度, 新鲜度) 的纯函数,签名锁死。
|
|
31
|
+
3. 雇主只为已授权、已交付的结果付费 —— 绝不为曝光付费(v0.1 无任何计费代码)。
|
|
32
|
+
|
|
33
|
+
对应自动化测试见 `tests/test_privacy.py`、`tests/test_ranking.py`。改动排序/服务层/apply 后必须跑 `pytest` 确认全绿。
|
|
34
|
+
|
|
35
|
+
## 关键路径与事实
|
|
36
|
+
|
|
37
|
+
- 代码根:`C:\openhire\src\openhire`
|
|
38
|
+
- 数据库(默认,绝对路径):`C:\Users\gdche\.openhire\openhire.db`(11,825 职位 / 96 公司 / 全量 DeepSeek 抽取)
|
|
39
|
+
- CLI 可执行文件:`C:\openhire\.venv\Scripts\ohp.exe`(**未在系统 PATH 上** —— 接入 Claude Desktop 时须写全路径)
|
|
40
|
+
- 抽取后端:DeepSeek(`deepseek-chat`,key 从 `.env` 的 `DEEPSEEK_API_KEY` 读;serve 阶段不需要 key)
|
|
41
|
+
- 设计交接文档:`design_handoff_openhire_v01\README.md`(唯一权威规格);`design_refs\*.html` 仅供交互参考,**不复用其代码**。
|
openhire-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openhire
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OpenHire v0.1 「哨兵」— an MCP job protocol server. Résumés never leave your device.
|
|
5
|
+
Project-URL: Homepage, https://github.com/gzchenhao/openhire
|
|
6
|
+
Project-URL: Repository, https://github.com/gzchenhao/openhire
|
|
7
|
+
Project-URL: Issues, https://github.com/gzchenhao/openhire/issues
|
|
8
|
+
Author: OpenHire
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: ashby,ats,greenhouse,jobs,lever,mcp,recruiting
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Requires-Dist: anthropic>=0.39
|
|
13
|
+
Requires-Dist: apscheduler>=3.10
|
|
14
|
+
Requires-Dist: httpx>=0.27
|
|
15
|
+
Requires-Dist: mcp>=1.2.0
|
|
16
|
+
Requires-Dist: python-dotenv>=1.0
|
|
17
|
+
Requires-Dist: rich>=13.7
|
|
18
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
19
|
+
Requires-Dist: typer>=0.12
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
23
|
+
Provides-Extra: postgres
|
|
24
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# openhire-mcp
|
|
28
|
+
|
|
29
|
+
> **Jobs come to you. Your résumé never passes through OpenHire's servers, and we never store it.**
|
|
30
|
+
> 岗位来找你,简历不经过我们的服务器,也不被我们存储。
|
|
31
|
+
|
|
32
|
+
    
|
|
33
|
+
|
|
34
|
+
An MCP server that turns your AI assistant (Claude, Cursor, Windsurf) into a private radar
|
|
35
|
+
for **remote AI / Infra roles** — sourced directly from ~100 company career sites and their
|
|
36
|
+
public ATS APIs (Greenhouse / Lever / Ashby). **No account. No signup. No résumé upload. Ever.**
|
|
37
|
+
|
|
38
|
+
Matching runs on your machine; only an anonymous fingerprint and hard filters ever reach the
|
|
39
|
+
server. This is the v0.1 「哨兵 / Sentinel」 reference implementation — see
|
|
40
|
+
`design_handoff_openhire_v01/README.md` for the full protocol spec.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Quickstart — under a minute
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 1. Install (pipx keeps it isolated and puts `ohp` on your PATH)
|
|
48
|
+
pipx install openhire
|
|
49
|
+
|
|
50
|
+
# 2. Get a job index. Default: download the public snapshot, then refresh it live.
|
|
51
|
+
ohp bootstrap
|
|
52
|
+
# --fresh crawl the public ATS from scratch (heuristic, free, no snapshot)
|
|
53
|
+
# --deepseek higher-quality extraction using YOUR OWN DEEPSEEK_API_KEY
|
|
54
|
+
|
|
55
|
+
# 3. Use it directly…
|
|
56
|
+
ohp search --required-skills rust,k8s --remote --role-family engineering
|
|
57
|
+
|
|
58
|
+
# …or connect it to an MCP client:
|
|
59
|
+
ohp serve
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For **Claude Desktop**, add to `claude_desktop_config.json`:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"openhire": {
|
|
68
|
+
"command": "ohp",
|
|
69
|
+
"args": ["serve"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> On Windows the config file lives at `%APPDATA%\Claude\claude_desktop_config.json` (for the
|
|
76
|
+
> Microsoft Store build it is under `…\Packages\<Claude package>\LocalCache\Roaming\Claude\`).
|
|
77
|
+
> Fully quit and reopen Claude Desktop after editing.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## What it does
|
|
82
|
+
|
|
83
|
+
| Tool | What it gives you |
|
|
84
|
+
|------|-------------------|
|
|
85
|
+
| `search_jobs` | Hard-filter the live index; every result carries `verified_at`, `datePosted`, `days_open`, `ghost_score`, `remote_scope`, `eligible_regions`, `apply_channel`. Filter by `required_skills` (AND), `role_family`, `remote_scope`, `min_salary` + `currency`. |
|
|
86
|
+
| `watch_intent` | Register a standing intent once — new matching jobs are waiting next time you check, even after you close the terminal. Accepts `required_skills` / `role_family` so sales / solutions roles stay out. |
|
|
87
|
+
| `check_watches` | Pull the matches that are new since your last check (client-pull; stdio has no push). |
|
|
88
|
+
| `authorize_application` | One explicit confirmation per job. It records your authorization and returns the employer's **own** application URL — you apply as yourself. It **cannot** accept a résumé. |
|
|
89
|
+
| `get_company_info` | Aggregate, anonymous trust signals for one employer (`ghost_score_avg`, `active_jobs`, `index_built_at`). Never any candidate data. |
|
|
90
|
+
|
|
91
|
+
Optional, entirely local: `ohp init --scan <dir>` derives a **skill fingerprint** from your
|
|
92
|
+
own repos. You never write a résumé; the code never leaves your machine — only an anonymous
|
|
93
|
+
vector does.
|
|
94
|
+
|
|
95
|
+
## The five protocol fields
|
|
96
|
+
|
|
97
|
+
Every listing is valid `schema.org/JobPosting`, plus:
|
|
98
|
+
|
|
99
|
+
- `verified_at` — last moment confirmed live on the employer's own site
|
|
100
|
+
- `source` — `employer_site | ats_public_api` (never a job board)
|
|
101
|
+
- `ghost_score` — 0–1 likelihood the listing is not a real, active hire (ages off the **real**
|
|
102
|
+
posting date; lower is better)
|
|
103
|
+
- `response_sla_days` — employer's committed response window (v0.1: always null)
|
|
104
|
+
- `apply_channel` — always the employer's own application URL, deep-linked to the specific job
|
|
105
|
+
|
|
106
|
+
## Privacy model
|
|
107
|
+
|
|
108
|
+
| | |
|
|
109
|
+
|---|---|
|
|
110
|
+
| **Résumé / PII upload** | **never** — matching runs locally; a résumé never transits the server, and we never store one |
|
|
111
|
+
| **What the server sees** | one anonymous, client-generated fingerprint + hard filters |
|
|
112
|
+
| **Repo scan** | local-only · personal projects · explicit consent · opt-out anytime |
|
|
113
|
+
| **Job sources** | first-party only: employer career pages + public ATS APIs (Greenhouse / Lever / Ashby) |
|
|
114
|
+
|
|
115
|
+
## First-run data — the snapshot vs. fresh
|
|
116
|
+
|
|
117
|
+
`ohp bootstrap` (default) downloads a small **public** index snapshot (a GitHub Release
|
|
118
|
+
asset — `companies` + `jobs` only, **zero** user data) and then runs one incremental crawl to
|
|
119
|
+
refresh `verified_at` / delisting. `--fresh` skips the snapshot and crawls the public ATS from
|
|
120
|
+
scratch with the free offline heuristic extractor. Either way: no account, no PII.
|
|
121
|
+
|
|
122
|
+
## Three rules this project will never break
|
|
123
|
+
|
|
124
|
+
1. Your résumé stays on your machine — it never transits the server, and we never store it.
|
|
125
|
+
2. Ranking is not for sale — it is only `f(match_quality, freshness)`, a locked pure function.
|
|
126
|
+
3. Employers pay only for authorized, delivered outcomes — never for exposure. (v0.1 has no
|
|
127
|
+
billing at all.)
|
|
128
|
+
|
|
129
|
+
These are enforced by CI (`tests/test_privacy.py`, `tests/test_ranking.py`,
|
|
130
|
+
`tests/test_snapshot.py`).
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
python -m venv .venv && . .venv/Scripts/activate # Windows
|
|
136
|
+
pip install -e ".[dev]"
|
|
137
|
+
pytest # privacy red lines + ranking + snapshot must be green
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Set `OPENHIRE_DATABASE_URL=postgresql+psycopg://…` to run against Postgres instead of the
|
|
141
|
+
default local SQLite file (`~/.openhire/openhire.db`).
|
|
142
|
+
|
|
143
|
+
## Roadmap
|
|
144
|
+
|
|
145
|
+
- **v0.2** — CN ATS adapters (Beisen / Moka) · `ghost_score` public beta
|
|
146
|
+
- **v0.3** — Employer claim + verified badges · response-SLA enforcement (7-day auto-delist)
|
|
147
|
+
- **v1.0** — Open, vendor-neutral schema extension for AI-readable job postings
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT © OpenHire Protocol · PRs welcome.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# PROGRESS · OpenHire `openhire-mcp` v0.1「哨兵」
|
|
2
|
+
|
|
3
|
+
> 一页进度台账。新条目置顶。恢复会话请先读 `README.md` 再读本文件,勿重做已完成工作。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 2026-07-12 — 真机验收修复 campaign(报告 003 → 逐项修,详见 reports/004)
|
|
8
|
+
|
|
9
|
+
**P0-1 写路径挂死 — ✅ 已修复**
|
|
10
|
+
- 根因:真库为 SQLite rollback-journal 模式,长驻 stdio server 的流式读(search/check 遍历 11,825 行)阻塞 watch_intent 写;async 事件循环里表现为 4 分钟挂死。apply 秒回是因当时无重叠读,证伪「写路径整体挂死」。
|
|
11
|
+
- 复现对照:rollback-journal 读活跃时写 5.46s 后 `database is locked`;WAL 下 0.004s 提交。
|
|
12
|
+
- 修复:`db/session.py` SQLite 引擎启用 `journal_mode=WAL` + `busy_timeout=5000` + `synchronous=NORMAL` + `check_same_thread=False`。真库已持久转 WAL。
|
|
13
|
+
- 验证:新增 `tests/test_concurrency.py`(2 项);全量 **100 passed**;T3 真库重跑(fp `88ba1102edb9205d`,4 线程读压下)watch_intent **0.046s** 返回、只落匿名数据。
|
|
14
|
+
|
|
15
|
+
**P0-2 datePosted 造假 + ghost_score 恒 0 — ✅ 已修复**
|
|
16
|
+
- 根因:入库丢弃 ATS 真实发布日,`first_seen_at`/`verified_at` 全写抓取时刻(全库 1 个 distinct);ghost 用 first_seen(岗龄≈0)+ relist=0 → 恒 0。DeepSeek 未污染日期。
|
|
17
|
+
- 修复:`Job` 加 `posted_at`/`updated_at` 列 + migrate;ingest 落库真实发布日、ghost 以真实岗龄为锚;`job_posting` 的 `datePosted` 用 posted_at、新增 `days_open`;新增 **免费** `ohp backfill-dates` 重抓 ATS 回填(不重跑抽取)。
|
|
18
|
+
- 回填真库:96/0,真实发布日 11446,ghost 重算 11825。回归:`mongodb:7727896` days_open=110(≈3.6 月)、ghost 0→0.3565;全库 ghost min0/median0.081/max1.0,**57.7% >0**。新增 `tests/test_posting_dates.py`。
|
|
19
|
+
|
|
20
|
+
**API 定型 5 项免费项 — ✅ 完成(107 passed)**
|
|
21
|
+
- required_skills(AND);min_salary 加 currency + 拆 require_stated_salary(未标薪资默认保留);remote_scope 枚举 + eligible_regions;MCP `apply`→`authorize_application`;get_company_info 去 verified、last_crawled_at→index_built_at;watch_intent 加 fingerprint_notice。
|
|
22
|
+
- role_family:参数+空列已冻结,**数据待跑**——报价 ≈¥29(¥50 硬上限),**等用户批准**。
|
|
23
|
+
|
|
24
|
+
**role_family 抽取 — ✅ 已批准跑完**:新增 `classify_role_family_with_usage` + 断点续跑成本封顶的 `rebuild_role_family` + CLI `ohp extract-role-family`。全量 11,825/11,825 标注、0 失败、**¥17.53**(低于 ¥29 估价)。分布 eng4207/sales3373/ops2621/…。回归达标:`--role-family engineering` 搜索 0 条 sales;两个附录 B 销售岗均 role_family=sales。
|
|
25
|
+
|
|
26
|
+
**附录 B 回归 — ✅**:`tests/test_appendix_b.py`(5 job_id 各锁一个契约:datePosted/销售岗排除/Ashby 写路径/荷兰无薪资岗保留-排除)。
|
|
27
|
+
|
|
28
|
+
**文案红线 — ✅**:README 标语 + CLI 帮助/横幅「简历永不离开设备」→「简历不经过我们服务器,也不被我们存储」。
|
|
29
|
+
|
|
30
|
+
**测试基线:112 passed。** 报告 003 的 P0-1/P0-2/6 项 API 定型/文案/附录 B 回归全部完成。
|
|
31
|
+
|
|
32
|
+
**CLI 真机体验 7 步单 — ✅ 交付**(`reports/005`,新手向:每步一命令 + 预期输出 + 出错时截图哪里;预期输出为真库副本+临时目录实跑截取)。
|
|
33
|
+
**顺手修一处小摩擦:** `ohp apply` 原来无论是否 `--no-open` 都打印「申请页(已打开)」;改为按实际结果打印「已在浏览器打开」/「请手动打开」。112 passed 不变。
|
|
34
|
+
|
|
35
|
+
**下一步:** 用户按 `reports/005` 亲跑最后一轮验收 → 通过即开 **M4**。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2026-07-12(续)— M4 就绪修复 + M4 启动
|
|
40
|
+
|
|
41
|
+
**两处 M4 就绪修复 — ✅(114 passed)**
|
|
42
|
+
- ① `ohp search` 回显行漏掉 `--role-family`/`--limit` → 补上。
|
|
43
|
+
- ② `ohp watch` + `watch_intent` 加可选 `required_skills`(AND) + `role_family`(非破坏;`_clean_filters` 白名单 + `check_watches` 透传)。实测 watch 加这两参数后命中不再混入销售/SA 岗。新增 2 项服务测试。
|
|
44
|
+
|
|
45
|
+
**M4 代码就绪 — ✅(118 passed,详见 reports/006)**
|
|
46
|
+
- 首跑数据(用户拍板混合):快照=GitHub Release 资产、仅 jobs/companies、**零用户态**(构建时红线校验,违规即失败)。`ohp bootstrap` 默认 拉快照→增量刷新→打印快照龄;`--fresh` 现抓(启发式免费);`--deepseek` 自带 key。开跑前先声明将做什么。新增 `pipeline/snapshot.py`、`ohp bootstrap`、`ohp snapshot-build`、`db.session.dispose_engine`、`config.SNAPSHOT_URL`、`tests/test_snapshot.py`(4)。
|
|
47
|
+
- 实测:真快照 96/11825·13.2MB·零用户态校验通过;bootstrap 全流程(安装 96/11825 龄3天→刷新 新323/更340/下线452)✓;护栏(已有索引提示 --force)✓。
|
|
48
|
+
- pipx:wheel 73KB 无数据大文件 → 全新隔离 venv 安装 → `ohp version`/命令齐全 ✓。终端用户 pipx 装后 config 可直接 `command: "ohp"`。
|
|
49
|
+
- README:design_refs→真 Markdown,隐私口径收窄。
|
|
50
|
+
- **待用户拍板**:GitHub 仓库地址、是否发 PyPI、是否落地 `smithery.yaml`(草案在 006)、快照刷新节奏。MCP 目录提交材料清单已备好、**未提交**。
|
|
51
|
+
|
|
52
|
+
**M4 发布准备 — 四项拍板已落地(详见 reports/007)**
|
|
53
|
+
- ① GitHub:个人账号下 `openhire` 仓库(URL 待用户名,`pyproject`/README/`smithery`/`config` 里占位 `OWNER`,拿到用户名我一次性 wire)。
|
|
54
|
+
- ② PyPI:查得 `openhire` **未被占用 → 沿用**(无需改名);CLI 保持 `ohp`。
|
|
55
|
+
- ③ `smithery.yaml` **已落地**(stdio + 可选 OPENHIRE_DATABASE_URL)。
|
|
56
|
+
- ④ 快照每周手动刷新:维护者操作卡 `docs/maintainer-snapshot-refresh.md`。
|
|
57
|
+
- 另:`.gitignore` 补 `.pypirc`/token/WAL/`*.db.gz` 防护;`pyproject` 加 `[project.urls]`;sdist+wheel 构建通过。
|
|
58
|
+
- **发布日操作单 `reports/007`**:GitHub/PyPI 双新手逐步指引,标注【可代劳】步骤;红线 token 只进本机 env/.pypirc、不进 git。
|
|
59
|
+
|
|
60
|
+
**下一步:** 用户给 GitHub 用户名 → 我 wire URL;建仓库/推代码/建 Release 传快照;PyPI 注册+2FA+token → 我 twine 上传。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 2026-07-11 — M4 前真机验收准备
|
|
65
|
+
|
|
66
|
+
**已完成**
|
|
67
|
+
- 建立常设工作制度:新增 `CLAUDE.md`(新会话恢复指引 + 工作约定 + 隐私红线 + 关键路径),补记本 `PROGRESS.md`(回填 M1–M3)。
|
|
68
|
+
- 核对真机事实:默认 DB URL = `sqlite:///C:/Users/gdche/.openhire/openhire.db`(绝对路径,11,825 职位 / 96 公司 / 全量 deepseek 抽取);`ohp.exe` 在 `.venv\Scripts\`,**不在系统 PATH**。
|
|
69
|
+
|
|
70
|
+
**关键决策**
|
|
71
|
+
- Claude Desktop 接入 `command` 用 `ohp.exe` 全路径(因未上 PATH,且 M4 才做 pipx/uvx)。这是**预判的头号发布阻塞项**:陌生开发者裸装无法直接 `command: "ohp"`。
|
|
72
|
+
- serve 阶段 DB 走绝对路径默认值 → Claude Desktop 从任意 cwd 启动都命中同一份数据,无需在 config 里配 env。
|
|
73
|
+
|
|
74
|
+
**下一步**
|
|
75
|
+
- 交付真机验收三件套(Claude Desktop 分步接入指引 + 5 条验收对话由用户亲跑 + CLI `init→watch→apply` 体验清单)。
|
|
76
|
+
- 用户跑完反馈摩擦点 → 按 v0.1 发布阻塞项处理 → 再开 M4(pipx/uvx 打包、正式 README、提交 MCP 目录)。
|
|
77
|
+
|
|
78
|
+
**已发现并修复的摩擦(发布阻塞项 #1)**
|
|
79
|
+
- `ohp search` / `check` 只打印 apply URL,**不打印 `job_id`**,而 `ohp apply` 恰需 `job_id` 参数 → 新手搜完不知道 apply 该敲什么。已修 `_print_job`:每条结果新增 `id=<job_id>` 及 `# 投递:ohp apply <job_id>` 提示行。98 测试仍全绿。
|
|
80
|
+
|
|
81
|
+
**待用户确认**
|
|
82
|
+
- 真机 5 条对话与 CLI 清单跑下来的摩擦点(报错/看不懂/步骤多),逐条反馈。
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 2026-07-10 — M3 验收通过(求职者旅程 CLI)
|
|
87
|
+
|
|
88
|
+
**已完成(验收证据)**
|
|
89
|
+
- 求职者旅程 CLI:`ohp init --scan <dir>` / `watch` / `check` / `apply` / `status`,客户端状态在 `~/.openhire`。
|
|
90
|
+
- `init` 本地扫描仓库导出技能指纹(按扩展名 + 依赖清单 → 技能标签,规范化),先确认后落盘;代码/内容不出机器,匿名 id 形如 `#a3f9`。
|
|
91
|
+
- `apply <job_id>` 打开链接**前**先在终端打印 JD 摘要(公司/职位/薪资/技能/JD 要点),`[Y/n]` 授权后再跳 —— 修复 Greenhouse embed 表单无 JD 上下文的问题。落 receipt 到 `~/.openhire/receipts.jsonl` + 服务端 `Application`。
|
|
92
|
+
- 实测链路:watch → 新 JD 入库 → `check` 增量浮现;apply 全流程 + receipt。**98 项测试全绿**。
|
|
93
|
+
|
|
94
|
+
**关键决策**
|
|
95
|
+
- `check_watches` 以 `last_notified_at` 为游标(None=基线返回当前全部匹配),修复首拉返回 0 的问题。
|
|
96
|
+
- stdio 无服务端推送 → `check` 为客户端主动拉取。
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 2026-07-09 — DeepSeek 全量抽取重建
|
|
101
|
+
|
|
102
|
+
**已完成(验收证据)**
|
|
103
|
+
- 新增 DeepSeek OpenAI 兼容后端(`base_url=https://api.deepseek.com`, `model=deepseek-chat`, key 从 `DEEPSEEK_API_KEY`)。
|
|
104
|
+
- 先抽 100 条对比启发式确认质量提升 → 分批断点续跑全量:**11,825/11,825 转换,0 失败,实际花费 ¥27.03**(硬上限 ¥50,超则停)。
|
|
105
|
+
- 抽取结果保留原启发式值作回退列(`*_fallback`),可对比/回滚。
|
|
106
|
+
- 合并策略:skills 永远用 LLM;remote/salary 保留权威 ATS 值(除非 ATS 缺失)—— 修复朴素全量替换导致的 remote(48 改)/salary(43 失) 回退。
|
|
107
|
+
- 复测「远程 Rust/K8s 基础设施岗」搜索:Top 8 中销售岗归零,回归确认。
|
|
108
|
+
|
|
109
|
+
**关键决策**
|
|
110
|
+
- 用最便宜 `deepseek-chat`;先 100 条抽样报差异、确认再全量;超 ¥50 先停问用户 —— 均按用户要求落地。
|
|
111
|
+
- 共享连接池 `httpx.Client` + 24 workers(原每调用新建连接过慢)。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 2026-07(M2)— MCP 服务端(四工具 + apply)
|
|
116
|
+
|
|
117
|
+
**已完成(验收证据)**
|
|
118
|
+
- FastMCP 服务 `openhire`,5 个工具:`search_jobs` / `get_company_info` / `watch_intent` / `check_watches` / `apply`。
|
|
119
|
+
- 通过 5 条 Claude Desktop 验收对话脚本(搜索 / 公司信任 / 常驻意向 / 跨会话拉取 / 隐私红线对抗)+ 2 条内部验收。
|
|
120
|
+
- 红线②落地:排序函数签名锁死 `f(match_quality, freshness)`,无任何付费参数(`test_ranking.py`)。
|
|
121
|
+
- `search_jobs` 服务端只做硬过滤 + 固定排序,精排留客户端。
|
|
122
|
+
- `apply` 结构上无 resume 参数 —— 无法接收简历;`assert_no_resume` 拒绝 resume/cv/file/email 等键 → `ERR_RESUME_NEVER_TRANSMITTED`。
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 2026-07(M1)— 数据管线
|
|
127
|
+
|
|
128
|
+
**已完成(验收证据)**
|
|
129
|
+
- 从公开 ATS API 入库(Greenhouse / Lever / Ashby),零爬虫;96 家已验证公司(含 anthropic/openai/cohere/mistral 等)。
|
|
130
|
+
- 五协议字段:`verified_at` / `source` / `ghost_score` / `response_sla_days`(v0.1 恒 NULL) / `apply_channel`。
|
|
131
|
+
- 变更/下线/重挂检测;`ghost_score = min(1.0, 0.15*relist_count + max(0, days_since_first_seen-45)/90*0.5)`(纯函数、可注入时钟)。
|
|
132
|
+
- **apply_channel 修复**:21 家嵌入式公司(4,188 职位)全部重生成,DB 不变量「0 个非 ATS 宿主」,全部 HTTP 200 直达具体职位;补自动化测试「apply_channel 必须直达具体职位」。
|
|
133
|
+
- Postgres 优先 schema,SQLite 为开发默认;方言感知 TypeDecorator(StringArray / JSONDict / TZDateTime 修复 SQLite naive datetime)。
|
|
134
|
+
|
|
135
|
+
**关键决策**
|
|
136
|
+
- 嵌入页无法可靠直达时,回退 Greenhouse `/embed/job_app` 官方申请表单(CoreWeave 等案例)。
|
openhire-0.1.0/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# openhire-mcp
|
|
2
|
+
|
|
3
|
+
> **Jobs come to you. Your résumé never passes through OpenHire's servers, and we never store it.**
|
|
4
|
+
> 岗位来找你,简历不经过我们的服务器,也不被我们存储。
|
|
5
|
+
|
|
6
|
+
    
|
|
7
|
+
|
|
8
|
+
An MCP server that turns your AI assistant (Claude, Cursor, Windsurf) into a private radar
|
|
9
|
+
for **remote AI / Infra roles** — sourced directly from ~100 company career sites and their
|
|
10
|
+
public ATS APIs (Greenhouse / Lever / Ashby). **No account. No signup. No résumé upload. Ever.**
|
|
11
|
+
|
|
12
|
+
Matching runs on your machine; only an anonymous fingerprint and hard filters ever reach the
|
|
13
|
+
server. This is the v0.1 「哨兵 / Sentinel」 reference implementation — see
|
|
14
|
+
`design_handoff_openhire_v01/README.md` for the full protocol spec.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Quickstart — under a minute
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 1. Install (pipx keeps it isolated and puts `ohp` on your PATH)
|
|
22
|
+
pipx install openhire
|
|
23
|
+
|
|
24
|
+
# 2. Get a job index. Default: download the public snapshot, then refresh it live.
|
|
25
|
+
ohp bootstrap
|
|
26
|
+
# --fresh crawl the public ATS from scratch (heuristic, free, no snapshot)
|
|
27
|
+
# --deepseek higher-quality extraction using YOUR OWN DEEPSEEK_API_KEY
|
|
28
|
+
|
|
29
|
+
# 3. Use it directly…
|
|
30
|
+
ohp search --required-skills rust,k8s --remote --role-family engineering
|
|
31
|
+
|
|
32
|
+
# …or connect it to an MCP client:
|
|
33
|
+
ohp serve
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For **Claude Desktop**, add to `claude_desktop_config.json`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"openhire": {
|
|
42
|
+
"command": "ohp",
|
|
43
|
+
"args": ["serve"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> On Windows the config file lives at `%APPDATA%\Claude\claude_desktop_config.json` (for the
|
|
50
|
+
> Microsoft Store build it is under `…\Packages\<Claude package>\LocalCache\Roaming\Claude\`).
|
|
51
|
+
> Fully quit and reopen Claude Desktop after editing.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## What it does
|
|
56
|
+
|
|
57
|
+
| Tool | What it gives you |
|
|
58
|
+
|------|-------------------|
|
|
59
|
+
| `search_jobs` | Hard-filter the live index; every result carries `verified_at`, `datePosted`, `days_open`, `ghost_score`, `remote_scope`, `eligible_regions`, `apply_channel`. Filter by `required_skills` (AND), `role_family`, `remote_scope`, `min_salary` + `currency`. |
|
|
60
|
+
| `watch_intent` | Register a standing intent once — new matching jobs are waiting next time you check, even after you close the terminal. Accepts `required_skills` / `role_family` so sales / solutions roles stay out. |
|
|
61
|
+
| `check_watches` | Pull the matches that are new since your last check (client-pull; stdio has no push). |
|
|
62
|
+
| `authorize_application` | One explicit confirmation per job. It records your authorization and returns the employer's **own** application URL — you apply as yourself. It **cannot** accept a résumé. |
|
|
63
|
+
| `get_company_info` | Aggregate, anonymous trust signals for one employer (`ghost_score_avg`, `active_jobs`, `index_built_at`). Never any candidate data. |
|
|
64
|
+
|
|
65
|
+
Optional, entirely local: `ohp init --scan <dir>` derives a **skill fingerprint** from your
|
|
66
|
+
own repos. You never write a résumé; the code never leaves your machine — only an anonymous
|
|
67
|
+
vector does.
|
|
68
|
+
|
|
69
|
+
## The five protocol fields
|
|
70
|
+
|
|
71
|
+
Every listing is valid `schema.org/JobPosting`, plus:
|
|
72
|
+
|
|
73
|
+
- `verified_at` — last moment confirmed live on the employer's own site
|
|
74
|
+
- `source` — `employer_site | ats_public_api` (never a job board)
|
|
75
|
+
- `ghost_score` — 0–1 likelihood the listing is not a real, active hire (ages off the **real**
|
|
76
|
+
posting date; lower is better)
|
|
77
|
+
- `response_sla_days` — employer's committed response window (v0.1: always null)
|
|
78
|
+
- `apply_channel` — always the employer's own application URL, deep-linked to the specific job
|
|
79
|
+
|
|
80
|
+
## Privacy model
|
|
81
|
+
|
|
82
|
+
| | |
|
|
83
|
+
|---|---|
|
|
84
|
+
| **Résumé / PII upload** | **never** — matching runs locally; a résumé never transits the server, and we never store one |
|
|
85
|
+
| **What the server sees** | one anonymous, client-generated fingerprint + hard filters |
|
|
86
|
+
| **Repo scan** | local-only · personal projects · explicit consent · opt-out anytime |
|
|
87
|
+
| **Job sources** | first-party only: employer career pages + public ATS APIs (Greenhouse / Lever / Ashby) |
|
|
88
|
+
|
|
89
|
+
## First-run data — the snapshot vs. fresh
|
|
90
|
+
|
|
91
|
+
`ohp bootstrap` (default) downloads a small **public** index snapshot (a GitHub Release
|
|
92
|
+
asset — `companies` + `jobs` only, **zero** user data) and then runs one incremental crawl to
|
|
93
|
+
refresh `verified_at` / delisting. `--fresh` skips the snapshot and crawls the public ATS from
|
|
94
|
+
scratch with the free offline heuristic extractor. Either way: no account, no PII.
|
|
95
|
+
|
|
96
|
+
## Three rules this project will never break
|
|
97
|
+
|
|
98
|
+
1. Your résumé stays on your machine — it never transits the server, and we never store it.
|
|
99
|
+
2. Ranking is not for sale — it is only `f(match_quality, freshness)`, a locked pure function.
|
|
100
|
+
3. Employers pay only for authorized, delivered outcomes — never for exposure. (v0.1 has no
|
|
101
|
+
billing at all.)
|
|
102
|
+
|
|
103
|
+
These are enforced by CI (`tests/test_privacy.py`, `tests/test_ranking.py`,
|
|
104
|
+
`tests/test_snapshot.py`).
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python -m venv .venv && . .venv/Scripts/activate # Windows
|
|
110
|
+
pip install -e ".[dev]"
|
|
111
|
+
pytest # privacy red lines + ranking + snapshot must be green
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Set `OPENHIRE_DATABASE_URL=postgresql+psycopg://…` to run against Postgres instead of the
|
|
115
|
+
default local SQLite file (`~/.openhire/openhire.db`).
|
|
116
|
+
|
|
117
|
+
## Roadmap
|
|
118
|
+
|
|
119
|
+
- **v0.2** — CN ATS adapters (Beisen / Moka) · `ghost_score` public beta
|
|
120
|
+
- **v0.3** — Employer claim + verified badges · response-SLA enforcement (7-day auto-delist)
|
|
121
|
+
- **v1.0** — Open, vendor-neutral schema extension for AI-readable job postings
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT © OpenHire Protocol · PRs welcome.
|