From 2ba07f34a243f51446dc9d5151a4e6fd45b3d015 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Mon, 30 Mar 2026 12:13:40 -0600 Subject: [PATCH] feat: add capabilities detection module for optional AI dependencies Introduces luminos_lib/capabilities.py as the single source of truth for optional package availability. Detects anthropic, tree-sitter, python-magic and their grammar packages. Provides check_ai_dependencies() for gating --ai mode and print_status() for --install-extras. Also hosts clear_cache() to avoid pulling heavy AI imports for cache cleanup. Co-Authored-By: Claude Opus 4.6 (1M context) --- luminos_lib/capabilities.py | 139 ++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 luminos_lib/capabilities.py diff --git a/luminos_lib/capabilities.py b/luminos_lib/capabilities.py new file mode 100644 index 0000000..8a01c12 --- /dev/null +++ b/luminos_lib/capabilities.py @@ -0,0 +1,139 @@ +"""Capability detection and cache management for optional luminos dependencies. + +The base tool requires zero external packages. The --ai flag requires: + - anthropic (API transport) + - tree-sitter (AST parsing via parse_structure tool) + - python-magic (improved file classification) + +This module is the single place that knows about optional dependencies. +""" + +_PACKAGES = { + "anthropic": { + "import": "anthropic", + "pip": "anthropic", + "purpose": "Claude API client (streaming, retries, token counting)", + }, + "tree-sitter": { + "import": "tree_sitter", + "pip": ("tree-sitter tree-sitter-python tree-sitter-javascript " + "tree-sitter-rust tree-sitter-go"), + "purpose": "AST parsing for parse_structure tool", + }, + "python-magic": { + "import": "magic", + "pip": "python-magic", + "purpose": "Improved file type detection via libmagic", + }, +} + + +def _check_package(import_name): + """Return True if a package is importable.""" + try: + __import__(import_name) + return True + except ImportError: + return False + + +ANTHROPIC_AVAILABLE = _check_package("anthropic") +TREE_SITTER_AVAILABLE = _check_package("tree_sitter") +MAGIC_AVAILABLE = _check_package("magic") + + +def check_ai_dependencies(): + """Check that all --ai dependencies are installed. + + If any are missing, prints a clear error with the pip install command + and returns False. Returns True if everything is available. + """ + missing = [] + for name, info in _PACKAGES.items(): + if not _check_package(info["import"]): + missing.append(name) + + if not missing: + return True + + # Also check tree-sitter grammar packages + grammar_missing = [] + if "tree-sitter" not in missing: + for grammar in ["tree_sitter_python", "tree_sitter_javascript", + "tree_sitter_rust", "tree_sitter_go"]: + if not _check_package(grammar): + grammar_missing.append(grammar.replace("_", "-")) + + import sys + print("\nluminos --ai requires missing packages:", file=sys.stderr) + for name in missing: + print(f" \u2717 {name}", file=sys.stderr) + for name in grammar_missing: + print(f" \u2717 {name}", file=sys.stderr) + + # Build pip install command + pip_parts = [] + for name in missing: + pip_parts.append(_PACKAGES[name]["pip"]) + for name in grammar_missing: + pip_parts.append(name) + pip_cmd = " \\\n ".join(pip_parts) + + print(f"\n Install with:\n pip install {pip_cmd}\n", file=sys.stderr) + return False + + +def print_status(): + """Print the install status of all optional packages.""" + print("\nLuminos optional dependencies:\n") + + for name, info in _PACKAGES.items(): + available = _check_package(info["import"]) + mark = "\u2713" if available else "\u2717" + status = "installed" if available else "missing" + print(f" {mark} {name:20s} {status:10s} {info['purpose']}") + + # Grammar packages + grammars = { + "tree-sitter-python": "tree_sitter_python", + "tree-sitter-javascript": "tree_sitter_javascript", + "tree-sitter-rust": "tree_sitter_rust", + "tree-sitter-go": "tree_sitter_go", + } + print() + for name, imp in grammars.items(): + available = _check_package(imp) + mark = "\u2713" if available else "\u2717" + status = "installed" if available else "missing" + print(f" {mark} {name:20s} {status:10s} Language grammar") + + # Full install command (deduplicated) + all_pkgs = [] + seen = set() + for info in _PACKAGES.values(): + for pkg in info["pip"].split(): + if pkg not in seen: + all_pkgs.append(pkg) + seen.add(pkg) + for name in grammars: + if name not in seen: + all_pkgs.append(name) + seen.add(name) + + print(f"\n Install all with:\n pip install {' '.join(all_pkgs)}\n") + + +CACHE_ROOT = "/tmp/luminos" + + +def clear_cache(): + """Remove all investigation caches under /tmp/luminos/.""" + import shutil + import os + import sys + if os.path.isdir(CACHE_ROOT): + shutil.rmtree(CACHE_ROOT) + print(f"Cleared cache: {CACHE_ROOT}", file=sys.stderr) + else: + print(f"No cache to clear ({CACHE_ROOT} does not exist).", + file=sys.stderr)