v0.20 alpha skill-driven python core
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deploy platform adapter templates outside the repository checkout."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from scripts.runtime.skills import SkillRegistry
|
||||
|
||||
CODEX_TEMPLATE = REPO_ROOT / "codex_adapter_templates" / "codex"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeployResult:
|
||||
platform: str
|
||||
target: Path
|
||||
written: list[Path] = field(default_factory=list)
|
||||
skipped: list[Path] = field(default_factory=list)
|
||||
planned: list[Path] = field(default_factory=list)
|
||||
backups: list[Path] = field(default_factory=list)
|
||||
|
||||
|
||||
def default_codex_home(
|
||||
*,
|
||||
env: dict[str, str] | None = None,
|
||||
user_home: Path | None = None,
|
||||
) -> Path:
|
||||
values = os.environ if env is None else env
|
||||
if values.get("CODEX_HOME"):
|
||||
return Path(values["CODEX_HOME"]).expanduser()
|
||||
home = Path.home() if user_home is None else user_home
|
||||
return home / ".codex"
|
||||
|
||||
|
||||
def copy_tree_contents(
|
||||
src: Path,
|
||||
dst: Path,
|
||||
*,
|
||||
force: bool,
|
||||
dry_run: bool = False,
|
||||
backup_existing: bool = True,
|
||||
exclude: set[Path] | None = None,
|
||||
) -> DeployResult:
|
||||
if not src.exists():
|
||||
raise FileNotFoundError(f"adapter template source not found: {src}")
|
||||
|
||||
result = DeployResult(platform="copy", target=dst)
|
||||
excluded = exclude or set()
|
||||
for item in sorted(src.rglob("*")):
|
||||
rel = item.relative_to(src)
|
||||
if rel in excluded:
|
||||
continue
|
||||
target = dst / rel
|
||||
if item.is_dir():
|
||||
if not dry_run:
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
continue
|
||||
|
||||
if target.exists() and not force:
|
||||
result.skipped.append(target)
|
||||
continue
|
||||
|
||||
result.planned.append(target)
|
||||
if dry_run:
|
||||
continue
|
||||
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
if target.exists() and force and backup_existing:
|
||||
backup = target.with_name(f"{target.name}.bak")
|
||||
shutil.copy2(target, backup)
|
||||
result.backups.append(backup)
|
||||
shutil.copy2(item, target)
|
||||
result.written.append(target)
|
||||
return result
|
||||
|
||||
|
||||
def _merge_results(platform: str, target: Path, parts: list[DeployResult]) -> DeployResult:
|
||||
merged = DeployResult(platform=platform, target=target)
|
||||
for part in parts:
|
||||
merged.written.extend(part.written)
|
||||
merged.skipped.extend(part.skipped)
|
||||
merged.planned.extend(part.planned)
|
||||
merged.backups.extend(part.backups)
|
||||
return merged
|
||||
|
||||
|
||||
def copy_registered_skills(dst: Path, *, force: bool, dry_run: bool = False) -> DeployResult:
|
||||
result = DeployResult(platform="skills", target=dst)
|
||||
for skill in SkillRegistry().list():
|
||||
part = copy_tree_contents(skill.path.parent, dst / skill.name, force=force, dry_run=dry_run)
|
||||
result.written.extend(part.written)
|
||||
result.skipped.extend(part.skipped)
|
||||
result.planned.extend(part.planned)
|
||||
result.backups.extend(part.backups)
|
||||
return result
|
||||
|
||||
|
||||
def deploy_codex(
|
||||
*,
|
||||
target: Path | None = None,
|
||||
force: bool = False,
|
||||
skip_skills: bool = False,
|
||||
dry_run: bool = False,
|
||||
include_config: bool = False,
|
||||
repo_root: Path = REPO_ROOT,
|
||||
) -> DeployResult:
|
||||
codex_home = (target or default_codex_home()).expanduser()
|
||||
template = repo_root / "codex_adapter_templates" / "codex"
|
||||
|
||||
parts = [
|
||||
copy_tree_contents(
|
||||
template,
|
||||
codex_home,
|
||||
force=force,
|
||||
dry_run=dry_run,
|
||||
exclude=set() if include_config else {Path("config.toml")},
|
||||
),
|
||||
]
|
||||
if not skip_skills:
|
||||
parts.append(copy_registered_skills(codex_home / "skills", force=force, dry_run=dry_run))
|
||||
return _merge_results("codex", codex_home, parts)
|
||||
|
||||
|
||||
def print_result(result: DeployResult) -> None:
|
||||
action = "planned" if result.planned and not result.written else "written"
|
||||
print(f"{result.platform} adapter deployment")
|
||||
print(f" target: {result.target}")
|
||||
print(f" files {action}: {len(result.planned if action == 'planned' else result.written)}")
|
||||
print(f" files skipped: {len(result.skipped)}")
|
||||
print(f" backups: {len(result.backups)}")
|
||||
if result.written:
|
||||
for path in result.written:
|
||||
print(f" {path}")
|
||||
elif result.planned:
|
||||
for path in result.planned:
|
||||
print(f" {path}")
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Deploy Deep Research surface adapters")
|
||||
sub = parser.add_subparsers(dest="platform", required=True)
|
||||
|
||||
codex = sub.add_parser("codex", help="Deploy Codex adapter to CODEX_HOME or a target directory")
|
||||
codex.add_argument("--target", type=Path, help="Codex home target; defaults to $CODEX_HOME or ~/.codex")
|
||||
codex.add_argument("--force", action="store_true", help="overwrite existing files and create .bak backups")
|
||||
codex.add_argument("--skip-skills", action="store_true", help="do not copy canonical skills into target/skills")
|
||||
codex.add_argument("--include-config", action="store_true", help="also copy config.toml; off by default to avoid overwriting global Codex config")
|
||||
codex.add_argument("--dry-run", action="store_true", help="show files that would be written")
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = build_parser().parse_args()
|
||||
if args.platform == "codex":
|
||||
result = deploy_codex(
|
||||
target=args.target,
|
||||
force=args.force,
|
||||
skip_skills=args.skip_skills,
|
||||
dry_run=args.dry_run,
|
||||
include_config=args.include_config,
|
||||
)
|
||||
print_result(result)
|
||||
print()
|
||||
print("Run Codex from this repository after deployment:")
|
||||
if args.include_config:
|
||||
print(" codex --profile deep-research")
|
||||
else:
|
||||
print(" codex")
|
||||
print("Note: config.toml is not copied by default. Use --include-config only if you want the bundled profile.")
|
||||
return 0
|
||||
raise SystemExit(f"unsupported platform: {args.platform}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user