Download sidebyside/scripts/extract_claude.py from smlflg/HarnessEcosystem: direct link, hf CLI and curl.
- Browser
- Download file 20.4 kB
-
https://huggingface.co/smlflg/HarnessEcosystem/resolve/main/sidebyside/scripts/extract_claude.py
- Command line
-
hf download hf://smlflg/HarnessEcosystem/sidebyside/scripts/extract_claude.py
-
curl -L -o extract_claude.py https://huggingface.co/smlflg/HarnessEcosystem/resolve/main/sidebyside/scripts/extract_claude.py
20.4 kB
| from __future__ import annotations | |
| from collections import Counter | |
| from pathlib import Path | |
| import json | |
| import re | |
| import shlex | |
| __all__ = ["extract_claude"] | |
| MAX_DOC_CHARS = 12000 | |
| MAX_SUMMARY_CHARS = 400 | |
| MAX_SOURCE_SNIPPET = 240 | |
| _FRONTMATTER_RE = re.compile(r"^---\s*$", re.M) | |
| _HEADING_RE = re.compile(r"^#{1,6}\s+(.*\S)\s*$") | |
| _BULLET_COMMAND_RE = re.compile(r"`/([A-Za-z0-9_.-]+)`") | |
| _TOKEN_RE = re.compile(r"[A-Za-z0-9_.:/@+-]+") | |
| def extract_claude(home: Path) -> dict: | |
| root = _resolve_claude_root(home) | |
| backup_root = Path("/home/smlflg/ClaudeSystemBackup") | |
| backup_present = backup_root.exists() | |
| settings = _load_json(root / "settings.json") | |
| permissions = settings.get("permissions", {}) if isinstance(settings, dict) else {} | |
| hooks = settings.get("hooks", {}) if isinstance(settings, dict) else {} | |
| command_items = _collect_markdown_surface(root / "commands", "command") | |
| agent_items = _collect_markdown_surface(root / "agents", "agent") | |
| skill_items = _collect_markdown_surface(root / "skills", "skill") | |
| command_count = len(command_items) | |
| agent_count = len(agent_items) | |
| skill_count = len(skill_items) | |
| session_count = _count_files(root / "sessions", include_hidden=False) | |
| session_log_count = _count_files(root / "session-logs", include_hidden=False) | |
| telemetry_count = _count_files(root / "telemetry", include_hidden=False) | |
| project_transcript_count = _count_files(root / "projects", include_hidden=False) | |
| project_tool_usage = _sample_project_tool_usage(root / "projects") | |
| root_group_counts = _command_group_counts(command_items) | |
| settings_allow = _as_list(permissions.get("allow")) | |
| settings_deny = _as_list(permissions.get("deny")) | |
| mcp_allow = _extract_mcp_allow_entries(settings_allow) | |
| hook_groups = _summarize_hooks(hooks) | |
| instruction_layers = _build_instruction_layers(root) | |
| changelog = _load_text(root / "cache" / "changelog.md") | |
| changelog_candidates = _extract_changelog_command_candidates( | |
| changelog, | |
| {item["name"] for item in command_items}, | |
| ) | |
| backup_metadata = _backup_metadata(backup_root) if backup_present else {} | |
| discoveries = [] | |
| if _plugins_dormant(root / "plugins"): | |
| discoveries.append( | |
| { | |
| "kind": "dormant_surface", | |
| "title": "Plugins directory looks dormant", | |
| "reason": "Only blocklist.json was found under ~/.claude/plugins; no installed plugin manifests or plugin trees were present.", | |
| } | |
| ) | |
| if changelog_candidates: | |
| discoveries.append( | |
| { | |
| "kind": "feature_gap", | |
| "title": "Changelog-advertised commands/features have no local file evidence", | |
| "items": changelog_candidates, | |
| "reason": "Recent changelog entries mention slash commands or features that do not appear in the local command docs, settings, or hooks.", | |
| } | |
| ) | |
| if session_count == 0: | |
| discoveries.append( | |
| { | |
| "kind": "sparse_history", | |
| "title": "Sessions directory is empty", | |
| "reason": "No files were found under ~/.claude/sessions.", | |
| } | |
| ) | |
| if command_count >= 40 or agent_count >= 15: | |
| discoveries.append( | |
| { | |
| "kind": "lightly_used_surface", | |
| "title": "Large command and agent surfaces with a thin history footprint", | |
| "reason": ( | |
| f"{command_count} command docs, {agent_count} agent docs, and only " | |
| f"{session_count} session file(s) / {session_log_count} session-log file(s) suggest a broad but lightly evidenced surface." | |
| ), | |
| } | |
| ) | |
| if session_count and session_count <= 1: | |
| discoveries.append( | |
| { | |
| "kind": "sparse_history", | |
| "title": "Sessions directory is effectively sparse", | |
| "reason": "Exactly one session JSON file was present under ~/.claude/sessions, so the empty-dir candidate does not apply but the footprint is still minimal.", | |
| } | |
| ) | |
| warnings = [ | |
| "Skipped ~/.claude/.env, ~/.claude/.credentials.json, ~/.claude/settings.local.json, and other secret-bearing files.", | |
| "Backup data at /home/smlflg/ClaudeSystemBackup was read as filenames and counts only; no file contents were read there.", | |
| ] | |
| if not mcp_allow: | |
| warnings.append("No explicit MCP tool allow entries were found in settings.json permissions.allow.") | |
| return { | |
| "id": "claude-code", | |
| "name": "Claude Code", | |
| "status": { | |
| "home": str(root), | |
| "backup_home": str(backup_root) if backup_present else None, | |
| "settings_present": (root / "settings.json").exists(), | |
| "backup_present": backup_present, | |
| "model": _settings_model(settings), | |
| "default_mode": _permission_default_mode(permissions), | |
| "counts": { | |
| "commands": command_count, | |
| "agents": agent_count, | |
| "skills": skill_count, | |
| "sessions": session_count, | |
| "session_logs": session_log_count, | |
| "project_transcripts": project_transcript_count, | |
| "telemetry": telemetry_count, | |
| }, | |
| "backup_metadata": backup_metadata, | |
| }, | |
| "config": { | |
| "model": _settings_model(settings), | |
| "permissions": { | |
| "default_mode": _permission_default_mode(permissions), | |
| "allow_count": len(settings_allow), | |
| "deny_count": len(settings_deny), | |
| "additional_directories": _as_list(permissions.get("additionalDirectories")), | |
| }, | |
| "hooks": { | |
| "groups": [group["name"] for group in hook_groups], | |
| "group_count": len(hook_groups), | |
| }, | |
| "mcp_tools": mcp_allow, | |
| }, | |
| "instruction_layers": instruction_layers, | |
| "extensions": { | |
| "skills": skill_items, | |
| "commands": command_items, | |
| "agents": agent_items, | |
| "hooks": hook_groups, | |
| "mcp_tools": mcp_allow, | |
| }, | |
| "usage": { | |
| "counts": { | |
| "commands": command_count, | |
| "agents": agent_count, | |
| "skills": skill_count, | |
| "session_files": session_count, | |
| "session_log_files": session_log_count, | |
| "project_transcripts": project_transcript_count, | |
| "telemetry_files": telemetry_count, | |
| "tools_observed": sum(item["count"] for item in project_tool_usage), | |
| }, | |
| "command_groups": root_group_counts, | |
| "history": { | |
| "session_file_count": session_count, | |
| "session_log_file_count": session_log_count, | |
| "telemetry_file_count": telemetry_count, | |
| "project_transcript_count": project_transcript_count, | |
| }, | |
| "tools": { | |
| "observed_count": sum(item["count"] for item in project_tool_usage), | |
| "top": project_tool_usage, | |
| }, | |
| "surface_pressure": { | |
| "commands_vs_history": _surface_pressure_label(command_count, session_log_count), | |
| "agents_vs_history": _surface_pressure_label(agent_count, session_log_count), | |
| }, | |
| }, | |
| "discoveries": discoveries, | |
| "sources": _sources(root, backup_present), | |
| "warnings": warnings, | |
| } | |
| def _resolve_claude_root(home: Path) -> Path: | |
| home = Path(home).expanduser() | |
| candidates = [home] | |
| if home.name != ".claude": | |
| candidates.insert(0, home / ".claude") | |
| for candidate in candidates: | |
| if (candidate / "settings.json").exists() or (candidate / "CLAUDE.md").exists(): | |
| return candidate | |
| return candidates[0] | |
| def _load_json(path: Path): | |
| try: | |
| return json.loads(path.read_text(encoding="utf-8")) | |
| except Exception: | |
| return {} | |
| def _load_text(path: Path) -> str: | |
| if not path.exists() or not path.is_file(): | |
| return "" | |
| try: | |
| with path.open("r", encoding="utf-8", errors="replace") as handle: | |
| chunks = [] | |
| total = 0 | |
| for line in handle: | |
| chunks.append(line) | |
| total += len(line) | |
| if total >= MAX_DOC_CHARS: | |
| break | |
| return "".join(chunks) | |
| except OSError: | |
| return "" | |
| def _count_files(path: Path, include_hidden: bool = False) -> int: | |
| if not path.exists(): | |
| return 0 | |
| count = 0 | |
| for item in path.rglob("*"): | |
| if not item.is_file(): | |
| continue | |
| if ".git" in item.parts: | |
| continue | |
| relative = item.relative_to(path) | |
| if not include_hidden and any(part.startswith(".") for part in relative.parts): | |
| # The tree contains operational dotfiles we do not want to over-report. | |
| continue | |
| count += 1 | |
| return count | |
| def _sample_project_tool_usage(path: Path, max_files: int = 140) -> list[dict]: | |
| if not path.exists(): | |
| return [] | |
| counter = Counter() | |
| files = sorted( | |
| (item for item in path.rglob("*.jsonl") if item.is_file() and ".git" not in item.parts), | |
| key=lambda item: item.stat().st_mtime if item.exists() else 0, | |
| reverse=True, | |
| )[:max_files] | |
| for file_path in files: | |
| try: | |
| with file_path.open("r", encoding="utf-8", errors="replace") as handle: | |
| for line in handle: | |
| parsed = _safe_json_line(line) | |
| if parsed: | |
| counter.update(_tool_names_from_json(parsed)) | |
| except OSError: | |
| continue | |
| return [{"name": name, "count": count} for name, count in counter.most_common(16)] | |
| def _safe_json_line(line: str): | |
| try: | |
| parsed = json.loads(line) | |
| except json.JSONDecodeError: | |
| return None | |
| return parsed if isinstance(parsed, dict) else None | |
| def _tool_names_from_json(value) -> list[str]: | |
| names = [] | |
| if isinstance(value, dict): | |
| tool_type = value.get("type") | |
| name = value.get("name") or value.get("tool_name") or value.get("tool") | |
| if isinstance(name, str) and (tool_type in {"tool_use", "tool", "function"} or "tool" in str(tool_type).lower()): | |
| names.append(name) | |
| for nested in value.values(): | |
| names.extend(_tool_names_from_json(nested)) | |
| elif isinstance(value, list): | |
| for item in value: | |
| names.extend(_tool_names_from_json(item)) | |
| return names | |
| def _as_list(value): | |
| if isinstance(value, list): | |
| return value | |
| if value is None: | |
| return [] | |
| return [value] | |
| def _settings_model(settings: dict) -> str | None: | |
| model = settings.get("model") | |
| return model if isinstance(model, str) else None | |
| def _permission_default_mode(permissions: dict) -> str | None: | |
| mode = permissions.get("defaultMode") | |
| return mode if isinstance(mode, str) else None | |
| def _surface_pressure_label(surface_count: int, history_count: int) -> str: | |
| if history_count == 0: | |
| return "high" | |
| ratio = surface_count / history_count | |
| if ratio >= 10: | |
| return "high" | |
| if ratio >= 3: | |
| return "medium" | |
| return "low" | |
| def _collect_markdown_surface(base: Path, kind: str) -> list[dict]: | |
| if not base.exists(): | |
| return [] | |
| items = [] | |
| for path in sorted(p for p in base.rglob("*.md") if ".git" not in p.parts and p.is_file()): | |
| rel = path.relative_to(base.parent) | |
| if kind == "command" and rel.parts[0] != "commands": | |
| continue | |
| if kind == "agent" and rel.parts[0] != "agents": | |
| continue | |
| if kind == "skill" and rel.parts[0] != "skills": | |
| continue | |
| title, summary = _summarize_markdown(path) | |
| group = _surface_group(rel) | |
| items.append( | |
| { | |
| "name": _surface_name(path, title), | |
| "path": str(rel), | |
| "group": group, | |
| "summary": summary, | |
| } | |
| ) | |
| return items | |
| def _surface_group(rel_path: Path) -> str: | |
| parts = rel_path.parts | |
| if len(parts) <= 2: | |
| return "root" | |
| if parts[1] in {"gsd", "_archived"}: | |
| return parts[1] | |
| if parts[0] == "skills" and len(parts) > 2: | |
| return parts[1] | |
| return "root" | |
| def _surface_name(path: Path, title: str) -> str: | |
| stem = path.stem | |
| if stem == "SKILL" and path.parent.name: | |
| return path.parent.name | |
| if title and title != path.name: | |
| return title | |
| return stem | |
| def _summarize_markdown(path: Path) -> tuple[str, str]: | |
| text = _load_text(path) | |
| if not text: | |
| return path.stem, "" | |
| frontmatter, body = _split_frontmatter(text) | |
| metadata = _parse_frontmatter(frontmatter) | |
| title = _first_non_empty( | |
| metadata.get("name"), | |
| metadata.get("title"), | |
| _first_heading(text), | |
| path.stem, | |
| ) | |
| lead = metadata.get("description", "") | |
| if not lead: | |
| lead = _extract_prose_summary(body) | |
| summary = _compact_summary(" ".join(part for part in [title, lead] if part)) | |
| return title, summary | |
| def _split_frontmatter(text: str) -> tuple[str, str]: | |
| if not text.startswith("---"): | |
| return "", text | |
| lines = text.splitlines() | |
| if not lines or lines[0].strip() != "---": | |
| return "", text | |
| for index in range(1, len(lines)): | |
| if lines[index].strip() == "---": | |
| frontmatter = "\n".join(lines[1:index]) | |
| body = "\n".join(lines[index + 1 :]) | |
| return frontmatter, body | |
| return "", text | |
| def _parse_frontmatter(text: str) -> dict[str, str]: | |
| if not text: | |
| return {} | |
| data = {} | |
| for raw in text.splitlines(): | |
| if ":" not in raw: | |
| continue | |
| key, value = raw.split(":", 1) | |
| key = key.strip() | |
| value = value.strip().strip("'\"") | |
| if key: | |
| data[key] = value | |
| return data | |
| def _first_heading(text: str) -> str: | |
| for line in text.splitlines(): | |
| match = _HEADING_RE.match(line) | |
| if match: | |
| return match.group(1).strip() | |
| return "" | |
| def _extract_prose_summary(text: str) -> str: | |
| lines = [] | |
| in_fence = False | |
| for line in text.splitlines(): | |
| stripped = line.strip() | |
| if stripped.startswith("```"): | |
| in_fence = not in_fence | |
| continue | |
| if in_fence: | |
| continue | |
| if not stripped: | |
| if lines: | |
| break | |
| continue | |
| if stripped.startswith("#"): | |
| continue | |
| lines.append(stripped) | |
| if len(" ".join(lines)) >= MAX_SUMMARY_CHARS: | |
| break | |
| return _compact_summary(" ".join(lines)) | |
| def _compact_summary(text: str) -> str: | |
| text = re.sub(r"\s+", " ", text).strip() | |
| if len(text) > MAX_SUMMARY_CHARS: | |
| return text[: MAX_SUMMARY_CHARS - 1].rstrip() + "…" | |
| return text | |
| def _first_non_empty(*values) -> str: | |
| for value in values: | |
| if isinstance(value, str) and value.strip(): | |
| return value.strip() | |
| return "" | |
| def _command_group_counts(items: list[dict]) -> dict[str, int]: | |
| counter = Counter() | |
| for item in items: | |
| group = item.get("group") or "root" | |
| counter[group] += 1 | |
| return dict(sorted(counter.items())) | |
| def _summarize_hooks(hooks: dict) -> list[dict]: | |
| if not isinstance(hooks, dict): | |
| return [] | |
| items = [] | |
| for name in sorted(hooks): | |
| rules = hooks.get(name) | |
| if not isinstance(rules, list): | |
| continue | |
| command_names = [] | |
| matchers = [] | |
| for rule in rules: | |
| if not isinstance(rule, dict): | |
| continue | |
| matcher = rule.get("matcher") | |
| if isinstance(matcher, str) and matcher: | |
| matchers.append(matcher) | |
| for hook in _as_list(rule.get("hooks")): | |
| if not isinstance(hook, dict): | |
| continue | |
| command = hook.get("command") | |
| if isinstance(command, str) and command: | |
| command_names.append(_hook_command_name(command)) | |
| items.append( | |
| { | |
| "name": name, | |
| "rule_count": len(rules), | |
| "matchers": sorted({m for m in matchers if m}), | |
| "commands": sorted({c for c in command_names if c}), | |
| } | |
| ) | |
| return items | |
| def _hook_command_name(command: str) -> str: | |
| try: | |
| parts = shlex.split(command) | |
| except ValueError: | |
| parts = command.split() | |
| if not parts: | |
| return command.strip() | |
| candidate = parts[-1] if len(parts) > 1 else parts[0] | |
| return Path(candidate.strip("\"'")).name | |
| def _extract_mcp_allow_entries(allow_entries) -> list[str]: | |
| if not isinstance(allow_entries, list): | |
| return [] | |
| items = [] | |
| for entry in allow_entries: | |
| if isinstance(entry, str) and ("mcp__" in entry or entry.startswith("mcp:")): | |
| items.append(entry) | |
| return sorted(set(items)) | |
| def _build_instruction_layers(root: Path) -> list[dict]: | |
| ordered = [ | |
| root / "CLAUDE.md", | |
| root / "WelcheFehlerVermeiden.md", | |
| root / "WieArbeitestDuMitSamuel.md", | |
| root / "Skilluebersicht.md", | |
| root / "README.md", | |
| ] | |
| layers = [] | |
| for path in ordered: | |
| if not path.exists(): | |
| continue | |
| title, summary = _summarize_markdown(path) | |
| layers.append( | |
| { | |
| "name": title, | |
| "path": str(path), | |
| "summary": summary, | |
| } | |
| ) | |
| return layers | |
| def _extract_changelog_command_candidates(changelog: str, known_commands: set[str]) -> list[str]: | |
| if not changelog: | |
| return [] | |
| seen = [] | |
| for match in _BULLET_COMMAND_RE.finditer(changelog): | |
| name = match.group(1) | |
| if name not in known_commands and name not in seen: | |
| seen.append(name) | |
| return seen[:8] | |
| def _backup_metadata(root: Path) -> dict: | |
| if not root.exists(): | |
| return {} | |
| entries = [] | |
| file_counts = Counter() | |
| top_level = [] | |
| for child in sorted(root.iterdir(), key=lambda p: p.name): | |
| top_level.append(child.name) | |
| if child.is_file(): | |
| file_counts["."] += 1 | |
| entries.append({"name": child.name, "kind": "file"}) | |
| elif child.is_dir(): | |
| count = _count_files(child, include_hidden=True) | |
| file_counts[child.name] += count | |
| entries.append({"name": child.name, "kind": "dir", "file_count": count}) | |
| return { | |
| "top_level": top_level[:40], | |
| "entries": entries[:40], | |
| "file_counts": dict(sorted(file_counts.items())), | |
| } | |
| def _plugins_dormant(path: Path) -> bool: | |
| if not path.exists(): | |
| return False | |
| files = [p for p in path.iterdir() if p.is_file() and ".git" not in p.parts] | |
| subdirs = [p for p in path.iterdir() if p.is_dir() and not p.name.startswith(".")] | |
| return len(files) == 1 and files[0].name == "blocklist.json" and not subdirs | |
| def _sources(root: Path, backup_present: bool) -> list[dict]: | |
| entries = [ | |
| {"kind": "config", "path": str(root / "settings.json")}, | |
| {"kind": "instruction", "path": str(root / "CLAUDE.md")}, | |
| {"kind": "instruction", "path": str(root / "WelcheFehlerVermeiden.md")}, | |
| {"kind": "instruction", "path": str(root / "WieArbeitestDuMitSamuel.md")}, | |
| {"kind": "instruction", "path": str(root / "Skilluebersicht.md")}, | |
| {"kind": "instruction", "path": str(root / "README.md")}, | |
| {"kind": "surface", "path": str(root / "commands")}, | |
| {"kind": "surface", "path": str(root / "agents")}, | |
| {"kind": "surface", "path": str(root / "skills")}, | |
| {"kind": "surface", "path": str(root / "sessions")}, | |
| {"kind": "surface", "path": str(root / "session-logs")}, | |
| {"kind": "surface", "path": str(root / "telemetry")}, | |
| {"kind": "surface", "path": str(root / "cache" / "changelog.md")}, | |
| ] | |
| if backup_present: | |
| entries.append({"kind": "metadata_only", "path": "/home/smlflg/ClaudeSystemBackup"}) | |
| return entries | |