Skill: nexaiguy/nex-depcheck (version 1.0.0)

--- name: Nex DepCheck description: Skill dependency checker. Scan Python skills for external dependencies. Verify stdlib-only compliance, check individual files or entire skill directories. No database needed, pure file scanner. version: 1.0.0 metadata: author: Nex AI (Kevin Blancaflor) license: MIT-0 website: https://nex-ai.be clawdbot: keywords: - dependency check - stdlib check - import scanner - external dependency - python imports - clean skill - dependency audit - afhankelijkheid check - import controle triggers: - check dependencies - scan for external imports - is this stdlib only - does this skill have external deps - check imports - scan skills for dependencies - controleer afhankelijkheden --- # Nex DepCheck Skill dependency checker. Scan Python skills to verify they only use stdlib and internal imports. Catch external dependencies before publishing. ## Requirements - Python 3.8+ - No external dependencies (stdlib only) - No database needed ## Setup ```bash bash setup.sh ``` ## Commands | Command | What it does | |---------|-------------| | `check` | Check one skill for external deps | | `scan` | Scan all skills in a directory | | `file` | Check a single Python file | | `stdlib` | Check if a module is in Python stdlib | ## Tone Guide **Check a skill before publishing:** > "Does nex-timetrack have any external dependencies?" ```bash nex-depcheck check /path/to/nex-timetrack ``` **Scan all skills at once:** > "Check all my skills for external deps" ```bash nex-depcheck scan /path/to/skills ``` **Check if a module is stdlib:** > "Is pathlib part of the standard library?" ```bash nex-depcheck stdlib pathlib ``` ## Storage No database. Pure file scanner. Reads Python files, parses imports, classifies them. ## License MIT-0 on ClawHub (free for any use). AGPL-3.0 on GitHub (commercial licenses via [REDACTED_SECRET]).

path: lib/config.py
content: """
Nex DepCheck - Configuration
Copyright 2026 Nex AI (Kevin Blancaflor)
"""
import sys

# Python stdlib modules (3.8+)
# This is a curated list of common stdlib modules.
STDLIB_MODULES = {
    'abc', 'aifc', 'argparse', 'array', 'ast', 'asynchat', 'asyncio',
    'asyncore', 'atexit', 'base64', 'bdb', 'binascii', 'binhex',
    'bisect', 'builtins', 'bz2', 'calendar', 'cgi', 'cgitb', 'chunk',
    'cmath', 'cmd', 'code', 'codecs', 'codeop', 'collections',
    'colorsys', 'compileall', 'concurrent', 'configparser', 'contextlib',
    'contextvars', 'copy', 'copyreg', 'cProfile', 'crypt', 'csv',
    'ctypes', 'curses', 'dataclasses', 'datetime', 'dbm', 'decimal',
    'difflib', 'dis', 'distutils', 'doctest', 'email', 'encodings',
    'enum', 'errno', 'faulthandler', 'fcntl', 'filecmp', 'fileinput',
    'fnmatch', 'formatter', 'fractions', 'ftplib', 'functools', 'gc',
    'getopt', 'getpass', 'gettext', 'glob', 'grp', 'gzip', 'hashlib',
    'heapq', 'hmac', 'html', 'http', 'idlelib', 'imaplib', 'imghdr',
    'imp', 'importlib', 'inspect', 'io', 'ipaddress', 'itertools',
    'json', 'keyword', 'lib2to3', 'linecache', 'locale', 'logging',
    'lzma', 'mailbox', 'mailcap', 'marshal', 'math', 'mimetypes',
    'mmap', 'modulefinder', 'multiprocessing', 'netrc', 'nis', 'nntplib',
    'numbers', 'operator', 'optparse', 'os', 'ossaudiodev', 'parser',
    'pathlib', 'pdb', 'pickle', 'pickletools', 'pipes', 'pkgutil',
    'platform', 'plistlib', 'poplib', 'posix', 'posixpath', 'pprint',
    'profile', 'pstats', 'pty', 'pwd', 'py_compile', 'pyclbr',
    'pydoc', 'queue', 'quopri', 'random', 're', 'readline', 'reprlib',
    'resource', 'rlcompleter', 'runpy', 'sched', 'secrets', 'select',
    'selectors', 'shelve', 'shlex', 'shutil', 'signal', 'site',
    'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', 'spwd',
    'sqlite3', 'ssl', 'stat', 'statistics', 'string', 'stringprep',
    'struct', 'subprocess', 'sunau', 'symtable', 'sys', 'sysconfig',
    'syslog', 'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'termios',
    'test', 'textwrap', 'threading', 'time', 'timeit', 'tkinter',
    'token', 'tokenize', 'trace', 'traceback', 'tracemalloc', 'tty',
    'turtle', 'turtledemo', 'types', 'typing', 'unicodedata', 'unittest',
    'urllib', 'uu', 'uuid', 'venv', 'warnings', 'wave', 'weakref',
    'webbrowser', 'winreg', 'winsound', 'wsgiref', 'xdrlib', 'xml',
    'xmlrpc', 'zipapp', 'zipfile', 'zipimport', 'zlib',
    '_thread', '__future__',
}

