open-knowledge-studio 0.2.2__tar.gz → 0.2.3__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.
Files changed (51) hide show
  1. {open_knowledge_studio-0.2.2/open_knowledge_studio.egg-info → open_knowledge_studio-0.2.3}/PKG-INFO +1 -1
  2. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/user-prompt-recall.sh +3 -1
  3. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/cli.py +78 -4
  4. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/config.py +11 -3
  5. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/metrics.py +7 -3
  6. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/store.py +32 -17
  7. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3/open_knowledge_studio.egg-info}/PKG-INFO +1 -1
  8. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/SOURCES.txt +1 -0
  9. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/pyproject.toml +1 -1
  10. open_knowledge_studio-0.2.3/setup.py +42 -0
  11. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/LICENSE +0 -0
  12. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/MANIFEST.in +0 -0
  13. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/README.md +0 -0
  14. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/__init__.py +0 -0
  15. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/_meta/frontmatter-schema.md +0 -0
  16. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/_meta/learning-schema.json +0 -0
  17. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/_meta/raw-evidence-schema.md +0 -0
  18. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/pre-compact.sh +0 -0
  19. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/session-start.sh +0 -0
  20. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/user-prompt-recall.py +0 -0
  21. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/validate-wiki-write.sh +0 -0
  22. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/rules/raw-intake.md +0 -0
  23. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/rules/wiki-writing.md +0 -0
  24. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/settings.json +0 -0
  25. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/archive/SKILL.md +0 -0
  26. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/compile/SKILL.md +0 -0
  27. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/ingest/SKILL.md +0 -0
  28. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/lint/SKILL.md +0 -0
  29. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/promote/SKILL.md +0 -0
  30. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/query/SKILL.md +0 -0
  31. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/start/SKILL.md +0 -0
  32. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/status/SKILL.md +0 -0
  33. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/settings/handlers.json +0 -0
  34. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/settings/input-sources.json +0 -0
  35. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/settings/raw-tools.example.json +0 -0
  36. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/templates/anti-pattern.md +0 -0
  37. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/templates/concept.md +0 -0
  38. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/templates/draft.md +0 -0
  39. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/templates/strategy.md +0 -0
  40. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/distiller.py +0 -0
  41. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/health.py +0 -0
  42. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/recall.py +0 -0
  43. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/dependency_links.txt +0 -0
  44. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/entry_points.txt +0 -0
  45. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/requires.txt +0 -0
  46. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/top_level.txt +0 -0
  47. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/setup.cfg +0 -0
  48. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/tests/test_distiller.py +0 -0
  49. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/tests/test_health.py +0 -0
  50. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/tests/test_init.py +0 -0
  51. {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/tests/test_recall.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: open-knowledge-studio
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: File-based knowledge engineering CLI for Claude Code
5
5
  Author: open-agent-power
6
6
  License: MIT
@@ -3,4 +3,6 @@
3
3
  # Passes the original stdin (editor JSON payload) through to the Python hook,
4
4
  # and drops jieba's stderr chatter so only the clean <recalled-memory> block
5
5
  # reaches stdout. Fails open (exit 0) so a prompt is never blocked.
6
- exec python3 "$(dirname "$0")/user-prompt-recall.py" 2>/dev/null
6
+ # OKS_PYTHON is baked in by `oks hook install` to point at the interpreter
7
+ # that can import knowledge_studio (pipx/venv safe); falls back to python3.
8
+ exec "${OKS_PYTHON:-python3}" "$(dirname "$0")/user-prompt-recall.py" 2>/dev/null
@@ -24,6 +24,27 @@ app = typer.Typer(
24
24
  )
25
25
  console = Console()
26
26
 
27
+
28
+ def _version_callback(value: bool):
29
+ if value:
30
+ from importlib.metadata import version, PackageNotFoundError
31
+ try:
32
+ console.print(f"oks {version('open-knowledge-studio')}")
33
+ except PackageNotFoundError:
34
+ console.print("oks (development, not installed as a package)")
35
+ raise typer.Exit()
36
+
37
+
38
+ @app.callback()
39
+ def _main(
40
+ version: bool = typer.Option(
41
+ False, "--version", "-V", callback=_version_callback, is_eager=True,
42
+ help="Show the oks version and exit.",
43
+ ),
44
+ ):
45
+ pass
46
+
47
+
27
48
  wiki_app = typer.Typer(help="Wiki page management.")
28
49
  drafts_app = typer.Typer(help="Draft proposal management.")
29
50
  config_app = typer.Typer(help="Global configuration (~/.oks/config.json).")
@@ -191,8 +212,18 @@ def wiki_create(
191
212
  if not content and not sys.stdin.isatty():
192
213
  content = sys.stdin.read()
193
214
 
194
- type_map = {"concept": "concepts", "strategy": "strategies", "anti-pattern": "anti-patterns"}
195
- wiki_type = type_map.get(page_type, "concepts")
215
+ type_map = {
216
+ "concept": "concepts", "concepts": "concepts",
217
+ "strategy": "strategies", "strategies": "strategies",
218
+ "anti-pattern": "anti-patterns", "anti-patterns": "anti-patterns",
219
+ }
220
+ wiki_type = type_map.get(page_type)
221
+ if wiki_type is None:
222
+ console.print(
223
+ f"[yellow]Unknown --type '{page_type}' — using 'concept'. "
224
+ f"Valid: concept, strategy, anti-pattern.[/yellow]"
225
+ )
226
+ wiki_type = "concepts"
196
227
 
197
228
  path = store.write_wiki_page(
198
229
  title=title,
@@ -617,7 +648,11 @@ def init(
617
648
 
618
649
  base, is_packaged = _asset_source()
619
650
  if base is None:
620
- console.print("[yellow]No bundled assets found — skills/templates not materialized.[/yellow]")
651
+ console.print(
652
+ "[yellow]No bundled assets found — skills/templates not materialized.[/yellow]\n"
653
+ " Source installs lack the asset bundle. Fix: pip install open-knowledge-studio,\n"
654
+ " or run python cli/scripts/bundle_assets.py in the repo before installing."
655
+ )
621
656
  else:
622
657
  copied = _materialize_assets(root, base, is_packaged, overwrite=upgrade)
623
658
  if copied:
@@ -696,6 +731,12 @@ def _ensure_recall_scripts(root: Path) -> list[str]:
696
731
  f"bundled hook script not found: {name} (asset source: {src_dir})"
697
732
  )
698
733
  shutil.copy2(src_dir / name, dest)
734
+ if name.endswith(".sh"):
735
+ import sys
736
+ text = dest.read_text(encoding="utf-8").replace(
737
+ '"${OKS_PYTHON:-python3}"', f'"${{OKS_PYTHON:-{sys.executable}}}"'
738
+ )
739
+ dest.write_text(text, encoding="utf-8")
699
740
  dest.chmod(dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
700
741
  created.append(name)
701
742
  return created
@@ -757,12 +798,29 @@ def hook_install(
757
798
  console.print("[red]--editor must be one of: claude, qoder, both[/red]")
758
799
  raise typer.Exit(1)
759
800
 
801
+ import platform
802
+ if platform.system() == "Windows":
803
+ console.print(
804
+ "[yellow]Warning: hooks are bash scripts and will not run on native Windows.[/yellow]\n"
805
+ " Use WSL (or Git Bash configured as the hook shell) for auto-recall to work."
806
+ )
807
+
760
808
  root = _instance_root(path)
761
809
  if not root.is_dir():
762
810
  console.print(f"[red]Instance root not found:[/red] {root}")
763
811
  raise typer.Exit(1)
764
812
 
765
- created = _ensure_recall_scripts(root)
813
+ try:
814
+ created = _ensure_recall_scripts(root)
815
+ except FileNotFoundError as e:
816
+ console.print(
817
+ f"[red]Cannot install hook — bundled assets missing.[/red]\n"
818
+ f" {e}\n"
819
+ f" This happens when oks was installed from source without the asset bundle.\n"
820
+ f" Fix: [bold]pip install open-knowledge-studio[/bold] (PyPI wheel includes assets),\n"
821
+ f" or run [bold]python cli/scripts/bundle_assets.py[/bold] in the repo before installing."
822
+ )
823
+ raise typer.Exit(1)
766
824
  if created:
767
825
  console.print(f"[green]Installed hook script:[/green] {', '.join(created)}")
768
826
 
@@ -788,6 +846,22 @@ def hook_status(
788
846
  script = root / ".claude" / "hooks" / "user-prompt-recall.sh"
789
847
  console.print(f"[bold]Instance:[/bold] {root}")
790
848
  console.print(f" script: {'present' if script.is_file() else 'missing'} ({script})")
849
+ if script.is_file():
850
+ import os
851
+ import re
852
+ import subprocess
853
+ m = re.search(r"\$\{OKS_PYTHON:-([^}]+)\}", script.read_text(encoding="utf-8"))
854
+ py = os.environ.get("OKS_PYTHON") or (m.group(1) if m else "python3")
855
+ try:
856
+ ok = subprocess.run(
857
+ [py, "-c", "import knowledge_studio"],
858
+ capture_output=True, timeout=15,
859
+ ).returncode == 0
860
+ except (OSError, subprocess.TimeoutExpired):
861
+ ok = False
862
+ state = ("[green]importable[/green]" if ok
863
+ else "[red]NOT importable — hook will silently no-op; re-run oks hook install[/red]")
864
+ console.print(f" engine: {state} (python: {py})")
791
865
  for name, rel in _HOOK_EDITORS.items():
792
866
  settings_path = root / rel
793
867
  wired = _hook_is_wired(settings_path, _RECALL_HOOK_CMD)
@@ -61,8 +61,14 @@ def load_config() -> dict[str, Any]:
61
61
  path = config_path()
62
62
  if not path.exists():
63
63
  return dict(DEFAULT_CONFIG)
64
- with open(path) as f:
65
- return json.load(f)
64
+ try:
65
+ with open(path) as f:
66
+ return json.load(f)
67
+ except json.JSONDecodeError as e:
68
+ raise ValueError(
69
+ f"{path} is corrupt ({e}). Fix the JSON manually, or delete it and "
70
+ f"re-run `oks config init` / `oks init <path>`."
71
+ ) from e
66
72
 
67
73
 
68
74
  def save_config(config: dict[str, Any]) -> None:
@@ -70,6 +76,7 @@ def save_config(config: dict[str, Any]) -> None:
70
76
  path = config_path()
71
77
  path.parent.mkdir(parents=True, exist_ok=True)
72
78
 
79
+ import contextlib
73
80
  import tempfile
74
81
  fd, tmp = tempfile.mkstemp(dir=str(path.parent), suffix=".tmp")
75
82
  try:
@@ -77,7 +84,8 @@ def save_config(config: dict[str, Any]) -> None:
77
84
  json.dump(config, f, indent=2, ensure_ascii=False)
78
85
  os.replace(tmp, path)
79
86
  except Exception:
80
- os.unlink(tmp)
87
+ with contextlib.suppress(OSError):
88
+ os.unlink(tmp)
81
89
  raise
82
90
 
83
91
 
@@ -5,7 +5,7 @@ Removed KnowledgeStore dependency. Uses store functions.
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
- from datetime import UTC, datetime, timedelta
8
+ from datetime import UTC, date, datetime, timedelta
9
9
 
10
10
  from knowledge_studio.store import list_wiki_pages, repo_root, wiki_dir
11
11
 
@@ -86,11 +86,15 @@ def _compute_credibility(pages: list[dict], ninety_days_ago: datetime) -> dict:
86
86
  }
87
87
 
88
88
 
89
- def _parse_date(value: str | None) -> datetime:
89
+ def _parse_date(value) -> datetime:
90
90
  if not value:
91
91
  return datetime.min.replace(tzinfo=UTC)
92
+ if isinstance(value, datetime):
93
+ return value if value.tzinfo else value.replace(tzinfo=UTC)
94
+ if isinstance(value, date):
95
+ return datetime(value.year, value.month, value.day, tzinfo=UTC)
92
96
  try:
93
- dt = datetime.fromisoformat(value)
97
+ dt = datetime.fromisoformat(str(value))
94
98
  if dt.tzinfo is None:
95
99
  dt = dt.replace(tzinfo=UTC)
96
100
  return dt
@@ -13,7 +13,7 @@ import math
13
13
  import os
14
14
  import re
15
15
  import tempfile
16
- from datetime import UTC, datetime
16
+ from datetime import UTC, date, datetime
17
17
  from pathlib import Path
18
18
 
19
19
  import yaml
@@ -44,8 +44,13 @@ def repo_root() -> Path:
44
44
  kb_path = load_config().get("knowledge_base_path")
45
45
  if kb_path:
46
46
  return Path(kb_path)
47
- except Exception:
48
- pass
47
+ except Exception as e:
48
+ import sys
49
+ print(
50
+ f"oks: warning: could not read ~/.oks/config.json ({e}); "
51
+ f"falling back to current directory as KB root",
52
+ file=sys.stderr,
53
+ )
49
54
  return Path(os.getcwd())
50
55
 
51
56
 
@@ -187,11 +192,16 @@ def compute_score(meta: dict, access_count: int = 0, config: dict | None = None)
187
192
  if archived or status == "dropped":
188
193
  return 0.0
189
194
 
190
- created_str = meta.get("created", "")
191
- try:
192
- created = datetime.fromisoformat(created_str)
193
- except (ValueError, TypeError):
194
- created = datetime.now(UTC)
195
+ created_raw = meta.get("created", "")
196
+ if isinstance(created_raw, datetime):
197
+ created = created_raw
198
+ elif isinstance(created_raw, date):
199
+ created = datetime(created_raw.year, created_raw.month, created_raw.day)
200
+ else:
201
+ try:
202
+ created = datetime.fromisoformat(str(created_raw))
203
+ except (ValueError, TypeError):
204
+ created = datetime.now(UTC)
195
205
 
196
206
  tz = UTC if not created.tzinfo else created.tzinfo
197
207
  days_old = max(0, (datetime.now(UTC) - created.replace(tzinfo=tz)).days)
@@ -274,18 +284,19 @@ def _find_file_by_slug(slug: str) -> Path | None:
274
284
  return None
275
285
 
276
286
 
277
- def _update_frontmatter_field(file_path: Path, field: str, value) -> None:
287
+ def _update_frontmatter_field(file_path: Path, field: str, value) -> bool:
278
288
  text = file_path.read_text(encoding="utf-8")
279
289
  parts = text.split("---", 2)
280
290
  if len(parts) < 3:
281
- return
291
+ return False
282
292
  try:
283
293
  meta = yaml.safe_load(parts[1].strip()) or {}
284
294
  except yaml.YAMLError:
285
- return
295
+ return False
286
296
  meta[field] = value
287
297
  new_fm = yaml.dump(meta, default_flow_style=False, allow_unicode=True, sort_keys=False)
288
298
  _atomic_write(file_path, f"---\n{new_fm}---\n{parts[2]}")
299
+ return True
289
300
 
290
301
 
291
302
  def _reinforce_confidence(slug: str) -> None:
@@ -493,17 +504,16 @@ def pin_page(slug: str) -> bool:
493
504
  f = _find_file_by_slug(slug)
494
505
  if not f:
495
506
  return False
496
- _update_frontmatter_field(f, "pinned", True)
497
- return True
507
+ return _update_frontmatter_field(f, "pinned", True)
498
508
 
499
509
 
500
510
  def archive_page(slug: str) -> bool:
501
511
  f = _find_file_by_slug(slug)
502
512
  if not f:
503
513
  return False
504
- _update_frontmatter_field(f, "status", "dropped")
505
- _update_frontmatter_field(f, "archived", True)
506
- return True
514
+ dropped = _update_frontmatter_field(f, "status", "dropped")
515
+ archived = _update_frontmatter_field(f, "archived", True)
516
+ return dropped and archived
507
517
 
508
518
 
509
519
  def list_drafts() -> list[dict]:
@@ -547,7 +557,12 @@ def promote_draft(
547
557
  body = meta.get("body", "")
548
558
 
549
559
  final_title = title or meta.get("title", slug)
550
- final_type = (wiki_type or meta.get("draft_type", "concepts")).rstrip("s") + "s"
560
+ _type_dirs = {
561
+ "concept": "concepts", "concepts": "concepts",
562
+ "strategy": "strategies", "strategies": "strategies",
563
+ "anti-pattern": "anti-patterns", "anti-patterns": "anti-patterns",
564
+ }
565
+ final_type = _type_dirs.get(wiki_type or meta.get("draft_type", "concept"), "concepts")
551
566
  final_area = area or meta.get("draft_area", "computing")
552
567
  human_note = meta.get("source_note") or None
553
568
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: open-knowledge-studio
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: File-based knowledge engineering CLI for Claude Code
5
5
  Author: open-agent-power
6
6
  License: MIT
@@ -2,6 +2,7 @@ LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
4
  pyproject.toml
5
+ setup.py
5
6
  knowledge_studio/__init__.py
6
7
  knowledge_studio/cli.py
7
8
  knowledge_studio/config.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "open-knowledge-studio"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "File-based knowledge engineering CLI for Claude Code"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,42 @@
1
+ """Build hook: bundle the shareable asset layer during any source build.
2
+
3
+ The repo root is the single source of truth for `.claude/`, `templates/`,
4
+ `_meta/`, `settings/`. When building from a git checkout (pip install ./cli,
5
+ pip install git+...#subdirectory=cli, python -m build), copy them into
6
+ `knowledge_studio/_assets/` before build_py runs, so source installs are
7
+ identical to PyPI wheels. When building from an sdist the repo root is
8
+ absent and `_assets/` is already included via MANIFEST.in — skip silently.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import shutil
13
+ from pathlib import Path
14
+
15
+ from setuptools import setup
16
+ from setuptools.command.build_py import build_py
17
+
18
+ _MAP = [
19
+ (".claude", "claude"),
20
+ ("templates", "templates"),
21
+ ("_meta", "_meta"),
22
+ ("settings", "settings"),
23
+ ]
24
+
25
+
26
+ class build_py_with_assets(build_py):
27
+ def run(self):
28
+ cli_dir = Path(__file__).resolve().parent
29
+ repo_root = cli_dir.parent
30
+ if (repo_root / ".claude").is_dir() and (repo_root / "templates").is_dir():
31
+ dest_root = cli_dir / "knowledge_studio" / "_assets"
32
+ if dest_root.exists():
33
+ shutil.rmtree(dest_root)
34
+ dest_root.mkdir(parents=True)
35
+ for src_name, dest_name in _MAP:
36
+ src = repo_root / src_name
37
+ if src.is_dir():
38
+ shutil.copytree(src, dest_root / dest_name)
39
+ super().run()
40
+
41
+
42
+ setup(cmdclass={"build_py": build_py_with_assets})