File size: 13,624 Bytes
ee0b555
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
"""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.
"""