# Also consider these as "internal" (project-local imports)
INTERNAL_PREFIXES = ['lib.', 'lib/', '.']

# Display
SEPARATOR = "=" * 60
SUBSEPARATOR = "-" * 60


path: lib/scanner.py
content: """
Nex DepCheck - Python file scanner
Copyright 2026 Nex AI (Kevin Blancaflor)
"""
import os
import re
import sys
from pathlib import Path

from lib.config import STDLIB_MODULES, INTERNAL_PREFIXES


def _is_internal(module_name, skill_dir):
    for prefix in INTERNAL_PREFIXES:
        if module_name.startswith(prefix):
            return True

    top = module_name.split('.')[0]
    if (Path(skill_dir) / top).is_dir():
        return True
    if (Path(skill_dir) / f"{top}.py").is_file():
        return True

    return False


def _is_stdlib(module_name):
    top = module_name.split('.')[0]
    return top in STDLIB_MODULES


def scan_imports(file_path, skill_dir=None):
    if skill_dir is None:
        skill_dir = str(Path(file_path).parent)

    imports = []
    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            content = f.read()
    except (OSError, IOError):
        return imports

    for line in content.splitlines():
        line = line.strip()
        from_match = re.match(r'^from\s+([\w.]+)\s+import', line)
        if from_match:
            imports.append(from_match.group(1))
            continue
        import_match = re.match(r'^import\s+([\w., ]+)', line)
        if import_match:
            for mod in import_match.group(1).split(','):
                mod = mod.strip().split(' as ')[0].strip()
                if mod and re.match(r'^[\w.]+$', mod):
                    imports.append(mod)

    results = []
    seen = set()
    for imp in imports:
        top = imp.split('.')[0]
        if top in seen:
            continue
        seen.add(top)

        if _is_internal(imp, skill_dir):
            category = "internal"
        elif _is_stdlib(imp):
            category = "stdlib"
        else:
            category = "external"

        results.append({
            'module': imp,
            'top_level': top,
            'category': category,
        })

    return results


def scan_skill(skill_dir):
    skill_path = Path(skill_dir)
    if not skill_path.is_dir():
        return None

    py_files = list(skill_path.rglob('*.py'))
    if not py_files:
        return None

    all_imports = {}
    file_results = {}

    for py_file in py_files:
        rel = str(py_file.relative_to(skill_path))
        imports = scan_imports(str(py_file), str(skill_path))
        file_results[rel] = imports

        for imp in imports:
            key = imp['top_level']
            if key not in all_imports:
                all_imports[key] = imp
            # Upgrade to external if any file sees it as external
            if imp['category'] == 'external':
                all_imports[key] = imp

    external = [v for v in all_imports.values() if v['category'] == 'external']
    stdlib = [v for v in all_imports.values() if v['category'] == 'stdlib']
    internal = [v for v in all_imports.values() if v['category'] == 'internal']

    # Check Python version
    has_skill_md = (skill_path / 'SKILL.md').exists()
    has_setup = (skill_path / 'setup.sh').exists()

    return {
        'name': skill_path.name,
        'path': str(skill_path),
        'py_files': [str(f.relative_to(skill_path)) for f in py_files],
        'file_imports': file_results,
        'external': external,
        'stdlib': stdlib,
        'internal': internal,
        'total_imports': len(all_imports),
        'has_skill_md': has_skill_md,
        'has_setup': has_setup,
        'clean': len(external) == 0,
    }


def scan_all_skills(base_dir):
    base = Path(base_dir)
    results = []

    for entry in sorted(base.iterdir()):
        if not entry.is_dir():
            continue
        if entry.name.startswith('.'):
            continue

        result = scan_skill(str(entry))
        if result:
            results.append(result)

    return results


path: lib/__init__.py
content: # Nex DepCheck - lib package


path: LICENSE.txt
content: GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007

Copyright (C) 2026 Nex AI (nex-ai.be)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/agpl-3.0.html>.

ADDITIONAL TERMS:

Commercial License Available
-----------------------------
If you wish to use this software in a proprietary product, offer it as
a hosted service, or integrate it into commercial software without
complying with the AGPL-3.0 obligations (including source disclosure),
a commercial license is available from Nex AI.

