Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 "双休,五险一金"

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ boss login
# 3. 验证登录态
boss status

# 可选:查看本地平台注册与能力状态(不触网)
boss platforms

# 4. 搜索广州的 Golang 职位,要求双休+五险一金
boss search "Golang" --city 广州 --welfare "双休,五险一金"

Expand Down
109 changes: 109 additions & 0 deletions src/boss_agent_cli/commands/platforms.py
Original file line number Diff line number Diff line change
@@ -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 <name> status — 检查指定平台本地登录态",
"boss schema — 查看命令级可用性矩阵",
],
},
)
2 changes: 2 additions & 0 deletions src/boss_agent_cli/commands/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
mark,
me,
pipeline,
platforms,
preset,
recommend,
resume_cmd,
Expand Down Expand Up @@ -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")
Expand Down
8 changes: 7 additions & 1 deletion src/boss_agent_cli/commands/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)。默认低风险模式仅用于用户主动触发的本地辅助与只读命令,不用于规避平台风控。",
Expand All @@ -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": [],
Expand Down
42 changes: 42 additions & 0 deletions tests/test_platforms_cmd.py
Original file line number Diff line number Diff line change
@@ -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"]