Initial commit
This commit is contained in:
157
skills/aminer-academic-search/SKILL.md
Executable file
157
skills/aminer-academic-search/SKILL.md
Executable file
@@ -0,0 +1,157 @@
|
||||
---
|
||||
name: aminer-academic-search
|
||||
description: >
|
||||
ACADEMIC PRIORITY: Activate whenever the user's query involves academic,
|
||||
scholarly, or research-related topics — papers, citations, scholars,
|
||||
institutions, venues, patents, research trends, or any "who published what /
|
||||
where / when" question. Takes precedence over general web search for academic
|
||||
data needs. Routes through the z-ai gateway's `/v1/functions/invoke` endpoint
|
||||
to the AMiner Open Platform (27 APIs, 5 workflows).
|
||||
---
|
||||
|
||||
# aminer-academic-search
|
||||
|
||||
Wraps 27 AMiner Open Platform APIs through the local gateway. The gateway owns
|
||||
the `AMINER_API_KEY`; the skill only needs a `.z-ai-config` file to reach the
|
||||
gateway.
|
||||
|
||||
## When to activate
|
||||
|
||||
Any academic/scholarly query — papers, citations, scholars, institutions,
|
||||
venues, patents. Covers:
|
||||
- Scholar full profile (bio, education, honors, papers, patents, projects)
|
||||
- Paper deep dive (full abstract, keywords, authors, citation chain)
|
||||
- Multi-condition or semantic paper search
|
||||
- Institution research capability analysis
|
||||
- Venue annual monitoring
|
||||
- Patent deep details (IPC/CPC, assignee, claims)
|
||||
|
||||
## Config
|
||||
|
||||
Script reads `.z-ai-config`, in order:
|
||||
1. `./.z-ai-config`
|
||||
2. `~/.z-ai-config`
|
||||
3. `/etc/.z-ai-config`
|
||||
|
||||
Required fields: `baseUrl`, `apiKey`, `token` (JWT). Optional: `chatId`,
|
||||
`userId`.
|
||||
|
||||
## Invocation
|
||||
|
||||
```bash
|
||||
python3 "{baseDir}/scripts/aminer.py" <ACTION> [--flag value …]
|
||||
```
|
||||
|
||||
The script POSTs to `${baseUrl}/functions/invoke` with
|
||||
`Authorization: Bearer ${apiKey}`, `X-Z-AI-From: Z`, `X-Token: ${token}`,
|
||||
and prints the upstream JSON result (pretty-formatted).
|
||||
|
||||
Run `python3 scripts/aminer.py --help` to list actions, or
|
||||
`python3 scripts/aminer.py <ACTION> --help` for per-action flags.
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Locate paper_id by title
|
||||
python3 scripts/aminer.py paper_search --title "Attention is all you need"
|
||||
|
||||
# Locate scholar by name + org
|
||||
python3 scripts/aminer.py person_search --name "Jie Tang" --org "Tsinghua"
|
||||
|
||||
# Full paper details
|
||||
python3 scripts/aminer.py paper_detail --id 57a4e91aac44365e35c98a1e
|
||||
|
||||
# Natural-language Q&A search
|
||||
python3 scripts/aminer.py paper_qa_search --query "retrieval-augmented generation" --size 5
|
||||
|
||||
# Venue papers for a given year (body with JSON list param)
|
||||
python3 scripts/aminer.py venue_paper_relation --id "53e9ba63b7602d9702f08c5d" --year 2024 --limit 10
|
||||
```
|
||||
|
||||
JSON-typed flags (`--title`, `--ids`, `--keywords`, `--year` on qa_search, etc.)
|
||||
expect a JSON-encoded value, e.g. `--ids '["abc","def"]'`.
|
||||
|
||||
## 27 APIs (quick reference)
|
||||
|
||||
| Action | Method | Purpose |
|
||||
|---|---|---|
|
||||
| paper_search | GET | Locate paper_id by title |
|
||||
| paper_search_pro | GET | Multi-condition paper search |
|
||||
| paper_qa_search | POST | Natural-language / topic Q&A |
|
||||
| paper_info | POST | Batch paper info by IDs |
|
||||
| paper_detail | GET | Full paper details |
|
||||
| paper_relation | GET | Citation chain |
|
||||
| paper_list_by_keywords | GET | Batch thematic retrieval |
|
||||
| paper_detail_by_condition | GET | Year + venue dimension |
|
||||
| person_search | POST | Locate person_id |
|
||||
| person_detail | GET | Scholar bio/education/honors |
|
||||
| person_figure | GET | Interests + work history |
|
||||
| person_paper_relation | GET | Scholar's papers |
|
||||
| person_patent_relation | GET | Scholar's patents |
|
||||
| person_project | GET | Funded projects |
|
||||
| org_search | POST | Locate org by name |
|
||||
| org_detail | POST | Org description/type |
|
||||
| org_person_relation | GET | Affiliated scholars |
|
||||
| org_paper_relation | GET | Org papers |
|
||||
| org_patent_relation | GET | Org patents |
|
||||
| org_disambiguate | POST | Normalize org string |
|
||||
| org_disambiguate_pro | POST | Extract org IDs |
|
||||
| venue_search | POST | Locate venue_id |
|
||||
| venue_detail | POST | ISSN/type/abbreviation |
|
||||
| venue_paper_relation | POST | Papers by venue (+ year filter) |
|
||||
| patent_search | POST | Patent keyword search |
|
||||
| patent_info | GET | Basic patent info |
|
||||
| patent_detail | GET | Full patent details (IPC/claims) |
|
||||
|
||||
## 5 Workflows (orchestrate via multiple calls)
|
||||
|
||||
### Scholar Profile
|
||||
```
|
||||
person_search → person_detail
|
||||
→ person_figure
|
||||
→ person_paper_relation
|
||||
→ person_patent_relation
|
||||
→ person_project
|
||||
```
|
||||
|
||||
### Paper Deep Dive
|
||||
```
|
||||
paper_search → paper_detail → paper_relation → paper_info
|
||||
```
|
||||
If `paper_search` is empty, fall back to `paper_search_pro`.
|
||||
|
||||
### Org Analysis
|
||||
```
|
||||
org_disambiguate_pro → org_detail
|
||||
→ org_person_relation
|
||||
→ org_paper_relation
|
||||
→ org_patent_relation
|
||||
```
|
||||
If `org_disambiguate_pro` returns no ID, fall back to `org_search`.
|
||||
|
||||
### Venue Papers
|
||||
```
|
||||
venue_search → venue_detail (optional) → venue_paper_relation
|
||||
```
|
||||
|
||||
### Patent Analysis
|
||||
```
|
||||
patent_search → patent_info / patent_detail
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
- `warning: .z-ai-config has no 'token' field` → add `token` to config.
|
||||
- `http 401 … missing X-Token header (hint: …)` → same.
|
||||
- `http 403` → gateway auth rejected; check `apiKey` / `X-Z-AI-From`.
|
||||
- `gateway error: aminer_* failed: http 4xx …` → upstream refused; check API
|
||||
key on the gateway side (`AMINER_API_KEY` env var) or parameter shape.
|
||||
|
||||
## Entity URL templates
|
||||
|
||||
- Paper: `https://www.aminer.cn/pub/{paper_id}`
|
||||
- Scholar: `https://www.aminer.cn/profile/{scholar_id}`
|
||||
- Patent: `https://www.aminer.cn/patent/{patent_id}`
|
||||
- Journal: `https://www.aminer.cn/open/journal/detail/{journal_id}`
|
||||
|
||||
Always append the relevant URL when presenting entities to the user.
|
||||
391
skills/aminer-academic-search/scripts/aminer.py
Executable file
391
skills/aminer-academic-search/scripts/aminer.py
Executable file
@@ -0,0 +1,391 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dispatcher for the 27 AMiner Open Platform functions exposed via the
|
||||
z-ai gateway's /v1/functions/invoke endpoint.
|
||||
|
||||
Each action maps 1:1 to an `aminer_*` function registered in the gateway.
|
||||
Arguments are collected via argparse and forwarded as the `arguments` field.
|
||||
Upstream response is printed as formatted JSON.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any, Callable, Tuple
|
||||
|
||||
REQUEST_TIMEOUT = 60
|
||||
CONFIG_PATHS = [
|
||||
pathlib.Path.cwd() / ".z-ai-config",
|
||||
pathlib.Path.home() / ".z-ai-config",
|
||||
pathlib.Path("/etc/.z-ai-config"),
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Action registry
|
||||
#
|
||||
# Each entry defines one AMiner Open Platform API:
|
||||
# function_name: matches the key in handlers/aminer_open.go's AminerOpenSpecs
|
||||
# args: (flag, dest, kind, help) — kind is "str"/"int"/"float"/
|
||||
# "bool"/"json" (parsed from JSON for list/dict/nested values)
|
||||
#
|
||||
# This list is the *single source of truth* for what params each action takes,
|
||||
# so the model only has to read one structure to learn the whole surface.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_Arg = Tuple[str, str, str, str] # (flag, dest, kind, help)
|
||||
|
||||
ACTIONS: dict[str, dict[str, Any]] = {
|
||||
# ── Paper ────────────────────────────────────────────────────────────
|
||||
"paper_search": {
|
||||
"function": "aminer_paper_search",
|
||||
"help": "Locate a paper_id by (partial) title.",
|
||||
"args": [
|
||||
("--title", "title", "str", "Paper title keyword (required)"),
|
||||
("--page", "page", "int", "Page number, default 1"),
|
||||
("--size", "size", "int", "Page size, default 10"),
|
||||
],
|
||||
},
|
||||
"paper_search_pro": {
|
||||
"function": "aminer_paper_search_pro",
|
||||
"help": "Multi-condition paper search (author/org/venue/keyword).",
|
||||
"args": [
|
||||
("--title", "title", "str", "Title keyword"),
|
||||
("--keyword", "keyword", "str", "Subject keyword"),
|
||||
("--abstract", "abstract", "str", "Abstract keyword"),
|
||||
("--author", "author", "str", "Author name"),
|
||||
("--org", "org", "str", "Organization name"),
|
||||
("--venue", "venue", "str", "Venue name"),
|
||||
("--order", "order", "str", "Sort: citation | year"),
|
||||
("--page", "page", "int", "Page number, default 0"),
|
||||
("--size", "size", "int", "Page size, default 10"),
|
||||
],
|
||||
},
|
||||
"paper_qa_search": {
|
||||
"function": "aminer_paper_qa_search",
|
||||
"help": "AI Q&A-style search. 'query' and 'topic_*' are mutually exclusive.",
|
||||
"args": [
|
||||
("--query", "query", "str", "Natural language question"),
|
||||
("--use-topic", "use_topic", "bool", "Use topic_* fields instead of query"),
|
||||
("--topic-high", "topic_high", "str", "High-level topic"),
|
||||
("--topic-middle", "topic_middle", "str", "Mid-level topic"),
|
||||
("--topic-low", "topic_low", "str", "Low-level topic"),
|
||||
("--title", "title", "json", "List of titles (JSON array)"),
|
||||
("--doi", "doi", "str", "DOI"),
|
||||
("--year", "year", "json", "Year filter, e.g. [2020,2024]"),
|
||||
("--sci-flag", "sci_flag", "bool", "Restrict to SCI venues"),
|
||||
("--n-citation-flag", "n_citation_flag", "bool", "Return citation count"),
|
||||
("--force-citation-sort", "force_citation_sort", "bool", "Sort by citations"),
|
||||
("--force-year-sort", "force_year_sort", "bool", "Sort by year"),
|
||||
("--author-terms", "author_terms", "json", "Author name list"),
|
||||
("--org-terms", "org_terms", "json", "Org name list"),
|
||||
("--author-id", "author_id", "json", "Author ID list"),
|
||||
("--org-id", "org_id", "json", "Org ID list"),
|
||||
("--venue-ids", "venue_ids", "json", "Venue ID list"),
|
||||
("--size", "size", "int", "Page size, default 10"),
|
||||
("--offset", "offset", "int", "Offset, default 0"),
|
||||
],
|
||||
},
|
||||
"paper_info": {
|
||||
"function": "aminer_paper_info",
|
||||
"help": "Batch retrieve papers by ID list.",
|
||||
"args": [
|
||||
("--ids", "ids", "json", "JSON array of paper_id strings (required)"),
|
||||
],
|
||||
},
|
||||
"paper_detail": {
|
||||
"function": "aminer_paper_detail",
|
||||
"help": "Full paper info for a single paper_id.",
|
||||
"args": [
|
||||
("--id", "id", "str", "paper_id (required)"),
|
||||
],
|
||||
},
|
||||
"paper_relation": {
|
||||
"function": "aminer_paper_relation",
|
||||
"help": "Citation chain (cited papers) for a paper_id.",
|
||||
"args": [
|
||||
("--id", "id", "str", "paper_id (required)"),
|
||||
],
|
||||
},
|
||||
"paper_list_by_keywords": {
|
||||
"function": "aminer_paper_list_by_keywords",
|
||||
"help": "Batch keyword retrieval returning abstracts + metadata.",
|
||||
"args": [
|
||||
("--keywords", "keywords", "json", "JSON array of keyword strings (required)"),
|
||||
("--page", "page", "int", "Page number, default 0"),
|
||||
("--size", "size", "int", "Page size, default 10"),
|
||||
],
|
||||
},
|
||||
"paper_detail_by_condition": {
|
||||
"function": "aminer_paper_detail_by_condition",
|
||||
"help": "Year + venue dimension lookup. Year + venue_id both required.",
|
||||
"args": [
|
||||
("--year", "year", "int", "Year (required)"),
|
||||
("--venue-id", "venue_id", "str", "Venue ID (required)"),
|
||||
],
|
||||
},
|
||||
# ── Scholar ──────────────────────────────────────────────────────────
|
||||
"person_search": {
|
||||
"function": "aminer_person_search",
|
||||
"help": "Search scholars by name and/or org.",
|
||||
"args": [
|
||||
("--name", "name", "str", "Scholar name"),
|
||||
("--org", "org", "str", "Organization name"),
|
||||
("--org-id", "org_id", "json", "List of org IDs"),
|
||||
("--offset", "offset", "int", "Offset, default 0"),
|
||||
("--size", "size", "int", "Page size, default 5"),
|
||||
],
|
||||
},
|
||||
"person_detail": {
|
||||
"function": "aminer_person_detail",
|
||||
"help": "Full scholar profile (bio/education/honors).",
|
||||
"args": [("--id", "id", "str", "person_id (required)")],
|
||||
},
|
||||
"person_figure": {
|
||||
"function": "aminer_person_figure",
|
||||
"help": "Scholar portrait (interests, work history).",
|
||||
"args": [("--id", "id", "str", "person_id (required)")],
|
||||
},
|
||||
"person_paper_relation": {
|
||||
"function": "aminer_person_paper_relation",
|
||||
"help": "List of papers by this scholar.",
|
||||
"args": [("--id", "id", "str", "person_id (required)")],
|
||||
},
|
||||
"person_patent_relation": {
|
||||
"function": "aminer_person_patent_relation",
|
||||
"help": "List of patents by this scholar.",
|
||||
"args": [("--id", "id", "str", "person_id (required)")],
|
||||
},
|
||||
"person_project": {
|
||||
"function": "aminer_person_project",
|
||||
"help": "Research projects (funding, dates, source).",
|
||||
"args": [("--id", "id", "str", "person_id (required)")],
|
||||
},
|
||||
# ── Organization ─────────────────────────────────────────────────────
|
||||
"org_search": {
|
||||
"function": "aminer_org_search",
|
||||
"help": "Search institutions by name keyword.",
|
||||
"args": [("--orgs", "orgs", "json", "JSON array of org name strings (required)")],
|
||||
},
|
||||
"org_detail": {
|
||||
"function": "aminer_org_detail",
|
||||
"help": "Org details by ID list.",
|
||||
"args": [("--ids", "ids", "json", "JSON array of org_id strings (required)")],
|
||||
},
|
||||
"org_person_relation": {
|
||||
"function": "aminer_org_person_relation",
|
||||
"help": "Affiliated scholars (10 per call).",
|
||||
"args": [
|
||||
("--org-id", "org_id", "str", "org_id (required)"),
|
||||
("--offset", "offset", "int", "Offset, default 0"),
|
||||
],
|
||||
},
|
||||
"org_paper_relation": {
|
||||
"function": "aminer_org_paper_relation",
|
||||
"help": "Papers authored by org members (10 per call).",
|
||||
"args": [
|
||||
("--org-id", "org_id", "str", "org_id (required)"),
|
||||
("--offset", "offset", "int", "Offset, default 0"),
|
||||
],
|
||||
},
|
||||
"org_patent_relation": {
|
||||
"function": "aminer_org_patent_relation",
|
||||
"help": "Org patent list (max page_size 10,000).",
|
||||
"args": [
|
||||
("--id", "id", "str", "org_id (required)"),
|
||||
("--page", "page", "int", "Page number, default 1"),
|
||||
("--page-size", "page_size", "int", "Page size, default 100"),
|
||||
],
|
||||
},
|
||||
"org_disambiguate": {
|
||||
"function": "aminer_org_disambiguate",
|
||||
"help": "Normalize raw org string.",
|
||||
"args": [("--org", "org", "str", "Raw org string (required)")],
|
||||
},
|
||||
"org_disambiguate_pro": {
|
||||
"function": "aminer_org_disambiguate_pro",
|
||||
"help": "Extract primary/secondary org IDs.",
|
||||
"args": [("--org", "org", "str", "Raw org string (required)")],
|
||||
},
|
||||
# ── Venue ────────────────────────────────────────────────────────────
|
||||
"venue_search": {
|
||||
"function": "aminer_venue_search",
|
||||
"help": "Search journals/conferences by name.",
|
||||
"args": [("--name", "name", "str", "Venue name (required)")],
|
||||
},
|
||||
"venue_detail": {
|
||||
"function": "aminer_venue_detail",
|
||||
"help": "Venue details (ISSN, abbreviation, type).",
|
||||
"args": [("--id", "id", "str", "venue_id (required)")],
|
||||
},
|
||||
"venue_paper_relation": {
|
||||
"function": "aminer_venue_paper_relation",
|
||||
"help": "Papers published in a venue, optionally filtered by year.",
|
||||
"args": [
|
||||
("--id", "id", "str", "venue_id (required)"),
|
||||
("--offset", "offset", "int", "Offset, default 0"),
|
||||
("--limit", "limit", "int", "Limit, default 20"),
|
||||
("--year", "year", "int", "Publication year"),
|
||||
],
|
||||
},
|
||||
# ── Patent ───────────────────────────────────────────────────────────
|
||||
"patent_search": {
|
||||
"function": "aminer_patent_search",
|
||||
"help": "Search patents by name/keyword.",
|
||||
"args": [
|
||||
("--query", "query", "str", "Search query (required)"),
|
||||
("--page", "page", "int", "Page number, default 0"),
|
||||
("--size", "size", "int", "Page size, default 10"),
|
||||
],
|
||||
},
|
||||
"patent_info": {
|
||||
"function": "aminer_patent_info",
|
||||
"help": "Basic patent info.",
|
||||
"args": [("--id", "id", "str", "patent_id (required)")],
|
||||
},
|
||||
"patent_detail": {
|
||||
"function": "aminer_patent_detail",
|
||||
"help": "Full patent details (abstract, IPC, claims).",
|
||||
"args": [("--id", "id", "str", "patent_id (required)")],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config loading and HTTP
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def load_config() -> dict[str, Any]:
|
||||
for path in CONFIG_PATHS:
|
||||
try:
|
||||
cfg = json.loads(path.read_text(encoding="utf-8"))
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise SystemExit(f"failed to read {path}: {exc}") from exc
|
||||
if cfg.get("baseUrl") and cfg.get("apiKey"):
|
||||
if not cfg.get("token"):
|
||||
print(
|
||||
f"warning: {path} has no 'token' field; the gateway requires "
|
||||
"X-Token for auth and will return 401.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return cfg
|
||||
raise SystemExit(
|
||||
"z-ai config not found. Create .z-ai-config in cwd, $HOME, or /etc "
|
||||
"with {\"baseUrl\": ..., \"apiKey\": ..., \"token\": ...}."
|
||||
)
|
||||
|
||||
|
||||
def invoke(config: dict[str, Any], function_name: str, arguments: dict[str, Any]) -> Any:
|
||||
base_url = str(config["baseUrl"]).rstrip("/")
|
||||
url = f"{base_url}/functions/invoke"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "aminer-academic-search-skill/1.0",
|
||||
"Authorization": f"Bearer {config['apiKey']}",
|
||||
"X-Z-AI-From": "Z",
|
||||
}
|
||||
if config.get("token"):
|
||||
headers["X-Token"] = str(config["token"])
|
||||
if config.get("chatId"):
|
||||
headers["X-Chat-Id"] = str(config["chatId"])
|
||||
if config.get("userId"):
|
||||
headers["X-User-Id"] = str(config["userId"])
|
||||
|
||||
body = json.dumps(
|
||||
{"function_name": function_name, "arguments": arguments},
|
||||
ensure_ascii=False,
|
||||
).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=body, headers=headers, method="POST")
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp: # nosec B310
|
||||
raw = resp.read().decode("utf-8")
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace") if exc.fp else ""
|
||||
hint = ""
|
||||
if exc.code == 401 and "X-Token" in detail:
|
||||
hint = " (hint: add a valid 'token' field to .z-ai-config)"
|
||||
elif exc.code == 403:
|
||||
hint = " (hint: request rejected by auth — check 'apiKey' / X-Z-AI-From)"
|
||||
raise SystemExit(f"http {exc.code}: {detail or exc.reason}{hint}") from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise SystemExit(f"gateway unreachable: {exc.reason}") from exc
|
||||
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise SystemExit(f"invalid json response: {exc}") from exc
|
||||
|
||||
if isinstance(parsed.get("error"), str) and parsed["error"]:
|
||||
raise SystemExit(f"gateway error: {parsed['error']}")
|
||||
return parsed.get("result")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Argparse wiring
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _cast(kind: str) -> Callable[[str], Any]:
|
||||
if kind == "int":
|
||||
return int
|
||||
if kind == "float":
|
||||
return float
|
||||
if kind == "json":
|
||||
return lambda s: json.loads(s)
|
||||
return str
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Call one of 27 AMiner Open Platform APIs via the z-ai gateway.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
sub = parser.add_subparsers(dest="action", required=True, metavar="ACTION")
|
||||
for name, meta in ACTIONS.items():
|
||||
p = sub.add_parser(
|
||||
name,
|
||||
help=meta["help"],
|
||||
description=f"{meta['help']} function_name: {meta['function']}",
|
||||
)
|
||||
for flag, dest, kind, help_text in meta["args"]:
|
||||
if kind == "bool":
|
||||
p.add_argument(flag, dest=dest, action="store_true", help=help_text)
|
||||
else:
|
||||
p.add_argument(flag, dest=dest, type=_cast(kind), help=help_text)
|
||||
return parser
|
||||
|
||||
|
||||
def collect_arguments(ns: argparse.Namespace, spec: list[_Arg]) -> dict[str, Any]:
|
||||
args: dict[str, Any] = {}
|
||||
for _flag, dest, kind, _help in spec:
|
||||
value = getattr(ns, dest, None)
|
||||
if value is None:
|
||||
continue
|
||||
if kind == "bool" and value is False:
|
||||
continue
|
||||
args[dest] = value
|
||||
return args
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = build_parser()
|
||||
ns = parser.parse_args()
|
||||
|
||||
meta = ACTIONS[ns.action]
|
||||
arguments = collect_arguments(ns, meta["args"])
|
||||
|
||||
config = load_config()
|
||||
result = invoke(config, meta["function"], arguments)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user