From 7e4a8e63a4c3b993b9cbb539aa98e8620078437b Mon Sep 17 00:00:00 2001 From: can4hou6joeng4 Date: Wed, 10 Jun 2026 16:26:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E8=83=BD=E5=8A=9B=E6=B8=85=E5=8D=95=E5=91=BD?= =?UTF-8?q?=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.en.md | 3 + README.md | 3 + src/boss_agent_cli/commands/platforms.py | 109 +++++++++++++++++++++++ src/boss_agent_cli/commands/register.py | 2 + src/boss_agent_cli/commands/schema.py | 8 +- tests/test_platforms_cmd.py | 42 +++++++++ 6 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/boss_agent_cli/commands/platforms.py create mode 100644 tests/test_platforms_cmd.py diff --git a/README.en.md b/README.en.md index 4421a14..9a3d0c6 100644 --- a/README.en.md +++ b/README.en.md @@ -125,6 +125,9 @@ boss login # 3. Verify login boss status +# Optional: inspect local platform capability status (no network) +boss platforms + # 4. Search Golang jobs in Guangzhou with 双休 + 五险一金 boss search "Golang" --city 广州 --welfare "双休,五险一金" diff --git a/README.md b/README.md index a4a3110..484b374 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,9 @@ boss login # 3. 验证登录态 boss status +# 可选:查看本地平台注册与能力状态(不触网) +boss platforms + # 4. 搜索广州的 Golang 职位,要求双休+五险一金 boss search "Golang" --city 广州 --welfare "双休,五险一金" diff --git a/src/boss_agent_cli/commands/platforms.py b/src/boss_agent_cli/commands/platforms.py new file mode 100644 index 0000000..1d1ea71 --- /dev/null +++ b/src/boss_agent_cli/commands/platforms.py @@ -0,0 +1,109 @@ +from __future__ import annotations + +from typing import Any + +import click + +from boss_agent_cli.display import handle_output +from boss_agent_cli.platforms import get_platform, list_platforms, list_recruiter_platforms + +_READONLY_CAPABILITIES = ["search", "detail", "recommend", "me", "status"] +_WRITE_CAPABILITIES = ["greet", "apply"] +_LOCAL_CAPABILITIES = ["shortlist", "stats", "config", "schema"] + +_PLATFORM_CAPABILITY_STATUS: dict[str, dict[str, str]] = { + "zhipin": { + "search": "available", + "detail": "available", + "recommend": "available", + "me": "available", + "status": "available", + "greet": "low_risk_blocked", + "apply": "low_risk_blocked", + }, + "zhilian": { + "search": "available", + "detail": "available", + "recommend": "available", + "me": "available", + "status": "available", + "greet": "low_risk_blocked", + "apply": "low_risk_blocked", + }, + "qiancheng": { + "search": "not_supported", + "detail": "not_supported", + "recommend": "not_supported", + "me": "not_supported", + "status": "placeholder_only", + "greet": "not_supported", + "apply": "not_supported", + }, +} + +_PLATFORM_NOTES = { + "zhipin": "默认平台;候选者侧与招聘者侧注册表均已接入。", + "zhilian": "候选者侧只读链路已接入;招聘者侧暂不可用。", + "qiancheng": "51job/前程无忧当前仅注册平台身份;真实能力返回 NOT_SUPPORTED。", +} + +_ALIAS_NAMES = { + "51job", +} + + +def platform_capability_data() -> dict[str, Any]: + """Return local-only platform capability metadata without creating clients.""" + candidate_platforms = [name for name in list_platforms() if name not in _ALIAS_NAMES] + recruiter_platforms = list_recruiter_platforms() + platforms = [] + for name in candidate_platforms: + platform_cls = get_platform(name) + statuses = _PLATFORM_CAPABILITY_STATUS[name] + platforms.append({ + "name": name, + "display_name": platform_cls.display_name, + "base_url": platform_cls.base_url, + "candidate": True, + "recruiter": f"{name}-recruiter" in recruiter_platforms, + "status": "placeholder" if name == "qiancheng" else "available", + "capabilities": { + "readonly": {capability: statuses[capability] for capability in _READONLY_CAPABILITIES}, + "write": {capability: statuses[capability] for capability in _WRITE_CAPABILITIES}, + "local": {capability: "available" for capability in _LOCAL_CAPABILITIES}, + }, + "notes": _PLATFORM_NOTES[name], + }) + return { + "count": len(platforms), + "default": "zhipin", + "aliases": {"51job": "qiancheng"}, + "platforms": platforms, + } + + +def _render_platforms(data: dict[str, Any]) -> None: + lines = ["name\tdisplay_name\tstatus\tcandidate\trecruiter"] + for item in data["platforms"]: + candidate = "yes" if item["candidate"] else "no" + recruiter = "yes" if item["recruiter"] else "no" + lines.append(f"{item['name']}\t{item['display_name']}\t{item['status']}\t{candidate}\t{recruiter}") + click.echo("\n".join(lines)) + + +@click.command("platforms") +@click.pass_context +def platforms_cmd(ctx: click.Context) -> None: + """列出本地已注册平台与能力状态。""" + handle_output( + ctx, + "platforms", + platform_capability_data(), + render=_render_platforms, + hints={ + "next_actions": [ + "boss --platform status — 检查指定平台本地登录态", + "boss schema — 查看命令级可用性矩阵", + ], + }, + ) diff --git a/src/boss_agent_cli/commands/register.py b/src/boss_agent_cli/commands/register.py index 291fd14..3485c97 100644 --- a/src/boss_agent_cli/commands/register.py +++ b/src/boss_agent_cli/commands/register.py @@ -24,6 +24,7 @@ mark, me, pipeline, + platforms, preset, recommend, resume_cmd, @@ -52,6 +53,7 @@ def register_candidate_commands(cli: click.Group) -> None: cli.add_command(login.login_cmd, "login") cli.add_command(logout.logout_cmd, "logout") cli.add_command(status.status_cmd, "status") + cli.add_command(platforms.platforms_cmd, "platforms") cli.add_command(doctor.doctor_cmd, "doctor") cli.add_command(search.search_cmd, "search") cli.add_command(detail.detail_cmd, "detail") diff --git a/src/boss_agent_cli/commands/schema.py b/src/boss_agent_cli/commands/schema.py index 552e2f3..5092b35 100644 --- a/src/boss_agent_cli/commands/schema.py +++ b/src/boss_agent_cli/commands/schema.py @@ -68,6 +68,7 @@ def _command_to_json_schema(cmd_name: str, cmd_spec: dict[str, Any]) -> dict[str "config", "clean", "cities", + "platforms", } _CANDIDATE_COMMANDS = { @@ -252,7 +253,7 @@ def _format_mcp_tools(data: dict[str, Any]) -> list[dict[str, Any]]: SCHEMA_DATA = { "name": "boss-agent-cli", - "description": "BOSS直聘本地辅助工具,共 34 个顶层命令。默认低风险模式聚焦只读、本地辅助、用户主动触发;自动触达、批量操作和候选人个人信息处理默认受限。", + "description": "BOSS直聘本地辅助工具,共 35 个顶层命令。默认低风险模式聚焦只读、本地辅助、用户主动触发;自动触达、批量操作和候选人个人信息处理默认受限。", "commands": { "login": { "description": "按当前平台登录(zhipin / zhilian)。默认低风险模式仅用于用户主动触发的本地辅助与只读命令,不用于规避平台风控。", @@ -270,6 +271,11 @@ def _format_mcp_tools(data: dict[str, Any]) -> list[dict[str, Any]]: }, }, }, + "platforms": { + "description": "列出本地已注册平台与能力状态;只读本地元数据,不触发登录、浏览器、CDP 或网络请求", + "args": [], + "options": {}, + }, "status": { "description": "轻量检查当前登录态分层健康状态;默认不请求平台,--live 才执行一次只读在线验证", "args": [], diff --git a/tests/test_platforms_cmd.py b/tests/test_platforms_cmd.py new file mode 100644 index 0000000..9457449 --- /dev/null +++ b/tests/test_platforms_cmd.py @@ -0,0 +1,42 @@ +"""本地平台能力清单命令测试。""" + +from __future__ import annotations + +import json + +from click.testing import CliRunner + +from boss_agent_cli.main import cli + + +def test_platforms_outputs_local_capability_matrix() -> None: + runner = CliRunner() + result = runner.invoke(cli, ["platforms"]) + + assert result.exit_code == 0, result.output + payload = json.loads(result.output) + assert payload["ok"] is True + assert payload["command"] == "platforms" + assert payload["data"]["default"] == "zhipin" + assert payload["data"]["aliases"] == {"51job": "qiancheng"} + + platforms = {item["name"]: item for item in payload["data"]["platforms"]} + assert set(platforms) == {"qiancheng", "zhipin", "zhilian"} + assert platforms["qiancheng"]["status"] == "placeholder" + assert platforms["qiancheng"]["capabilities"]["readonly"]["search"] == "not_supported" + assert platforms["qiancheng"]["capabilities"]["readonly"]["status"] == "placeholder_only" + assert "NOT_SUPPORTED" in platforms["qiancheng"]["notes"] + assert platforms["zhipin"]["recruiter"] is True + assert platforms["zhilian"]["capabilities"]["readonly"]["search"] == "available" + + +def test_platforms_is_listed_in_schema() -> None: + runner = CliRunner() + result = runner.invoke(cli, ["schema"]) + + assert result.exit_code == 0, result.output + payload = json.loads(result.output) + platforms_schema = payload["data"]["commands"]["platforms"] + assert platforms_schema["args"] == [] + assert platforms_schema["options"] == {} + assert "不触发登录" in platforms_schema["description"]