medsci-skills 5.9.0 → 5.9.1

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.
package/README.md CHANGED
@@ -57,7 +57,9 @@ rather than reimplementing the ecosystem. Clinical AI model research
57
57
  engineering is in scope; it is **not** a diagnostic tool, an autonomous author, or a
58
58
  general AI-scientist platform, and every output requires human-expert verification.
59
59
  New here? See the [3 workflows below](#start-here-3-workflows), the
60
- [FAQ](docs/faq.md), and the
60
+ [FAQ](docs/faq.md), the
61
+ [research connectors it calls](docs/connectors.md) (keyless public APIs — nothing to set
62
+ up in the common case), and the
61
63
  [scope boundary](ROADMAP.md#not-planned--explicitly-out-of-scope).
62
64
 
63
65
  ---
@@ -1578,8 +1578,8 @@
1578
1578
  },
1579
1579
  {
1580
1580
  "path": "skills/fulltext-retrieval/fetch_oa.py",
1581
- "size": 24503,
1582
- "sha256": "768f95a30737e67b7f9edee72fe8815a795c9f83451b46551bbe25fc36094f4b"
1581
+ "size": 24810,
1582
+ "sha256": "12bfbf094d338ac461267cea133c0a63a66bc0d2dadd28727921149bc5b72855"
1583
1583
  },
1584
1584
  {
1585
1585
  "path": "skills/fulltext-retrieval/fetch_oa_report_challenge/expected/projection.json",
@@ -3853,8 +3853,8 @@
3853
3853
  },
3854
3854
  {
3855
3855
  "path": "skills/verify-refs/scripts/verify_refs.py",
3856
- "size": 40629,
3857
- "sha256": "ca762e7f294cf90aa1675129a8c152304b48ba7340982c09a64204babc53c674"
3856
+ "size": 41415,
3857
+ "sha256": "4377e25e6b125ace31fd35df9ffdd21a413f0f70ba1e429e5bfcf50c0a29510e"
3858
3858
  },
3859
3859
  {
3860
3860
  "path": "skills/verify-refs/skill.yml",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "version": "5.9.0",
3
+ "version": "5.9.1",
4
4
  "owned_skills": [
5
5
  "academic-aio",
6
6
  "add-journal",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medsci-skills",
3
- "version": "5.9.0",
3
+ "version": "5.9.1",
4
4
  "description": "MedSci Skills — a medical/scientific research skill suite for AI coding agents (Claude Code, Codex, Cursor, Copilot). The npm package is a terminal-friendly installer shortcut; the canonical distribution remains the GitHub repository and the Claude Code plugin marketplace.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://github.com/Aperivue/medsci-skills#readme",
@@ -21,6 +21,7 @@ import csv
21
21
  import io
22
22
  import json
23
23
  import logging
24
+ import os
24
25
  import re
25
26
  import shutil
26
27
  import subprocess
@@ -562,8 +563,9 @@ def main():
562
563
  "with a DOI column (optional PMID, Title)")
563
564
  parser.add_argument("-o", "--output", type=Path, default=Path("pdfs"),
564
565
  help="Output directory (default: pdfs/)")
565
- parser.add_argument("-e", "--email", required=True,
566
- help="Contact email (required by Unpaywall TOS)")
566
+ parser.add_argument("-e", "--email", default=os.environ.get("MEDSCI_CONTACT_EMAIL"),
567
+ help="Contact email (required by Unpaywall TOS). "
568
+ "Falls back to the MEDSCI_CONTACT_EMAIL environment variable.")
567
569
  parser.add_argument("--report", type=Path, default=None,
568
570
  help="Path for the JSON retrieval report "
569
571
  "(default: <output>/retrieval_report.json)")
@@ -571,6 +573,10 @@ def main():
571
573
  help="Show debug messages")
572
574
  args = parser.parse_args()
573
575
 
576
+ if not args.email:
577
+ parser.error("a contact email is required (Unpaywall TOS): pass --email you@lab.org "
578
+ "or set MEDSCI_CONTACT_EMAIL")
579
+
574
580
  logging.basicConfig(
575
581
  level=logging.DEBUG if args.verbose else logging.WARNING,
576
582
  format="%(levelname)s: %(message)s",
@@ -13,6 +13,7 @@ import argparse
13
13
  import csv
14
14
  import html
15
15
  import json
16
+ import os
16
17
  import re
17
18
  import sys
18
19
  import time
@@ -372,8 +373,30 @@ def guess_title(raw: str) -> str:
372
373
  return ""
373
374
 
374
375
 
376
+ def _contact_email() -> str:
377
+ """User-supplied contact email (courtesy for NCBI/CrossRef), never a credential."""
378
+ return (os.environ.get("MEDSCI_CONTACT_EMAIL")
379
+ or os.environ.get("NCBI_EMAIL")
380
+ or "medsci-skills@users.noreply.github.com")
381
+
382
+
383
+ def _user_agent() -> str:
384
+ return f"medsci-skills/verify-refs (mailto:{_contact_email()})"
385
+
386
+
387
+ def _ncbi_extras() -> dict:
388
+ """Optional NCBI E-utilities etiquette / rate-limit params, all from env.
389
+ Setting NCBI_API_KEY raises the PubMed rate limit from 3 to 10 requests/s;
390
+ absent it, the calls stay keyless. `tool`/`email` are NCBI-recommended courtesy."""
391
+ extra = {"tool": "medsci-skills", "email": _contact_email()}
392
+ key = os.environ.get("NCBI_API_KEY")
393
+ if key:
394
+ extra["api_key"] = key
395
+ return extra
396
+
397
+
375
398
  def http_json(url: str, timeout: int) -> dict | None:
376
- req = urllib.request.Request(url, headers={"User-Agent": "medsci-skills/verify-refs (mailto:example@example.com)"})
399
+ req = urllib.request.Request(url, headers={"User-Agent": _user_agent()})
377
400
  try:
378
401
  with urllib.request.urlopen(req, timeout=timeout) as resp:
379
402
  return json.loads(resp.read().decode("utf-8", "replace"))
@@ -421,7 +444,7 @@ def verify_pubmed_pmid(pmid: str, timeout: int) -> tuple[str, str, list]:
421
444
  verify_pubmed_efetch().
422
445
  """
423
446
  url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?" + urllib.parse.urlencode(
424
- {"db": "pubmed", "id": pmid, "retmode": "json"}
447
+ {"db": "pubmed", "id": pmid, "retmode": "json", **_ncbi_extras()}
425
448
  )
426
449
  data = http_json(url, timeout)
427
450
  if not data:
@@ -458,11 +481,11 @@ def verify_pubmed_efetch(pmid: str, timeout: int) -> tuple[str, str, list, list]
458
481
  case: CrossRef "Vasileios" vs PubMed "Victoria" — PubMed is authoritative).
459
482
  """
460
483
  url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?" + urllib.parse.urlencode(
461
- {"db": "pubmed", "id": pmid, "retmode": "xml"}
484
+ {"db": "pubmed", "id": pmid, "retmode": "xml", **_ncbi_extras()}
462
485
  )
463
486
  req = urllib.request.Request(
464
487
  url,
465
- headers={"User-Agent": "medsci-skills/verify-refs (mailto:example@example.com)"},
488
+ headers={"User-Agent": _user_agent()},
466
489
  )
467
490
  try:
468
491
  with urllib.request.urlopen(req, timeout=timeout) as resp:
@@ -496,7 +519,7 @@ def verify_pubmed_title(title: str, timeout: int) -> tuple[str, str, list]:
496
519
  if not title:
497
520
  return "UNVERIFIED", "No DOI, PMID, or usable title", []
498
521
  url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?" + urllib.parse.urlencode(
499
- {"db": "pubmed", "term": title, "retmode": "json", "retmax": "3"}
522
+ {"db": "pubmed", "term": title, "retmode": "json", "retmax": "3", **_ncbi_extras()}
500
523
  )
501
524
  data = http_json(url, timeout)
502
525
  if not data: