mirror of
https://github.com/Ar9av/obsidian-wiki.git
synced 2026-09-14 20:36:34 +08:00
fix(cli): report the install mode that actually happened (#205)
On Windows without symlink privilege, install_skills falls back to copying per agent directory. The fallback was tracked per call, so the warning printed 12 times and the setup summary still claimed 'mode: symlink'. Track the fallback module-wide: warn once, and report 'mode: copy'. Verified on a Windows Server 2022 box (ACP 936, non-admin user).
This commit is contained in:
+10
-5
@@ -138,6 +138,11 @@ def _is_symlink_privilege_error(error: OSError) -> bool:
|
||||
return os.name == "nt" and getattr(error, "winerror", None) == 1314
|
||||
|
||||
|
||||
# Set when a symlink install falls back to copying, so the warning prints once
|
||||
# and the setup summary can report the mode that actually happened.
|
||||
_SYMLINK_FALLBACK = False
|
||||
|
||||
|
||||
def install_skills(
|
||||
target_dir: Path,
|
||||
label: str,
|
||||
@@ -149,9 +154,9 @@ def install_skills(
|
||||
"""Install bundled skills into *target_dir*. Returns the count installed."""
|
||||
src_root = skills_dir()
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
global _SYMLINK_FALLBACK
|
||||
installed = 0
|
||||
install_mode = mode
|
||||
warned_copy_fallback = False
|
||||
install_mode = "copy" if _SYMLINK_FALLBACK else mode
|
||||
for skill in sorted(p for p in src_root.iterdir() if p.is_dir()):
|
||||
name = skill.name
|
||||
if subset is not None and name not in subset:
|
||||
@@ -176,13 +181,13 @@ def install_skills(
|
||||
if not _is_symlink_privilege_error(error):
|
||||
raise
|
||||
install_mode = "copy"
|
||||
if not warned_copy_fallback:
|
||||
if not _SYMLINK_FALLBACK:
|
||||
print(
|
||||
"Warning: symbolic links are unavailable; "
|
||||
"copying skills instead. Use Developer Mode or "
|
||||
"--copy to choose this explicitly."
|
||||
)
|
||||
warned_copy_fallback = True
|
||||
_SYMLINK_FALLBACK = True
|
||||
shutil.copytree(skill, link_path)
|
||||
else: # copy
|
||||
shutil.copytree(skill, link_path)
|
||||
@@ -1010,7 +1015,7 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
||||
n = len(list_skills())
|
||||
print("\n───────────────────────────────────────────────────")
|
||||
print(" Setup complete!\n")
|
||||
print(f" Skills installed: {n} (mode: {mode})")
|
||||
print(f" Skills installed: {n} (mode: {'copy' if _SYMLINK_FALLBACK else mode})")
|
||||
if vault_path:
|
||||
print(f" Vault: {vault_path}")
|
||||
print(f" Writing profile: {writing_profile.resolve()}")
|
||||
|
||||
@@ -102,3 +102,32 @@ def test_install_skills_keeps_unrelated_symlink_errors(
|
||||
|
||||
with pytest.raises(OSError, match="access denied"):
|
||||
cli.install_skills(tmp_path / "target-skills", "test")
|
||||
|
||||
|
||||
def test_symlink_fallback_reports_copy_mode_once(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
) -> None:
|
||||
source_root = tmp_path / "bundled-skills"
|
||||
for name in ("a-skill", "b-skill"):
|
||||
skill = source_root / name
|
||||
skill.mkdir(parents=True)
|
||||
(skill / "SKILL.md").write_text(f"---\nname: {name}\n---\n", encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(cli, "skills_dir", lambda: source_root)
|
||||
monkeypatch.setattr(cli, "_SYMLINK_FALLBACK", False)
|
||||
|
||||
def deny_symlink(self, target, target_is_directory=False): # noqa: ANN001
|
||||
error = OSError(1314, "A required privilege is not held by the client")
|
||||
error.winerror = 1314
|
||||
raise error
|
||||
|
||||
monkeypatch.setattr(Path, "symlink_to", deny_symlink)
|
||||
monkeypatch.setattr(cli, "_is_symlink_privilege_error", lambda error: True)
|
||||
|
||||
for target in ("agent-one", "agent-two"):
|
||||
assert cli.install_skills(tmp_path / target, target, mode="symlink") == 2
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert out.count("symbolic links are unavailable") == 1
|
||||
assert cli._SYMLINK_FALLBACK is True
|
||||
assert (tmp_path / "agent-two" / "a-skill" / "SKILL.md").is_file()
|
||||
|
||||
Reference in New Issue
Block a user