2026-02-19 18:25:53 -08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
Skills Sync -- Manifest-based seeding and updating of bundled skills.
|
2026-02-19 18:25:53 -08:00
|
|
|
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
Copies bundled skills from the repo's skills/ directory into ~/.hermes/skills/
|
2026-03-06 16:13:47 -08:00
|
|
|
and uses a manifest to track which skills have been synced and their origin hash.
|
|
|
|
|
|
|
|
|
|
Manifest format (v2): each line is "skill_name:origin_hash" where origin_hash
|
|
|
|
|
is the MD5 of the bundled skill at the time it was last synced to the user dir.
|
|
|
|
|
Old v1 manifests (plain names without hashes) are auto-migrated.
|
|
|
|
|
|
|
|
|
|
Update logic:
|
|
|
|
|
- NEW skills (not in manifest): copied to user dir, origin hash recorded.
|
|
|
|
|
- EXISTING skills (in manifest, present in user dir):
|
|
|
|
|
* If user copy matches origin hash: user hasn't modified it → safe to
|
|
|
|
|
update from bundled if bundled changed. New origin hash recorded.
|
|
|
|
|
* If user copy differs from origin hash: user customized it → SKIP.
|
|
|
|
|
- DELETED by user (in manifest, absent from user dir): respected, not re-added.
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
- REMOVED from bundled (in manifest, gone from repo): cleaned from manifest.
|
2026-02-19 18:25:53 -08:00
|
|
|
|
2026-03-06 16:13:47 -08:00
|
|
|
The manifest lives at ~/.hermes/skills/.bundled_manifest.
|
2026-02-19 18:25:53 -08:00
|
|
|
"""
|
|
|
|
|
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
import hashlib
|
2026-02-21 03:32:11 -08:00
|
|
|
import logging
|
2026-02-19 18:25:53 -08:00
|
|
|
import os
|
|
|
|
|
import shutil
|
|
|
|
|
from pathlib import Path
|
refactor: consolidate get_hermes_home() and parse_reasoning_effort() (#3062)
Centralizes two widely-duplicated patterns into hermes_constants.py:
1. get_hermes_home() — Path resolution for ~/.hermes (HERMES_HOME env var)
- Was copy-pasted inline across 30+ files as:
Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))
- Now defined once in hermes_constants.py (zero-dependency module)
- hermes_cli/config.py re-exports it for backward compatibility
- Removed local wrapper functions in honcho_integration/client.py,
tools/website_policy.py, tools/tirith_security.py, hermes_cli/uninstall.py
2. parse_reasoning_effort() — Reasoning effort string validation
- Was copy-pasted in cli.py, gateway/run.py, cron/scheduler.py
- Same validation logic: check against (xhigh, high, medium, low, minimal, none)
- Now defined once in hermes_constants.py, called from all 3 locations
- Warning log for unknown values kept at call sites (context-specific)
31 files changed, net +31 lines (125 insertions, 94 deletions)
Full test suite: 6179 passed, 0 failed
2026-03-25 15:54:28 -07:00
|
|
|
from hermes_constants import get_hermes_home
|
2026-03-06 16:13:47 -08:00
|
|
|
from typing import Dict, List, Tuple
|
2026-02-19 18:25:53 -08:00
|
|
|
|
2026-02-21 03:32:11 -08:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-02-19 18:25:53 -08:00
|
|
|
|
refactor: consolidate get_hermes_home() and parse_reasoning_effort() (#3062)
Centralizes two widely-duplicated patterns into hermes_constants.py:
1. get_hermes_home() — Path resolution for ~/.hermes (HERMES_HOME env var)
- Was copy-pasted inline across 30+ files as:
Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))
- Now defined once in hermes_constants.py (zero-dependency module)
- hermes_cli/config.py re-exports it for backward compatibility
- Removed local wrapper functions in honcho_integration/client.py,
tools/website_policy.py, tools/tirith_security.py, hermes_cli/uninstall.py
2. parse_reasoning_effort() — Reasoning effort string validation
- Was copy-pasted in cli.py, gateway/run.py, cron/scheduler.py
- Same validation logic: check against (xhigh, high, medium, low, minimal, none)
- Now defined once in hermes_constants.py, called from all 3 locations
- Warning log for unknown values kept at call sites (context-specific)
31 files changed, net +31 lines (125 insertions, 94 deletions)
Full test suite: 6179 passed, 0 failed
2026-03-25 15:54:28 -07:00
|
|
|
HERMES_HOME = get_hermes_home()
|
2026-02-19 18:25:53 -08:00
|
|
|
SKILLS_DIR = HERMES_HOME / "skills"
|
|
|
|
|
MANIFEST_FILE = SKILLS_DIR / ".bundled_manifest"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_bundled_dir() -> Path:
|
feat: nix flake — uv2nix build, NixOS module, persistent container mode (#20)
* feat: nix flake, uv2nix build, dev shell and home manager
* fixed nix run, updated docs for setup
* feat(nix): NixOS module with persistent container mode, managed guards, checks
- Replace homeModules.nix with nixosModules.nix (two deployment modes)
- Mode A (native): hardened systemd service with ProtectSystem=strict
- Mode B (container): persistent Ubuntu container with /nix/store bind-mount,
identity-hash-based recreation, GC root protection, symlink-based updates
- Add HERMES_MANAGED guards blocking CLI config mutation (config set, setup,
gateway install/uninstall) when running under NixOS module
- Add nix/checks.nix with build-time verification (binary, CLI, managed guard)
- Remove container.nix (no Nix-built OCI image; pulls ubuntu:24.04 at runtime)
- Simplify packages.nix (drop fetchFromGitHub submodules, PYTHONPATH wrappers)
- Rewrite docs/nixos-setup.md with full options reference, container
architecture, secrets management, and troubleshooting guide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update config.py
* feat(nix): add CI workflow and enhanced build checks
- GitHub Actions workflow for nix flake check + build on linux/macOS
- Entry point sync check to catch pyproject.toml drift
- Expanded managed-guard check to cover config edit
- Wrap hermes-acp binary in Nix package
- Fix Path type mismatch in is_managed()
* Update MCP server package name; bundled skills support
* fix reading .env. instead have container user a common mounted .env file
* feat(nix): container entrypoint with privilege drop and sudo provisioning
Container was running as non-root via --user, which broke apt/pip installs
and caused crashes when $HOME didn't exist. Replace --user with a Nix-built
entrypoint script that provisions the hermes user, sudo (NOPASSWD), and
/home/hermes inside the container on first boot, then drops privileges via
setpriv. Writable layer persists so setup only runs once.
Also expands MCP server options to support HTTP transport and sampling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix group and user creation in container mode
* feat(nix): persistent /home/hermes and MESSAGING_CWD in container mode
Container mode now bind-mounts ${stateDir}/home to /home/hermes so the
agent's home directory survives container recreation. Previously it lived
in the writable layer and was lost on image/volume/options changes.
Also passes MESSAGING_CWD to the container so the agent finds its
workspace and documents, matching native mode behavior.
Other changes:
- Extract containerDataDir/containerHomeDir bindings (no more magic strings)
- Fix entrypoint chown to run unconditionally (volume mounts always exist)
- Add schema field to container identity hash for auto-recreation
- Add idempotency test (Scenario G) to config-roundtrip check
* docs: add Nix & NixOS setup guide to docs site
Add comprehensive Nix documentation to the Docusaurus site at
website/docs/getting-started/nix-setup.md, covering nix run/profile
install, NixOS module (native + container modes), declarative settings,
secrets management, MCP servers, managed mode, container architecture,
dev shell, flake checks, and full options reference.
- Register nix-setup in sidebar after installation page
- Add Nix callout tip to installation.md linking to new guide
- Add canonical version pointer in docs/nixos-setup.md
* docs: remove docs/nixos-setup.md, consolidate into website docs
Backfill missing details (restart/restartSec in full example,
gateway.pid, 0750 permissions, docker inspect commands) into
the canonical website/docs/getting-started/nix-setup.md and
delete the old standalone file.
* fix(nix): add compression.protect_last_n and target_ratio to config-keys.json
New keys were added to DEFAULT_CONFIG on main, causing the
config-drift check to fail in CI.
* fix(nix): skip checks on aarch64-darwin (onnxruntime wheel missing)
The full Python venv includes onnxruntime (via faster-whisper/STT)
which lacks a compatible uv2nix wheel on aarch64-darwin. Gate all
checks behind stdenv.hostPlatform.isLinux. The package and devShell
still evaluate on macOS.
* fix(nix): skip flake check and build on macOS CI
onnxruntime (transitive dep via faster-whisper) lacks a compatible
uv2nix wheel on aarch64-darwin. Run full checks and build on Linux
only; macOS CI verifies the flake evaluates without building.
* fix(nix): preserve container writable layer across nixos-rebuild
The container identity hash included the entrypoint's Nix store path,
which changes on every nixpkgs update (due to runtimeShell/stdenv
input-addressing). This caused false-positive identity mismatches,
triggering container recreation and losing the persistent writable layer.
- Use stable symlink (current-entrypoint) like current-package already does
- Remove entrypoint from identity hash (only image/volumes/options matter)
- Add GC root for entrypoint so nix-collect-garbage doesn't break it
- Remove global HERMES_HOME env var from addToSystemPackages (conflicted
with interactive CLI use, service already sets its own)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 01:08:02 +05:30
|
|
|
"""Locate the bundled skills/ directory.
|
|
|
|
|
|
|
|
|
|
Checks HERMES_BUNDLED_SKILLS env var first (set by Nix wrapper),
|
|
|
|
|
then falls back to the relative path from this source file.
|
|
|
|
|
"""
|
|
|
|
|
env_override = os.getenv("HERMES_BUNDLED_SKILLS")
|
|
|
|
|
if env_override:
|
|
|
|
|
return Path(env_override)
|
2026-02-19 18:25:53 -08:00
|
|
|
return Path(__file__).parent.parent / "skills"
|
|
|
|
|
|
|
|
|
|
|
2026-03-06 16:13:47 -08:00
|
|
|
def _read_manifest() -> Dict[str, str]:
|
|
|
|
|
"""
|
|
|
|
|
Read the manifest as a dict of {skill_name: origin_hash}.
|
|
|
|
|
|
|
|
|
|
Handles both v1 (plain names) and v2 (name:hash) formats.
|
|
|
|
|
v1 entries get an empty hash string which triggers migration on next sync.
|
|
|
|
|
"""
|
2026-02-19 18:25:53 -08:00
|
|
|
if not MANIFEST_FILE.exists():
|
2026-03-06 16:13:47 -08:00
|
|
|
return {}
|
2026-02-19 18:25:53 -08:00
|
|
|
try:
|
2026-03-06 16:13:47 -08:00
|
|
|
result = {}
|
|
|
|
|
for line in MANIFEST_FILE.read_text(encoding="utf-8").splitlines():
|
|
|
|
|
line = line.strip()
|
|
|
|
|
if not line:
|
|
|
|
|
continue
|
|
|
|
|
if ":" in line:
|
|
|
|
|
# v2 format: name:hash
|
|
|
|
|
name, _, hash_val = line.partition(":")
|
|
|
|
|
result[name.strip()] = hash_val.strip()
|
|
|
|
|
else:
|
|
|
|
|
# v1 format: plain name — empty hash triggers migration
|
|
|
|
|
result[line] = ""
|
|
|
|
|
return result
|
2026-02-19 18:25:53 -08:00
|
|
|
except (OSError, IOError):
|
2026-03-06 16:13:47 -08:00
|
|
|
return {}
|
2026-02-19 18:25:53 -08:00
|
|
|
|
|
|
|
|
|
2026-03-06 16:13:47 -08:00
|
|
|
def _write_manifest(entries: Dict[str, str]):
|
2026-03-08 23:53:57 -07:00
|
|
|
"""Write the manifest file atomically in v2 format (name:hash).
|
|
|
|
|
|
|
|
|
|
Uses a temp file + os.replace() to avoid corruption if the process
|
|
|
|
|
crashes or is interrupted mid-write.
|
|
|
|
|
"""
|
|
|
|
|
import tempfile
|
|
|
|
|
|
2026-02-19 18:25:53 -08:00
|
|
|
MANIFEST_FILE.parent.mkdir(parents=True, exist_ok=True)
|
2026-03-08 23:53:57 -07:00
|
|
|
data = "\n".join(f"{name}:{hash_val}" for name, hash_val in sorted(entries.items())) + "\n"
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
fd, tmp_path = tempfile.mkstemp(
|
|
|
|
|
dir=str(MANIFEST_FILE.parent),
|
|
|
|
|
prefix=".bundled_manifest_",
|
|
|
|
|
suffix=".tmp",
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
|
|
|
f.write(data)
|
|
|
|
|
f.flush()
|
|
|
|
|
os.fsync(f.fileno())
|
|
|
|
|
os.replace(tmp_path, MANIFEST_FILE)
|
|
|
|
|
except BaseException:
|
|
|
|
|
try:
|
|
|
|
|
os.unlink(tmp_path)
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|
raise
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("Failed to write skills manifest %s: %s", MANIFEST_FILE, e, exc_info=True)
|
2026-02-19 18:25:53 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _discover_bundled_skills(bundled_dir: Path) -> List[Tuple[str, Path]]:
|
|
|
|
|
"""
|
|
|
|
|
Find all SKILL.md files in the bundled directory.
|
|
|
|
|
Returns list of (skill_name, skill_directory_path) tuples.
|
|
|
|
|
"""
|
|
|
|
|
skills = []
|
|
|
|
|
if not bundled_dir.exists():
|
|
|
|
|
return skills
|
|
|
|
|
|
|
|
|
|
for skill_md in bundled_dir.rglob("SKILL.md"):
|
|
|
|
|
path_str = str(skill_md)
|
|
|
|
|
if "/.git/" in path_str or "/.github/" in path_str or "/.hub/" in path_str:
|
|
|
|
|
continue
|
|
|
|
|
skill_dir = skill_md.parent
|
|
|
|
|
skill_name = skill_dir.name
|
|
|
|
|
skills.append((skill_name, skill_dir))
|
|
|
|
|
|
|
|
|
|
return skills
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _compute_relative_dest(skill_dir: Path, bundled_dir: Path) -> Path:
|
|
|
|
|
"""
|
|
|
|
|
Compute the destination path in SKILLS_DIR preserving the category structure.
|
|
|
|
|
e.g., bundled/skills/mlops/axolotl -> ~/.hermes/skills/mlops/axolotl
|
|
|
|
|
"""
|
|
|
|
|
rel = skill_dir.relative_to(bundled_dir)
|
|
|
|
|
return SKILLS_DIR / rel
|
|
|
|
|
|
|
|
|
|
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
def _dir_hash(directory: Path) -> str:
|
|
|
|
|
"""Compute a hash of all file contents in a directory for change detection."""
|
|
|
|
|
hasher = hashlib.md5()
|
|
|
|
|
try:
|
|
|
|
|
for fpath in sorted(directory.rglob("*")):
|
|
|
|
|
if fpath.is_file():
|
|
|
|
|
rel = fpath.relative_to(directory)
|
|
|
|
|
hasher.update(str(rel).encode("utf-8"))
|
|
|
|
|
hasher.update(fpath.read_bytes())
|
|
|
|
|
except (OSError, IOError):
|
|
|
|
|
pass
|
|
|
|
|
return hasher.hexdigest()
|
|
|
|
|
|
|
|
|
|
|
2026-02-19 18:25:53 -08:00
|
|
|
def sync_skills(quiet: bool = False) -> dict:
|
|
|
|
|
"""
|
|
|
|
|
Sync bundled skills into ~/.hermes/skills/ using the manifest.
|
|
|
|
|
|
|
|
|
|
Returns:
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
dict with keys: copied (list), updated (list), skipped (int),
|
2026-03-06 16:13:47 -08:00
|
|
|
user_modified (list), cleaned (list), total_bundled (int)
|
2026-02-19 18:25:53 -08:00
|
|
|
"""
|
|
|
|
|
bundled_dir = _get_bundled_dir()
|
|
|
|
|
if not bundled_dir.exists():
|
2026-03-06 16:13:47 -08:00
|
|
|
return {
|
|
|
|
|
"copied": [], "updated": [], "skipped": 0,
|
|
|
|
|
"user_modified": [], "cleaned": [], "total_bundled": 0,
|
|
|
|
|
}
|
2026-02-19 18:25:53 -08:00
|
|
|
|
|
|
|
|
SKILLS_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
manifest = _read_manifest()
|
|
|
|
|
bundled_skills = _discover_bundled_skills(bundled_dir)
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
bundled_names = {name for name, _ in bundled_skills}
|
|
|
|
|
|
2026-02-19 18:25:53 -08:00
|
|
|
copied = []
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
updated = []
|
2026-03-06 16:13:47 -08:00
|
|
|
user_modified = []
|
2026-02-19 18:25:53 -08:00
|
|
|
skipped = 0
|
|
|
|
|
|
|
|
|
|
for skill_name, skill_src in bundled_skills:
|
|
|
|
|
dest = _compute_relative_dest(skill_src, bundled_dir)
|
2026-03-06 16:13:47 -08:00
|
|
|
bundled_hash = _dir_hash(skill_src)
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
|
|
|
|
|
if skill_name not in manifest:
|
2026-03-06 16:13:47 -08:00
|
|
|
# ── New skill — never offered before ──
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
try:
|
|
|
|
|
if dest.exists():
|
2026-03-06 16:13:47 -08:00
|
|
|
# User already has a skill with the same name — don't overwrite
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
skipped += 1
|
2026-03-07 03:58:32 +03:00
|
|
|
manifest[skill_name] = bundled_hash
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
else:
|
|
|
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
shutil.copytree(skill_src, dest)
|
|
|
|
|
copied.append(skill_name)
|
2026-03-07 03:58:32 +03:00
|
|
|
manifest[skill_name] = bundled_hash
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
if not quiet:
|
|
|
|
|
print(f" + {skill_name}")
|
|
|
|
|
except (OSError, IOError) as e:
|
2026-02-19 18:25:53 -08:00
|
|
|
if not quiet:
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
print(f" ! Failed to copy {skill_name}: {e}")
|
2026-03-07 03:58:32 +03:00
|
|
|
# Do NOT add to manifest — next sync should retry
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
|
|
|
|
|
elif dest.exists():
|
2026-03-06 16:13:47 -08:00
|
|
|
# ── Existing skill — in manifest AND on disk ──
|
|
|
|
|
origin_hash = manifest.get(skill_name, "")
|
|
|
|
|
user_hash = _dir_hash(dest)
|
|
|
|
|
|
|
|
|
|
if not origin_hash:
|
|
|
|
|
# v1 migration: no origin hash recorded. Set baseline from
|
|
|
|
|
# user's current copy so future syncs can detect modifications.
|
|
|
|
|
manifest[skill_name] = user_hash
|
|
|
|
|
if user_hash == bundled_hash:
|
|
|
|
|
skipped += 1 # already in sync
|
|
|
|
|
else:
|
|
|
|
|
# Can't tell if user modified or bundled changed — be safe
|
|
|
|
|
skipped += 1
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if user_hash != origin_hash:
|
|
|
|
|
# User modified this skill — don't overwrite their changes
|
|
|
|
|
user_modified.append(skill_name)
|
|
|
|
|
if not quiet:
|
|
|
|
|
print(f" ~ {skill_name} (user-modified, skipping)")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# User copy matches origin — check if bundled has a newer version
|
|
|
|
|
if bundled_hash != origin_hash:
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
try:
|
2026-03-07 03:58:32 +03:00
|
|
|
# Move old copy to a backup so we can restore on failure
|
|
|
|
|
backup = dest.with_suffix(".bak")
|
|
|
|
|
shutil.move(str(dest), str(backup))
|
|
|
|
|
try:
|
|
|
|
|
shutil.copytree(skill_src, dest)
|
|
|
|
|
manifest[skill_name] = bundled_hash
|
|
|
|
|
updated.append(skill_name)
|
|
|
|
|
if not quiet:
|
|
|
|
|
print(f" ↑ {skill_name} (updated)")
|
|
|
|
|
# Remove backup after successful copy
|
|
|
|
|
shutil.rmtree(backup, ignore_errors=True)
|
|
|
|
|
except (OSError, IOError):
|
|
|
|
|
# Restore from backup
|
|
|
|
|
if backup.exists() and not dest.exists():
|
|
|
|
|
shutil.move(str(backup), str(dest))
|
|
|
|
|
raise
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
except (OSError, IOError) as e:
|
|
|
|
|
if not quiet:
|
|
|
|
|
print(f" ! Failed to update {skill_name}: {e}")
|
|
|
|
|
else:
|
2026-03-06 16:13:47 -08:00
|
|
|
skipped += 1 # bundled unchanged, user unchanged
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
|
|
|
|
|
else:
|
2026-03-06 16:13:47 -08:00
|
|
|
# ── In manifest but not on disk — user deleted it ──
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
skipped += 1
|
2026-02-19 18:25:53 -08:00
|
|
|
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
# Clean stale manifest entries (skills removed from bundled dir)
|
2026-03-06 16:13:47 -08:00
|
|
|
cleaned = sorted(set(manifest.keys()) - bundled_names)
|
|
|
|
|
for name in cleaned:
|
|
|
|
|
del manifest[name]
|
2026-02-19 18:25:53 -08:00
|
|
|
|
|
|
|
|
# Also copy DESCRIPTION.md files for categories (if not already present)
|
|
|
|
|
for desc_md in bundled_dir.rglob("DESCRIPTION.md"):
|
|
|
|
|
rel = desc_md.relative_to(bundled_dir)
|
|
|
|
|
dest_desc = SKILLS_DIR / rel
|
|
|
|
|
if not dest_desc.exists():
|
|
|
|
|
try:
|
|
|
|
|
dest_desc.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
shutil.copy2(desc_md, dest_desc)
|
2026-02-21 03:32:11 -08:00
|
|
|
except (OSError, IOError) as e:
|
|
|
|
|
logger.debug("Could not copy %s: %s", desc_md, e)
|
2026-02-19 18:25:53 -08:00
|
|
|
|
|
|
|
|
_write_manifest(manifest)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"copied": copied,
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
"updated": updated,
|
2026-02-19 18:25:53 -08:00
|
|
|
"skipped": skipped,
|
2026-03-06 16:13:47 -08:00
|
|
|
"user_modified": user_modified,
|
fix: restore all removed bundled skills + fix skills sync system
- Restored 21 skills removed in commits 757d012 and 740dd92:
accelerate, audiocraft, code-review, faiss, flash-attention, gguf,
grpo-rl-training, guidance, llava, nemo-curator, obliteratus, peft,
pytorch-fsdp, pytorch-lightning, simpo, slime, stable-diffusion,
tensorrt-llm, torchtitan, trl-fine-tuning, whisper
- Rewrote sync_skills() with proper update semantics:
* New skills (not in manifest): copied to user dir
* Existing skills (in manifest + on disk): updated via hash comparison
* User-deleted skills (in manifest, not on disk): respected, not re-added
* Stale manifest entries (removed from bundled): cleaned from manifest
- Added sync_skills() to CLI startup (cmd_chat) and gateway startup
(start_gateway) — previously only ran during 'hermes update'
- Updated cmd_update output to show new/updated/cleaned counts
- Rewrote tests: 20 tests covering manifest CRUD, dir hashing, fresh
install, user deletion respect, update detection, stale cleanup, and
name collision handling
75 bundled skills total. 2002 tests pass.
2026-03-06 15:57:12 -08:00
|
|
|
"cleaned": cleaned,
|
2026-02-19 18:25:53 -08:00
|
|
|
"total_bundled": len(bundled_skills),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
print("Syncing bundled skills into ~/.hermes/skills/ ...")
|
|
|
|
|
result = sync_skills(quiet=False)
|
2026-03-06 16:13:47 -08:00
|
|
|
parts = [
|
|
|
|
|
f"{len(result['copied'])} new",
|
|
|
|
|
f"{len(result['updated'])} updated",
|
|
|
|
|
f"{result['skipped']} unchanged",
|
|
|
|
|
]
|
|
|
|
|
if result["user_modified"]:
|
|
|
|
|
parts.append(f"{len(result['user_modified'])} user-modified (kept)")
|
|
|
|
|
if result["cleaned"]:
|
|
|
|
|
parts.append(f"{len(result['cleaned'])} cleaned from manifest")
|
|
|
|
|
print(f"\nDone: {', '.join(parts)}. {result['total_bundled']} total bundled.")
|