smartbiblia 0.1.0__tar.gz
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.
- smartbiblia-0.1.0/.gitignore +31 -0
- smartbiblia-0.1.0/PKG-INFO +87 -0
- smartbiblia-0.1.0/README.md +74 -0
- smartbiblia-0.1.0/pyproject.toml +26 -0
- smartbiblia-0.1.0/src/smartbiblia/__init__.py +1 -0
- smartbiblia-0.1.0/src/smartbiblia/catalog.toml +51 -0
- smartbiblia-0.1.0/src/smartbiblia/cli.py +142 -0
- smartbiblia-0.1.0/src/smartbiblia/installer.py +76 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
wheels/
|
|
6
|
+
*.egg-info
|
|
7
|
+
**/*.lock
|
|
8
|
+
|
|
9
|
+
# Jupyter Notebook checkpoints
|
|
10
|
+
.ipynb_checkpoints
|
|
11
|
+
|
|
12
|
+
# VS Code settings
|
|
13
|
+
.vscode/
|
|
14
|
+
|
|
15
|
+
# Pytest cache
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
|
|
18
|
+
# Virtual environments
|
|
19
|
+
.venv
|
|
20
|
+
|
|
21
|
+
# Environment files (all locations)
|
|
22
|
+
.env
|
|
23
|
+
**/.env
|
|
24
|
+
.env.*
|
|
25
|
+
**/.env.*
|
|
26
|
+
|
|
27
|
+
# Private
|
|
28
|
+
outputs-examples/
|
|
29
|
+
**/CHANGELOG.json
|
|
30
|
+
**/*.zip
|
|
31
|
+
**/DEPLOYMENT.md
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smartbiblia
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI to install smartbiblia skills and MCP servers into your agent workspace
|
|
5
|
+
Project-URL: Repository, https://github.com/smartbiblia-solutions/agentic-stack
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: bibliography,claude,codex,hermes,library,mcp,openclaw,research,skills
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: rich>=13
|
|
11
|
+
Requires-Dist: typer>=0.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# smartbiblia
|
|
15
|
+
|
|
16
|
+
CLI d'installation des skills [smartbiblia](https://github.com/smartbiblia-solutions/agentic-stack)
|
|
17
|
+
dans un workspace agent.
|
|
18
|
+
|
|
19
|
+
> Pour les MCP servers (OpenAlex, Sudoc SRU, Primo), voir les READMEs dans `mcp/` —
|
|
20
|
+
> ils s'installent via `git clone` ou `uv run <url>` directement, sans passer par cette CLI.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Aucune installation permanente requise — utilise [`uvx`](https://docs.astral.sh/uv/) pour lancer directement depuis PyPI :
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uvx smartbiblia list
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Ou installe globalement :
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv tool install smartbiblia
|
|
34
|
+
# puis :
|
|
35
|
+
smartbiblia list
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Commandes
|
|
39
|
+
|
|
40
|
+
### Lister les skills disponibles
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
smartbiblia list
|
|
44
|
+
smartbiblia list --tag french
|
|
45
|
+
smartbiblia list --tag open-access
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Consulter le détail d'une skill
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
smartbiblia info idref
|
|
52
|
+
smartbiblia info synthesize
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Installer une skill
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Installe dans ./skills/idref/
|
|
59
|
+
smartbiblia add idref
|
|
60
|
+
|
|
61
|
+
# Dossier personnalisé
|
|
62
|
+
smartbiblia add sudoc --dest ./mon-projet/skills
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Mettre à jour une skill installée
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
smartbiblia update idref
|
|
69
|
+
smartbiblia update synthesize
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Développement local
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cd cli/
|
|
76
|
+
uv sync
|
|
77
|
+
uv run smartbiblia list
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Publier sur PyPI
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv build
|
|
84
|
+
uv publish
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
> **Note :** mettre à jour `GITHUB_REPO` dans `installer.py` si le repo est renommé.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# smartbiblia
|
|
2
|
+
|
|
3
|
+
CLI d'installation des skills [smartbiblia](https://github.com/smartbiblia-solutions/agentic-stack)
|
|
4
|
+
dans un workspace agent.
|
|
5
|
+
|
|
6
|
+
> Pour les MCP servers (OpenAlex, Sudoc SRU, Primo), voir les READMEs dans `mcp/` —
|
|
7
|
+
> ils s'installent via `git clone` ou `uv run <url>` directement, sans passer par cette CLI.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Aucune installation permanente requise — utilise [`uvx`](https://docs.astral.sh/uv/) pour lancer directement depuis PyPI :
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uvx smartbiblia list
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Ou installe globalement :
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv tool install smartbiblia
|
|
21
|
+
# puis :
|
|
22
|
+
smartbiblia list
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Commandes
|
|
26
|
+
|
|
27
|
+
### Lister les skills disponibles
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
smartbiblia list
|
|
31
|
+
smartbiblia list --tag french
|
|
32
|
+
smartbiblia list --tag open-access
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Consulter le détail d'une skill
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
smartbiblia info idref
|
|
39
|
+
smartbiblia info synthesize
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Installer une skill
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Installe dans ./skills/idref/
|
|
46
|
+
smartbiblia add idref
|
|
47
|
+
|
|
48
|
+
# Dossier personnalisé
|
|
49
|
+
smartbiblia add sudoc --dest ./mon-projet/skills
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Mettre à jour une skill installée
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
smartbiblia update idref
|
|
56
|
+
smartbiblia update synthesize
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Développement local
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cd cli/
|
|
63
|
+
uv sync
|
|
64
|
+
uv run smartbiblia list
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Publier sur PyPI
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv build
|
|
71
|
+
uv publish
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
> **Note :** mettre à jour `GITHUB_REPO` dans `installer.py` si le repo est renommé.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smartbiblia"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI to install smartbiblia skills and MCP servers into your agent workspace"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["claude", "codex", "openclaw", "hermes", "skills", "mcp", "bibliography", "library", "research"]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"typer>=0.12",
|
|
15
|
+
"httpx>=0.27",
|
|
16
|
+
"rich>=13",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
smartbiblia = "smartbiblia.cli:app"
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Repository = "https://github.com/smartbiblia-solutions/agentic-stack"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/smartbiblia"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Catalogue des skills disponibles.
|
|
2
|
+
# Ce fichier est lu directement depuis GitHub par la CLI.
|
|
3
|
+
# Mettre à jour ce fichier lors de l'ajout d'une nouvelle skill.
|
|
4
|
+
|
|
5
|
+
[skills.resolve-idref]
|
|
6
|
+
description = "Alignement automatique de noms de personnes vers des PPNs IdRef avec score de confiance"
|
|
7
|
+
path = "skills/resolve-authorities-idref"
|
|
8
|
+
maturity = "experimental"
|
|
9
|
+
tags = ["authority", "french", "abes", "idref", "alignment"]
|
|
10
|
+
|
|
11
|
+
[skills.sudoc]
|
|
12
|
+
description = "Recherche dans le catalogue collectif SUDOC (livres, thèses, périodiques, manuscrits)"
|
|
13
|
+
path = "skills/search-records-sudoc"
|
|
14
|
+
maturity = "stable"
|
|
15
|
+
tags = ["catalog", "french", "unimarc", "abes", "sudoc"]
|
|
16
|
+
|
|
17
|
+
[skills.hal]
|
|
18
|
+
description = "Recherche dans HAL, dépôt ouvert français (articles, thèses, rapports)"
|
|
19
|
+
path = "skills/search-records-hal"
|
|
20
|
+
maturity = "stable"
|
|
21
|
+
tags = ["repository", "french", "open-access", "hal"]
|
|
22
|
+
|
|
23
|
+
[skills.openalex]
|
|
24
|
+
description = "Recherche dans OpenAlex, la plus grande base bibliographique mondiale en accès ouvert"
|
|
25
|
+
path = "skills/search-works-openalex"
|
|
26
|
+
maturity = "stable"
|
|
27
|
+
tags = ["global", "open-access", "openalex", "doi"]
|
|
28
|
+
|
|
29
|
+
[skills.generate-queries]
|
|
30
|
+
description = "Génération de stratégies de recherche documentaire à partir d'une question de recherche (8-15 requêtes bilingues)"
|
|
31
|
+
path = "skills/generate-search-queries"
|
|
32
|
+
maturity = "stable"
|
|
33
|
+
tags = ["search-strategy", "bilingual", "queries"]
|
|
34
|
+
|
|
35
|
+
[skills.synthesize]
|
|
36
|
+
description = "Synthèse de littérature académique (screening PRISMA, résumé, appréciation qualité, synthèse thématique/chronologique)"
|
|
37
|
+
path = "skills/synthesize-literature"
|
|
38
|
+
maturity = "stable"
|
|
39
|
+
tags = ["synthesis", "review", "prisma", "literature"]
|
|
40
|
+
|
|
41
|
+
[skills.convert-unimarc]
|
|
42
|
+
description = "Conversion de notices UNIMARC entre formats XML, JSON et ISO 2709 binaire"
|
|
43
|
+
path = "skills/convert-records-unimarc"
|
|
44
|
+
maturity = "stable"
|
|
45
|
+
tags = ["unimarc", "marc", "conversion", "format"]
|
|
46
|
+
|
|
47
|
+
[skills.dmp]
|
|
48
|
+
description = "Rédaction de Plans de Gestion de Données (PGD/DMP) selon les principes FAIR"
|
|
49
|
+
path = "skills/write-data-management-plan"
|
|
50
|
+
maturity = "experimental"
|
|
51
|
+
tags = ["dmp", "fair", "open-science", "research"]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
smartbiblia — CLI d'installation de skills pour agents.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
smartbiblia list [--tag <tag>]
|
|
6
|
+
smartbiblia add <name> [--dest <path>] [--force]
|
|
7
|
+
smartbiblia info <name>
|
|
8
|
+
smartbiblia update <name> [--dest <path>]
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Annotated, Optional
|
|
15
|
+
|
|
16
|
+
import tomllib
|
|
17
|
+
import typer
|
|
18
|
+
from rich import print
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
|
|
22
|
+
from .installer import fetch_catalog_raw, fetch_path
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(
|
|
25
|
+
name="smartbiblia",
|
|
26
|
+
help="Installe les skills smartbiblia dans ton workspace agent.",
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
)
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_catalog() -> dict:
|
|
33
|
+
try:
|
|
34
|
+
raw = fetch_catalog_raw()
|
|
35
|
+
return tomllib.loads(raw)
|
|
36
|
+
except Exception as exc:
|
|
37
|
+
print(f"[red]Impossible de charger le catalogue : {exc}[/red]")
|
|
38
|
+
raise typer.Exit(1)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _resolve(catalog: dict, name: str) -> dict:
|
|
42
|
+
meta = catalog.get("skills", {}).get(name)
|
|
43
|
+
if not meta:
|
|
44
|
+
print(f"[red]Skill '{name}' introuvable dans le catalogue.[/red]")
|
|
45
|
+
print(f"[dim]Utilisez [bold]smartbiblia list[/bold] pour voir les skills disponibles.[/dim]")
|
|
46
|
+
raise typer.Exit(1)
|
|
47
|
+
return meta
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# list
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
@app.command("list")
|
|
55
|
+
def list_cmd(
|
|
56
|
+
tag: Annotated[Optional[str], typer.Option("--tag", "-t", help="Filtrer par tag")] = None,
|
|
57
|
+
):
|
|
58
|
+
"""Liste les skills disponibles."""
|
|
59
|
+
catalog = _load_catalog()
|
|
60
|
+
|
|
61
|
+
table = Table(title="smartbiblia — skills disponibles", show_lines=False, highlight=True)
|
|
62
|
+
table.add_column("Nom", style="cyan bold", no_wrap=True)
|
|
63
|
+
table.add_column("Maturité")
|
|
64
|
+
table.add_column("Description")
|
|
65
|
+
|
|
66
|
+
for name, meta in catalog.get("skills", {}).items():
|
|
67
|
+
if tag and tag not in meta.get("tags", []):
|
|
68
|
+
continue
|
|
69
|
+
maturity = meta.get("maturity", "")
|
|
70
|
+
color = {"stable": "green", "beta": "yellow", "experimental": "red"}.get(maturity, "white")
|
|
71
|
+
table.add_row(
|
|
72
|
+
name,
|
|
73
|
+
f"[{color}]{maturity}[/{color}]",
|
|
74
|
+
meta.get("description", ""),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
console.print(table)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# info
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
@app.command()
|
|
85
|
+
def info(
|
|
86
|
+
name: Annotated[str, typer.Argument(help="Nom de la skill")],
|
|
87
|
+
):
|
|
88
|
+
"""Affiche les détails d'une skill."""
|
|
89
|
+
catalog = _load_catalog()
|
|
90
|
+
meta = _resolve(catalog, name)
|
|
91
|
+
|
|
92
|
+
print(f"\n[bold cyan]{name}[/bold cyan]")
|
|
93
|
+
print(f"[bold]Description :[/bold] {meta.get('description', '')}")
|
|
94
|
+
print(f"[bold]Maturité :[/bold] {meta.get('maturity', '')}")
|
|
95
|
+
print(f"[bold]Tags :[/bold] {', '.join(meta.get('tags', []))}")
|
|
96
|
+
print(f"[bold]Chemin repo :[/bold] {meta.get('path', '')}")
|
|
97
|
+
print()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
# add
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
@app.command()
|
|
105
|
+
def add(
|
|
106
|
+
name: Annotated[str, typer.Argument(help="Nom de la skill (ex: idref, sudoc, synthesize)")],
|
|
107
|
+
dest: Annotated[Optional[Path], typer.Option("--dest", "-d", help="Dossier de destination (défaut: ./skills/)")] = None,
|
|
108
|
+
force: Annotated[bool, typer.Option("--force", "-f", help="Écraser sans confirmation")] = False,
|
|
109
|
+
):
|
|
110
|
+
"""Installe une skill dans le workspace courant."""
|
|
111
|
+
catalog = _load_catalog()
|
|
112
|
+
meta = _resolve(catalog, name)
|
|
113
|
+
|
|
114
|
+
target: Path = (dest or Path("skills")) / name
|
|
115
|
+
|
|
116
|
+
if target.exists() and not force:
|
|
117
|
+
overwrite = typer.confirm(f"{target} existe déjà. Écraser ?", default=False)
|
|
118
|
+
if not overwrite:
|
|
119
|
+
raise typer.Exit(0)
|
|
120
|
+
|
|
121
|
+
with console.status(f"Téléchargement de la skill [cyan]{name}[/cyan]…"):
|
|
122
|
+
try:
|
|
123
|
+
fetch_path(meta["path"], target)
|
|
124
|
+
except Exception as exc:
|
|
125
|
+
print(f"[red]Erreur lors du téléchargement : {exc}[/red]")
|
|
126
|
+
raise typer.Exit(1)
|
|
127
|
+
|
|
128
|
+
print(f"[green]✓ Skill [bold]{name}[/bold] installée dans [bold]{target}[/bold][/green]")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
# update
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
@app.command()
|
|
136
|
+
def update(
|
|
137
|
+
name: Annotated[str, typer.Argument(help="Nom de la skill à mettre à jour")],
|
|
138
|
+
dest: Annotated[Optional[Path], typer.Option("--dest", "-d")] = None,
|
|
139
|
+
):
|
|
140
|
+
"""Met à jour une skill déjà installée (réinstalle depuis GitHub)."""
|
|
141
|
+
add(name=name, dest=dest, force=True)
|
|
142
|
+
print(f"[dim]Mise à jour terminée.[/dim]")
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Télécharge un sous-dossier du repo GitHub et l'installe localement.
|
|
3
|
+
Utilise l'API tarball de GitHub — pas besoin de git ni de clone complet.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import io
|
|
9
|
+
import os
|
|
10
|
+
import tarfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
GITHUB_ORG = "smartbiblia-solutions"
|
|
16
|
+
GITHUB_REPO = "agentic-stack" # à mettre à jour si le repo est renommé
|
|
17
|
+
BRANCH = "main"
|
|
18
|
+
|
|
19
|
+
_TARBALL_URL = (
|
|
20
|
+
f"https://api.github.com/repos/{GITHUB_ORG}/{GITHUB_REPO}/tarball/{BRANCH}"
|
|
21
|
+
)
|
|
22
|
+
_CATALOG_URL = (
|
|
23
|
+
f"https://raw.githubusercontent.com/{GITHUB_ORG}/{GITHUB_REPO}"
|
|
24
|
+
f"/{BRANCH}/cli/src/smartbiblia/catalog.toml"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _auth_headers() -> dict[str, str]:
|
|
29
|
+
token = os.environ.get("SMARTBIBLIA_GITHUB_TOKEN")
|
|
30
|
+
if token:
|
|
31
|
+
return {"Authorization": f"Bearer {token}"}
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def fetch_catalog_raw() -> str:
|
|
36
|
+
"""Retourne le contenu brut du catalog.toml depuis GitHub."""
|
|
37
|
+
resp = httpx.get(_CATALOG_URL, headers=_auth_headers(), timeout=10, follow_redirects=True)
|
|
38
|
+
resp.raise_for_status()
|
|
39
|
+
return resp.text
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def fetch_path(remote_path: str, dest: Path) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Télécharge le tarball du repo et extrait uniquement le sous-dossier
|
|
45
|
+
`remote_path` vers `dest`.
|
|
46
|
+
|
|
47
|
+
Le tarball GitHub préfixe tous les chemins par "<org>-<repo>-<sha>/",
|
|
48
|
+
ce préfixe est retiré à l'extraction.
|
|
49
|
+
"""
|
|
50
|
+
resp = httpx.get(
|
|
51
|
+
_TARBALL_URL,
|
|
52
|
+
headers=_auth_headers(),
|
|
53
|
+
timeout=60,
|
|
54
|
+
follow_redirects=True,
|
|
55
|
+
)
|
|
56
|
+
resp.raise_for_status()
|
|
57
|
+
|
|
58
|
+
with tarfile.open(fileobj=io.BytesIO(resp.content), mode="r:gz") as tar:
|
|
59
|
+
root_prefix = tar.getnames()[0].split("/")[0] + "/"
|
|
60
|
+
target_prefix = root_prefix + remote_path.strip("/") + "/"
|
|
61
|
+
|
|
62
|
+
members = [m for m in tar.getmembers() if m.name.startswith(target_prefix)]
|
|
63
|
+
|
|
64
|
+
if not members:
|
|
65
|
+
raise FileNotFoundError(
|
|
66
|
+
f"Chemin '{remote_path}' introuvable dans le tarball GitHub. "
|
|
67
|
+
"Vérifiez que le nom correspond bien à un dossier du repo."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
for member in members:
|
|
73
|
+
member.name = member.name[len(target_prefix):]
|
|
74
|
+
if not member.name:
|
|
75
|
+
continue
|
|
76
|
+
tar.extract(member, path=dest, filter="data")
|