nielsr's picture
nielsr HF Staff
Prototype PwC MCP Space from pwc-cli@6a547659
ee0b555 verified
Raw History Blame Contribute Delete
13.6 kB
"""Generate and install the pwc CLI skill for AI coding agents."""
from __future__ import annotations
import argparse
import os
import shutil
import tempfile
from pathlib import Path
from pwc_cli import __version__
SKILL_NAME = "pwc-cli"
CENTRAL_LOCAL = Path(".agents/skills")
CENTRAL_GLOBAL = Path("~/.agents/skills")
CLAUDE_LOCAL = Path(".claude/skills")
CLAUDE_GLOBAL = Path("~/.claude/skills")
_DESCRIPTION = (
"Papers With Code CLI (`pwc`) for searching and reading AI/ML papers, "
"discovering recent and trending research, finding related work and paper "
"lineage, browsing tasks, methods, conferences, organizations, frameworks, "
"and benchmark leaderboards, and submitting authenticated paper edits "
"through the public Papers With Code catalog. Use whenever the user asks to "
"find papers, survey literature, compare research, inspect an arXiv paper, "
"explore AI/ML taxonomy or conferences, discover benchmarks or "
"state-of-the-art models, or mentions Papers With Code, `pwc`, or `pwc-cli`."
)
_INTRODUCTION = """
Research commands query the public [Papers With Code](https://paperswithcode.co) catalog anonymously.
Paper editing requires explicit browser authorization through `pwc auth login --paper PAPER`.
Run `pwc --help` or a nested `--help` command when the live parser and this
skill disagree; the parser is authoritative.
Use compact output for reading and discovery. Add `--json` for programmatic
filtering, joining, or schema-dependent processing.
When the user identifies an author, prefer repeatable structured
`pwc paper list --author AUTHOR` filters. Add `--search TEXT` for stated topic
terms and explicit `--order-by date_published --order-dir desc` for newest or
recent work. Author references accept an exact normalized name, numeric ID, or
`@HF_USERNAME`; repeated authors use AND semantics.
Publication date ranges are inclusive: use `--start-date YYYY-MM-DD` and
`--end-date YYYY-MM-DD` with `pwc search` or `pwc paper list`; the start date
must not be later than `--end-date`. The combined flags are
`--start-date YYYY-MM-DD --end-date YYYY-MM-DD`.
Paper discovery commands accept `--implementation-coverage` to add official
implementation status and total linked repository count columns. JSON and
`pwc paper info` always include both fields. Use
`--has-official-implementation` with `pwc search` or `pwc paper list` to require
a catalog-linked official repository; these filters fail closed if unconfirmed.
Use `pwc benchmark --name NAME --max-parameters SIZE` to keep models at or
below an inclusive parameter limit. SIZE accepts values such as `500M`, `1.5B`,
`3B`, and raw integers. Models without one consistent parameter count are
excluded from constrained results.
`PAPER` accepts a modern or legacy arXiv ID, a numeric external-paper ID, or an
exact paper title. Quote titles containing spaces. Title matching is
case-insensitive but exact; ambiguous titles fail with their matching IDs.
When `pwc paper info` or `pwc paper read` names a paper Papers With Code has
not indexed, stderr shows "Not indexed on Papers With Code — available at:"
with public links and metadata before the usual not-found error; cite those
links, not a Papers With Code page.
"""
_WORKFLOW = """
## Research workflow
1. Use `pwc benchmark list --task TASK` to discover active benchmarks, then
`pwc benchmark --name NAME` to inspect a leaderboard. Add
`--max-parameters SIZE` when model size is part of the request.
2. Use `pwc paper info` to inspect promising results. Add
`--include-resources` when repositories, project pages, or Hugging Face
artifacts matter.
3. Use exact `pwc paper list --author`, `--task`, `--method`, `--conference`,
`--framework`, and `--organization` filters for known identities or catalog
associations. Combine them to require every association; do not substitute
a keyword search. Add `--search` for title or abstract topic terms.
4. Use `pwc search` for broader discovery, then `pwc paper read` for primary
evidence.
5. Expand the literature with `pwc paper related` and use
`pwc paper lineage list` when model or method ancestry matters.
6. Preserve paper titles, identifiers, and URLs so claims remain traceable.
## Output and limits
- Interactive output is optimized for people. Benchmark lists remain aligned
when captured; other captured list output uses lossless TSV. Add `--json` for
structured agent or script consumption.
- Stable exit codes are `0` success, `2` invalid usage, `3` network/server
failure, and `4` invalid API response.
- `PWC_API_URL` may select another compatible v1 endpoint. The default is
`https://paperswithcode.co/api/v1`.
- Catalog-filtered paper lists fail closed unless the server confirms every
requested filter; never treat results from an older server as filtered.
- Parameter-filtered benchmark details fail closed unless the server confirms
parameter-filter support and every returned model satisfies the limit.
The research commands contain no authentication, catalog mutation, ingestion,
publication, image, embedding, CRON, or infrastructure-maintenance operations.
"""
class SkillInstallError(Exception):
"""An invalid or unsafe skill installation request."""
def _subcommand_help(action: argparse._SubParsersAction, name: str) -> str:
for choice in action._choices_actions:
if choice.dest == name:
return str(choice.help or "")
return ""
def _command_signature(parser: argparse.ArgumentParser) -> str:
parts: list[str] = []
for action in parser._actions:
if isinstance(action, argparse._SubParsersAction):
continue
if action.dest == "help":
continue
if not action.option_strings:
parts.append(str(action.metavar or action.dest).upper())
continue
option = next(
(item for item in action.option_strings if item.startswith("--")),
action.option_strings[0],
)
if action.nargs == 0:
parts.append(f"[{option}]")
continue
if action.choices:
value = "|".join(str(choice) for choice in action.choices)
else:
value = str(action.metavar or action.dest).upper()
parts.append(f"[{option} {value}]")
return " ".join(parts)
def _command_entries(
parser: argparse.ArgumentParser, prefix: tuple[str, ...] = ()
) -> list[str]:
entries: list[str] = []
for action in parser._actions:
if not isinstance(action, argparse._SubParsersAction):
continue
for name, child in action.choices.items():
path = (*prefix, name)
if "handler" in child._defaults:
signature = _command_signature(child)
invocation = " ".join(("pwc", *path, signature)).rstrip()
help_text = _subcommand_help(action, name)
suffix = f" — {help_text.rstrip('.')}" if help_text else ""
entries.append(f"- `{invocation}`{suffix}.")
entries.extend(_command_entries(child, path))
return entries
def build_skill_md() -> str:
"""Build a Skill matching the commands in the installed pwc version."""
from pwc_cli.cli import build_parser
commands = "\n".join(_command_entries(build_parser()))
return (
"---\n"
f"name: {SKILL_NAME}\n"
f'description: "{_DESCRIPTION}"\n'
"---\n\n"
f"Generated with `pwc v{__version__}`. Run "
"`pwc skills add --force` to regenerate.\n"
f"{_INTRODUCTION}\n"
"## Commands\n\n"
f"{commands}\n"
f"{_WORKFLOW}\n{_EDIT_WORKFLOW}"
)
def _remove_existing(path: Path, force: bool) -> None:
if not (path.exists() or path.is_symlink()):
return
if not force:
raise SkillInstallError(
f"skill already exists at {path}; rerun with --force to overwrite"
)
if path.is_dir() and not path.is_symlink():
shutil.rmtree(path)
else:
path.unlink()
def _install_to(skills_dir: Path, force: bool) -> Path:
skills_dir = skills_dir.expanduser().resolve()
skills_dir.mkdir(parents=True, exist_ok=True)
target = skills_dir / SKILL_NAME
_remove_existing(target, force)
temporary = Path(tempfile.mkdtemp(prefix=f".{SKILL_NAME}-", dir=skills_dir))
try:
(temporary / "SKILL.md").write_text(build_skill_md(), encoding="utf-8")
os.replace(temporary, target)
finally:
if temporary.exists():
shutil.rmtree(temporary)
return target
def _link_to(skills_dir: Path, installed: Path, force: bool) -> Path:
skills_dir = skills_dir.expanduser().resolve()
skills_dir.mkdir(parents=True, exist_ok=True)
link = skills_dir / SKILL_NAME
_remove_existing(link, force)
link.symlink_to(os.path.relpath(installed, skills_dir), target_is_directory=True)
return link
def skills_add(args: argparse.Namespace, _client: object) -> int:
"""Install the generated pwc skill in central and optional harness paths."""
if args.dest is not None and (args.global_ or args.claude):
raise SkillInstallError("--dest cannot be combined with --global or --claude")
if args.dest is not None:
installed = _install_to(args.dest, args.force)
print(f"Installed '{SKILL_NAME}' to {installed}")
return 0
central_root = CENTRAL_GLOBAL if args.global_ else CENTRAL_LOCAL
installed = _install_to(central_root, args.force)
print(f"Installed '{SKILL_NAME}' to {installed}")
if args.claude:
claude_root = CLAUDE_GLOBAL if args.global_ else CLAUDE_LOCAL
link = _link_to(claude_root, installed, args.force)
print(f"Linked '{SKILL_NAME}' for Claude at {link}")
return 0
_EDIT_WORKFLOW = """
## Editing one paper
Use this workflow only when the user requests edits. Research commands remain anonymous.
1. Run `pwc auth login --paper PAPER`. Give the user the browser link and code;
they sign in with Hugging Face and authorize that paper for one hour. Use
`--no-browser` on a remote machine. Never extract, print, or copy credentials.
2. Run `pwc paper edit export PAPER --output edits.json` (the output must not
already exist). It includes paper_id, version, idempotency_key, empty operations,
and current reference data. Change operations, not current. Preserve the
exported version and retry key. Documents may contain at most 50 operations
and 1 MiB of JSON.
3. Add operations of the form `{"section":"tasks","payload":{"task_ids":[1,2]}}`.
A section operation explicitly replaces that section; preserve unrelated links
and tags. Omitted sections stay unchanged. Allowed sections and payload keys:
tasks/task_ids, methods/method_ids, repositories/repositories,
project_pages/project_pages, hf_artifacts/hf_models+hf_datasets+hf_spaces,
x_threads/x_threads, source_url/source_url (external papers only), and
evaluations. Repository/project-page entries contain url and is_official.
x_threads is a list of x.com or twitter.com post URLs in display order (at
most 10). Link only posts in which the paper's authors or their organization
introduce the paper, never outside accounts; the server captures each post's
preview and rejects deleted or protected posts.
4. For evaluations, an operation without evaluation_id creates a row. Include
task_id, dataset_id, metrics (a dictionary of existing metric names to scores),
model_name, and the evaluation setup where available. One row can contain
multiple metrics. Add evaluation_id to correct an existing row on this paper;
omitted update fields stay unchanged. Use source_url and methodology to cite
precise evidence when available; source references remain optional. Existing
benchmark/task/metric definitions are required. Do not invent missing IDs or
create benchmarks. Report unsupported results to the user. No evaluation
deletion, paper identity changes, organization edits, or rank overrides.
5. Run `pwc paper edit preview PAPER --file edits.json`, inspect the before/after
changes, then `pwc paper edit submit PAPER --file edits.json`. Publication is
immediate: no per-batch browser approval. Respect the user's requested scope.
A batch succeeds completely or makes no changes. The response includes status
published and a link to the paper's history.
6. Retry an uncertain network result using the exact same document and retry key.
A 409 means changed data or a reused key with different edits. Fetch a fresh
export, preserve others' edits, and reconcile. Ask the user if the same score
has conflicting corrections. Once you intentionally revise a previously
submitted batch, use a fresh export/key. A 403 may mean expired authorization,
suspension, or insufficient scope: read the error; do not broaden access.
A 429 indicates the shared account limit: 20 distinct papers per UTC day and
30 publications per minute. Do not work around account limits.
7. `pwc auth status --paper PAPER` shows local expiry metadata (revocation is
checked by the server on use). `pwc auth logout --paper PAPER` revokes access.
If authorization expires, keep the prepared file and request browser login
again. Renewing authorization does not require discarding a valid edit document.
Credentials are stored in owner-only files under ~/.config/pwc/edit-credentials,
separately for each API base URL and paper. They never belong in a prompt, edit
file, repository, or command argument. The CLI does not follow edit redirects.
"""