Contact: [REDACTED_SECRET]
Website: https://nex-ai.be

DUAL LICENSE NOTICE:
This software is also published on ClawHub under the MIT-0 license as
required by the ClawHub platform. The MIT-0 license applies exclusively
to copies obtained through ClawHub. All copies obtained from this GitHub
repository or any other source are governed by the AGPL-3.0 license above.


path: nex-depcheck.py
content: #!/usr/bin/env python3
"""
Nex DepCheck - Skill dependency checker.
Copyright 2026 Nex AI (Kevin Blancaflor)
"""
import sys
import os
import argparse

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from lib.scanner import scan_skill, scan_all_skills, scan_imports
from lib.config import SEPARATOR, SUBSEPARATOR

FOOTER = "[DepCheck by Nex AI | nex-ai.be]"


def cmd_check(args):
    result = scan_skill(args.path)
    if not result:
        print(f"No Python files found in: {args.path}")
        print(FOOTER)
        return

    print(f"\n{SEPARATOR}")
    print(f"DEPENDENCY CHECK: {result['name']}")
    print(f"{SEPARATOR}\n")

    print(f"Python files: {len(result['py_files'])}")
    print(f"Total unique imports: {result['total_imports']}")
    print(f"SKILL.md: {'yes' if result['has_skill_md'] else 'MISSING'}")
    print(f"setup.sh: {'yes' if result['has_setup'] else 'MISSING'}")

    if result['external']:
        print(f"\n  EXTERNAL DEPENDENCIES ({len(result['external'])}):")
        for imp in sorted(result['external'], key=lambda x: x['module']):
            print(f"    - {imp['module']}")
        print(f"\n  STATUS: NOT CLEAN (external deps found)")
    else:
        print(f"\n  STATUS: CLEAN (stdlib + internal only)")

    if args.verbose:
        print(f"\n  Stdlib ({len(result['stdlib'])}):")
        for imp in sorted(result['stdlib'], key=lambda x: x['module']):
            print(f"    {imp['module']}")

        print(f"\n  Internal ({len(result['internal'])}):")
        for imp in sorted(result['internal'], key=lambda x: x['module']):
            print(f"    {imp['module']}")

        print(f"\n  Files:")
        for f, imports in result['file_imports'].items():
            ext = [i for i in imports if i['category'] == 'external']
            marker = " [!]" if ext else ""
            print(f"    {f}{marker}")

    print(f"\n{FOOTER}")


def cmd_scan(args):
    results = scan_all_skills(args.path)
    if not results:
        print(f"No skill directories found in: {args.path}")
        print(FOOTER)
        return

    clean = [r for r in results if r['clean']]
    dirty = [r for r in results if not r['clean']]

    print(f"\n{SEPARATOR}")
    print(f"SCAN: {len(results)} skills in {args.path}")
    print(f"{SEPARATOR}\n")

    print(f"{'Skill':<30} {'Files':<7} {'Imports':<9} {'External':<10} {'Status':<8}")
    print("-" * 64)

    for r in sorted(results, key=lambda x: (-len(x['external']), x['name'])):
        ext_count = len(r['external'])
        status = "CLEAN" if r['clean'] else f"{ext_count} ext"
        print(f"{r['name'][:29]:<30} {len(r['py_files']):<7} {r['total_imports']:<9} {ext_count:<10} {status:<8}")

    print(f"\nClean: {len(clean)} | Issues: {len(dirty)} | Total: {len(results)}")

    if dirty:
        print(f"\nSkills with external dependencies:")
        for r in dirty:
            ext_names = ', '.join(i['module'] for i in r['external'])
            print(f"  {r['name']}: {ext_names}")

    print(f"\n{FOOTER}")


def cmd_file(args):
    imports = scan_imports(args.path)
    if not imports:
        print(f"No imports found in: {args.path}")
        print(FOOTER)
        return

    print(f"\nImports in {args.path}:\n")
    for imp in sorted(imports, key=lambda x: (x['category'], x['module'])):
        tag = f"[{imp['category']}]"
        print(f"  {tag:<12} {imp['module']}")

    external = [i for i in imports if i['category'] == 'external']
    if external:
        print(f"\n  External deps: {', '.join(i['module'] for i in external)}")
    else:
        print(f"\n  All imports are stdlib or internal.")

    print(FOOTER)


def cmd_stdlib(args):
    from lib.config import STDLIB_MODULES
    query = args.module.lower()
    if query in STDLIB_MODULES:
        print(f"  '{query}' is a Python stdlib module.")
    else:
        close = [m for m in sorted(STDLIB_MODULES) if query in m]
        if close:
            print(f"  '{query}' is NOT in stdlib. Similar: {', '.join(close[:10])}")
        else:
            print(f"  '{query}' is NOT in stdlib.")
    print(FOOTER)


