|
13 | 13 | BOOT_FILES = ("README.md", "AGENTS.md", "BOOTSTRAP.md", "TOOLS.md") |
14 | 14 | LIST_SECTIONS = {"required_agents", "optional_agents", "pack_rules"} |
15 | 15 | REQUIRED_MANIFEST_KEYS = {"version", "name", "status", "required_agents", "optional_agents", "pack_rules"} |
| 16 | +REPO_ROOT_MARKERS = ( |
| 17 | + "pyproject.toml", |
| 18 | + "agents/PACK-MANIFEST.yaml", |
| 19 | + "config/openclaw.public.example.jsonc", |
| 20 | +) |
16 | 21 |
|
17 | 22 |
|
18 | | -def repo_root() -> Path: |
19 | | - return Path(__file__).resolve().parents[2] |
| 23 | +def looks_like_repo_root(path: Path) -> bool: |
| 24 | + return all((path / marker).exists() for marker in REPO_ROOT_MARKERS) |
| 25 | + |
| 26 | + |
| 27 | +def repo_root(cwd: Path | None = None, module_path: Path | None = None) -> Path: |
| 28 | + candidates: list[Path] = [] |
| 29 | + if cwd is not None: |
| 30 | + candidates.append(cwd.resolve()) |
| 31 | + else: |
| 32 | + candidates.append(Path.cwd().resolve()) |
| 33 | + |
| 34 | + source_path = module_path.resolve() if module_path is not None else Path(__file__).resolve() |
| 35 | + candidates.extend(source_path.parents) |
| 36 | + |
| 37 | + seen: set[Path] = set() |
| 38 | + for candidate in candidates: |
| 39 | + if candidate in seen: |
| 40 | + continue |
| 41 | + seen.add(candidate) |
| 42 | + if looks_like_repo_root(candidate): |
| 43 | + return candidate |
| 44 | + raise FileNotFoundError("Unable to locate the HyperClaw-Max repository root; pass --repo explicitly.") |
20 | 45 |
|
21 | 46 |
|
22 | 47 | def now_iso() -> str: |
@@ -205,15 +230,15 @@ def materialize_pack( |
205 | 230 | def main() -> int: |
206 | 231 | ap = argparse.ArgumentParser(description="Materialize the public HyperClaw-Max pack over a target root") |
207 | 232 | ap.add_argument("target_root", type=Path, help="Destination root that will host config, workspaces, and runtime state") |
208 | | - ap.add_argument("--repo", type=Path, default=repo_root()) |
| 233 | + ap.add_argument("--repo", type=Path, default=None) |
209 | 234 | ap.add_argument("--include-optional", action="append", default=[], help="Optional agent id to materialize") |
210 | 235 | ap.add_argument("--all-optional", action="store_true", help="Materialize all optional overlay agents") |
211 | 236 | ap.add_argument("--force", action="store_true", help="Overwrite existing materialized files") |
212 | 237 | ap.add_argument("--format", choices=["json", "human"], default="human") |
213 | 238 | args = ap.parse_args() |
214 | 239 |
|
215 | 240 | payload = materialize_pack( |
216 | | - args.repo, |
| 241 | + args.repo.resolve() if args.repo is not None else repo_root(), |
217 | 242 | args.target_root, |
218 | 243 | include_optional=set(args.include_optional), |
219 | 244 | include_all_optional=args.all_optional, |
|
0 commit comments