|
import click |
|
import sys |
|
import asyncio |
|
from typing import List |
|
from .docs_manager import DocsManager |
|
from .async_logger import AsyncLogger |
|
|
|
logger = AsyncLogger(verbose=True) |
|
docs_manager = DocsManager(logger) |
|
|
|
def print_table(headers: List[str], rows: List[List[str]], padding: int = 2): |
|
"""Print formatted table with headers and rows""" |
|
widths = [max(len(str(cell)) for cell in col) for col in zip(headers, *rows)] |
|
border = '+' + '+'.join('-' * (w + 2 * padding) for w in widths) + '+' |
|
|
|
def format_row(row): |
|
return '|' + '|'.join(f"{' ' * padding}{str(cell):<{w}}{' ' * padding}" |
|
for cell, w in zip(row, widths)) + '|' |
|
|
|
click.echo(border) |
|
click.echo(format_row(headers)) |
|
click.echo(border) |
|
for row in rows: |
|
click.echo(format_row(row)) |
|
click.echo(border) |
|
|
|
@click.group() |
|
def cli(): |
|
"""Crawl4AI Command Line Interface""" |
|
pass |
|
|
|
@cli.group() |
|
def docs(): |
|
"""Documentation operations""" |
|
pass |
|
|
|
@docs.command() |
|
@click.argument('sections', nargs=-1) |
|
@click.option('--mode', type=click.Choice(['extended', 'condensed']), default='extended') |
|
def combine(sections: tuple, mode: str): |
|
"""Combine documentation sections""" |
|
try: |
|
asyncio.run(docs_manager.ensure_docs_exist()) |
|
click.echo(docs_manager.generate(sections, mode)) |
|
except Exception as e: |
|
logger.error(str(e), tag="ERROR") |
|
sys.exit(1) |
|
|
|
@docs.command() |
|
@click.argument('query') |
|
@click.option('--top-k', '-k', default=5) |
|
@click.option('--build-index', is_flag=True, help='Build index if missing') |
|
def search(query: str, top_k: int, build_index: bool): |
|
"""Search documentation""" |
|
try: |
|
result = docs_manager.search(query, top_k) |
|
if result == "No search index available. Call build_search_index() first.": |
|
if build_index or click.confirm('No search index found. Build it now?'): |
|
asyncio.run(docs_manager.llm_text.generate_index_files()) |
|
result = docs_manager.search(query, top_k) |
|
click.echo(result) |
|
except Exception as e: |
|
click.echo(f"Error: {str(e)}", err=True) |
|
sys.exit(1) |
|
|
|
@docs.command() |
|
def update(): |
|
"""Update docs from GitHub""" |
|
try: |
|
asyncio.run(docs_manager.fetch_docs()) |
|
click.echo("Documentation updated successfully") |
|
except Exception as e: |
|
click.echo(f"Error: {str(e)}", err=True) |
|
sys.exit(1) |
|
|
|
@docs.command() |
|
@click.option('--force-facts', is_flag=True, help='Force regenerate fact files') |
|
@click.option('--clear-cache', is_flag=True, help='Clear BM25 cache') |
|
def index(force_facts: bool, clear_cache: bool): |
|
"""Build or rebuild search indexes""" |
|
try: |
|
asyncio.run(docs_manager.ensure_docs_exist()) |
|
asyncio.run(docs_manager.llm_text.generate_index_files( |
|
force_generate_facts=force_facts, |
|
clear_bm25_cache=clear_cache |
|
)) |
|
click.echo("Search indexes built successfully") |
|
except Exception as e: |
|
click.echo(f"Error: {str(e)}", err=True) |
|
sys.exit(1) |
|
|
|
|
|
@docs.command() |
|
def list(): |
|
"""List available documentation sections""" |
|
try: |
|
sections = docs_manager.list() |
|
print_table(["Sections"], [[section] for section in sections]) |
|
|
|
except Exception as e: |
|
click.echo(f"Error: {str(e)}", err=True) |
|
sys.exit(1) |
|
|
|
if __name__ == '__main__': |
|
cli() |