# --- Main ---

def main():
    parser = argparse.ArgumentParser(
        description="Nex DepCheck - Skill dependency checker.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    subparsers = parser.add_subparsers(dest='command', help='Commands')

    # CHECK
    p = subparsers.add_parser('check', help='Check a single skill for external deps')
    p.add_argument('path', help='Path to skill directory')
    p.add_argument('--verbose', '-v', action='store_true', help='Show all imports')
    p.set_defaults(func=cmd_check)

    # SCAN
    p = subparsers.add_parser('scan', help='Scan all skills in a directory')
    p.add_argument('path', help='Path to parent directory containing skills')
    p.set_defaults(func=cmd_scan)

    # FILE
    p = subparsers.add_parser('file', help='Check a single Python file')
    p.add_argument('path', help='Path to Python file')
    p.set_defaults(func=cmd_file)

    # STDLIB
    p = subparsers.add_parser('stdlib', help='Check if a module is in Python stdlib')
    p.add_argument('module', help='Module name to check')
    p.set_defaults(func=cmd_stdlib)

    args = parser.parse_args()

    if not hasattr(args, 'func'):
        parser.print_help()
        return

    try:
        args.func(args)
    except KeyboardInterrupt:
        print("\nInterrupted.")
        sys.exit(130)
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == '__main__':
    main()


path: README.md
content: # Nex DepCheck

Skill dependency checker. Scan Python skills to verify they only use stdlib and internal imports. Catch external dependencies before publishing.

**Built by [Nex AI](https://nex-ai.be)**

## Features

- Check individual skills or scan entire directories
- Classifies imports as stdlib, internal, or external
- Verbose mode shows per-file breakdown
- Check if any module is in Python stdlib
- No database needed, pure file scanner
- Python stdlib only, zero dependencies

## Setup

```bash
bash setup.sh
```

## Usage

```bash
# Check a single skill
nex-depcheck check /path/to/nex-timetrack

# Scan all skills
nex-depcheck scan /path/to/skills

# Check a single file
nex-depcheck file /path/to/script.py

# Is this stdlib?
nex-depcheck stdlib pathlib
```

## License

- **ClawHub:** MIT-0 (free for any use)
- **GitHub:** AGPL-3.0 (commercial licenses available via [REDACTED_SECRET])


path: setup.sh
content: #!/usr/bin/env bash
set -euo pipefail

SKILL_NAME="Nex DepCheck"
SKILL_SLUG="nex-depcheck"
BRAND="Nex AI (nex-ai.be)"
BIN_DIR="$HOME/.local/bin"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

echo "=========================================="
echo "  $SKILL_NAME - Setup"
echo "  By $BRAND"
echo "=========================================="
echo ""

PYTHON=""
for cmd in python3 python; do
    if command -v "$cmd" &>/dev/null; then
        version=$("$cmd" --version 2>&1 | grep -oP '\d+\.\d+\.\d+')
        major=$(echo "$version" | cut -d. -f1)
        minor=$(echo "$version" | cut -d. -f2)
        if [ "$major" -ge 3 ] && [ "$minor" -ge 8 ]; then
            PYTHON="$cmd"
            echo "[OK] Found Python $version"
            break
        fi
    fi
done

if [ -z "$PYTHON" ]; then
    echo "[ERROR] Python 3.8+ required but not found."
    exit 1
fi

echo ""
echo "Verifying CLI..."
$PYTHON "$SCRIPT_DIR/$SKILL_SLUG.py" --help > /dev/null 2>&1
echo "[OK] CLI verified"

mkdir -p "$BIN_DIR"
WRAPPER="$BIN_DIR/$SKILL_SLUG"

cat > "$WRAPPER" << WRAPPER_EOF
#!/usr/bin/env bash
exec $PYTHON "$SCRIPT_DIR/$SKILL_SLUG.py" "\$@"
WRAPPER_EOF

chmod +x "$WRAPPER"
echo "[OK] Created CLI wrapper at $WRAPPER"

if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
    echo "[NOTE] Add $BIN_DIR to your PATH:"
    echo "  export PATH=\"\$HOME/.local/bin:\$PATH\""
fi

echo ""
echo "=========================================="
echo "  Setup complete!"
echo "=========================================="
echo ""
echo "Quick start:"
echo "  $SKILL_SLUG check /path/to/skill"
echo "  $SKILL_SLUG scan /path/to/skills"
echo "  $SKILL_SLUG stdlib pathlib"
echo ""
echo "No database. No telemetry. Pure scanner."
echo ""
echo "[$SKILL_NAME by $BRAND]"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)