#!/usr/bin/env python3
"""
Pocket - Unified CLI entry point.
This module provides a unified command-line interface for all Pocket
functionalities, organized into logical subcommands.
"""
import asyncio
import sys
from super_pocket.settings import click, CONTEXT_SETTINGS, add_help_command, add_help_argument
from rich.console import Console
from pathlib import Path
from super_pocket import __version__
from super_pocket.web.job_search import main as job_search
from super_pocket.markdown.renderer import markd
from super_pocket.project.to_file import create_codebase_markdown
from super_pocket.iconify.cli import iconify_cli
# from super_pocket.project.readme import run_readme_wizard # Module moved
from super_pocket.documents.cli import (
list_items, view_item, copy_item, init_agents
)
from super_pocket.pdf.converter import conv2pdf
from super_pocket.web.favicon import convert_to_favicon as favicon_convert
from super_pocket.project.req_to_date import run_req_to_date
from super_pocket.readme.cli import readme_cli
from super_pocket.web.favicon import web_favicon
from super_pocket.project.req_to_date import run_req_to_date, print_req_to_date_results
from super_pocket.project.init.cli import init_group
from super_pocket.interactive import pocket_cmd
from super_pocket.xml.cli import xml as xml_cmd
from super_pocket.utils import console
@click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
@click.version_option(version=__version__, prog_name="pocket")
@click.pass_context
def cli(ctx):
"""
A collection of tools.
Available commands:
- markdown: Render markdown files in terminal
- project: Project management tools (export to file, etc.)
- documents: Manage agent templates and cheatsheets
- pdf: PDF conversion tools
- web: Web utilities (favicon conversion, etc.)
- readme: Generate README.md files or analyze your project
- req-to-date: Check for outdated dependencies
- xml: Convert custom tag syntax into formatted XML
Examples:
pocket markdown render README.md
pocket project to-file . -o project-to-file.md
pocket documents list
pocket readme generate README.md
pocket project req-to-date pyproject.toml
"""
if not ctx.invoked_subcommand and not ctx.args:
if not sys.stdin or not sys.stdin.isatty():
console.print(ctx.get_help())
return
pocket_cmd()
return
# ==================== Markdown Commands ====================
@cli.group(name="markdown", context_settings=CONTEXT_SETTINGS)
def markdown_group():
"""Markdown rendering and conversion tools."""
pass
@markdown_group.command(name="render", context_settings=CONTEXT_SETTINGS)
@click.argument('file', type=click.Path())
@click.option('--width', '-w', type=int, help='Output width in characters.')
def markdown_render(file: str, width: int):
"""
Render a Markdown file beautifully in the terminal.
This command reads a Markdown file and displays it with enhanced formatting,
syntax highlighting, and beautiful terminal rendering using the Rich library.
Args:
file: Path to the Markdown file to render.
width: Optional output width in characters for wrapping.
Examples:
pocket markdown render README.md
pocket markdown render docs/guide.md -w 100
"""
file_path = Path(file)
if not file_path.exists():
raise click.BadParameter(f"Path '{file}' does not exist.", param_hint="'FILE'")
# Call the markd function with the file
ctx = click.Context(markd)
ctx.invoke(markd, file=file_path, output=None, input=None)
add_help_argument(markdown_render)
# ==================== Project Commands ====================
@cli.group(name="project", context_settings=CONTEXT_SETTINGS)
def project_group():
"""Project management and export tools."""
pass
@project_group.command(name="to-file", context_settings=CONTEXT_SETTINGS)
@click.option(
'-p', '--path',
default='.',
help='Root directory of the project to scan.'
)
@click.option(
'-o', '--output',
default=None,
help='Output Markdown file name.'
)
@click.option(
'-e', '--exclude',
default=".AGENTS,Agents,AGENTS.md,.claude,.cursor,WORKFLOWS.md,RULES.md,env,.env,venv,.venv,.gitignore,.git,.vscode,.idea,lib,bin,site-packages,node_modules,__pycache__,.DS_Store",
help='Comma-separated list of files/directories to exclude.'
)
def project_to_file(path: str, output: str, exclude: str):
"""
Export entire project to a single Markdown file.
This command scans a project directory and generates a single Markdown
file containing the file tree structure and all source code with syntax
highlighting. Useful for documentation, code reviews, or AI analysis.
Args:
path: Root directory of the project to scan (default: current directory).
output: Name of the output Markdown file (default: <project_name>-1-file.md).
exclude: Comma-separated list of files/directories to exclude from export.
Examples:
pocket project to-file
pocket project to-file -p ./my-project -o export.md
pocket project to-file -e "node_modules,dist,build"
"""
create_codebase_markdown(path, output, exclude)
add_help_argument(project_to_file)
@project_group.command(name="readme", context_settings=CONTEXT_SETTINGS)
@click.option(
'-p', '--path',
default='.',
help='Root directory of the project to scan.'
)
@click.option(
'-o', '--output',
default=None,
help='Output README file path (defaults to <project_root>/README.md).'
)
def project_readme(path: str, output: str | None):
"""Interactively generate a README.md for a project.
This command scans the target project to infer its languages, dependencies,
frameworks and a likely project type. It then runs an interactive wizard
to build a relevant README.md, asking the user a few questions to
customize the final content.
Args:
path: Root directory of the project to scan (default: current directory).
output: Optional explicit README path; default is README.md in project root.
Examples:
pocket project readme
pocket project readme -p ./my-project
pocket project readme -p ./my-project -o ./docs/README.md
"""
run_readme_wizard(path, output)
add_help_argument(project_readme)
@project_group.command(name="req-to-date", context_settings=CONTEXT_SETTINGS)
@click.argument("packages", nargs=-1)
def req_to_date(packages: tuple[str, ...]):
"""Accepte `nom==version`, une liste séparée par des virgules ou un fichier requirements."""
if not packages:
raise click.BadParameter(
"Fournissez au moins un package, une liste séparée par des virgules ou un fichier requirements.txt.",
ctx=click.get_current_context(),
param_hint="packages"
)
try:
results = run_req_to_date(packages)
except ValueError as exc:
raise click.BadParameter(str(exc))
outdated_count = 0
for result in results:
if result.latest_overall and result.current_version != result.latest_overall:
console.print(
f"[red]{result.package} ({result.current_version})[/red] -> "
f"[green]{result.latest_overall}[/green]"
)
outdated_count += 1
if outdated_count == 0:
console.print("[green]All packages are up to date[/green]")
print_req_to_date_results(
results,
lambda result: console.print(
f"{result.package} [red]{result.current_version}[/red] -> "
f"[green]{result.latest_overall}[/green]",
style="bold",
justify="center",
),
)
add_help_argument(req_to_date)
project_group.add_command(init_group)
# ==================== Documents Commands ====================
@cli.group(name="documents", context_settings=CONTEXT_SETTINGS)
def documents_group():
"""Manage agent templates and development cheatsheets."""
pass
@documents_group.command(name="list", context_settings=CONTEXT_SETTINGS)
@click.option(
'--type', '-t',
type=click.Choice(['templates', 'cheatsheets', 'all'], case_sensitive=False),
default='all',
help='Type of items to list.'
)
def documents_list(type: str):
"""
List available templates and cheatsheets.
Displays a formatted table showing all available templates and/or cheatsheets
with their names and descriptions.
Args:
type: Filter by type - 'templates', 'cheatsheets', or 'all' (default: all).
Examples:
pocket documents list
pocket documents list --type templates
pocket documents list -t cheatsheets
"""
ctx = click.Context(list_items)
ctx.invoke(list_items, type=type)
add_help_argument(documents_list)
@documents_group.command(name="view", context_settings=CONTEXT_SETTINGS)
@click.argument('name', type=str)
@click.option(
'--type', '-t',
type=click.Choice(['template', 'cheatsheet'], case_sensitive=False),
help='Type of item to view.'
)
def documents_view(name: str, type: str):
"""
View a template or cheatsheet in the terminal.
Renders the template or cheatsheet content with Markdown formatting
directly in the terminal for quick reference.
Args:
name: Name of the template or cheatsheet (without .md extension).
type: Type of item to view - 'template' or 'cheatsheet' (auto-detected if omitted).
Examples:
pocket documents view unit_tests_agent
pocket documents view SQL -t cheatsheet
"""
ctx = click.Context(view_item)
ctx.invoke(view_item, name=name, type=type)
add_help_argument(documents_view)
@documents_group.command(name="copy", context_settings=CONTEXT_SETTINGS)
@click.argument('name', type=str)
@click.option(
'--output', '-o',
type=click.Path(),
help='Output path for the copied file.'
)
@click.option(
'--type', '-t',
type=click.Choice(['template', 'cheatsheet'], case_sensitive=False),
help='Type of item to copy.'
)
@click.option(
'--force', '-f',
is_flag=True,
help='Overwrite existing file.'
)
def documents_copy(name: str, output: str, type: str, force: bool):
"""
Copy a template or cheatsheet to your project.
Copies the specified template or cheatsheet file to a destination in your
project. Creates parent directories if they don't exist.
Args:
name: Name of the template or cheatsheet (without .md extension).
output: Output path (file or directory) for the copied file.
type: Type of item to copy - 'template' or 'cheatsheet' (auto-detected if omitted).
force: If True, overwrite existing file without confirmation.
Examples:
pocket documents copy unit_tests_agent -o .agents/
pocket documents copy SQL -o docs/cheatsheets/
"""
output_path = Path(output) if output else None
ctx = click.Context(copy_item)
ctx.invoke(copy_item, name=name, output=output_path, type=type, force=force)
add_help_argument(documents_copy)
@documents_group.command(name="init", context_settings=CONTEXT_SETTINGS)
@click.option(
'--output', '-o',
type=click.Path(),
help='Directory for agent templates.'
)
def documents_init(output: str):
"""
Initialize agent configuration directory with all templates.
Creates a directory and copies all available agent templates into it.
Useful for quickly setting up agent configurations in a new project.
Args:
output: Directory where agent templates will be copied (default: .AGENTS).
Examples:
pocket documents init
pocket documents init -o ./agents/
"""
output_path = Path(output) if output else Path.cwd() / ".AGENTS"
ctx = click.Context(init_agents)
ctx.invoke(init_agents, output=output_path)
add_help_argument(documents_init)
# ==================== PDF Commands ====================
@cli.group(name="pdf", context_settings=CONTEXT_SETTINGS)
def pdf_group():
"""PDF conversion tools."""
pass
@pdf_group.command(name="convert", context_settings=CONTEXT_SETTINGS)
@click.argument('input_file', type=click.Path())
@click.option(
'-o', '--output',
type=click.Path(),
help='Output PDF file path.'
)
def pdf_convert_cmd(input_file: str, output: str):
"""
Convert text or Markdown files to PDF.
Converts plain text (.txt) or Markdown (.md) files to PDF format.
Output file defaults to input filename with .pdf extension.
Args:
input_file: Path to the input file (.txt or .md).
output: Optional output PDF file path (default: <input_file>.pdf).
Examples:
pocket pdf convert document.txt
pocket pdf convert README.md -o output.pdf
"""
input_path = Path(input_file)
if not input_path.exists():
raise click.BadParameter(f"Path '{input_file}' does not exist.", param_hint="'INPUT_FILE'")
output_path = Path(output) if output else None
ctx = click.Context(conv2pdf)
ctx.invoke(conv2pdf, input_file=input_path, output=output_path)
add_help_argument(pdf_convert_cmd)
# ==================== Web Commands ====================
@cli.group(name="web", context_settings=CONTEXT_SETTINGS)
def web_group():
"""Web utilities."""
pass
@web_group.command(name="job-search", context_settings=CONTEXT_SETTINGS)
@click.argument("query")
@click.option("-p", "--page", type=int, default=1, help="Page number to start from")
@click.option("-n", "--num_pages", type=int, default=10, help="Number of pages to scrape")
@click.option("-c", "--country", type=str, default="fr", help="Country to search in")
@click.option("-l", "--language", type=str, default="fr", help="Language to search in")
@click.option("-d", "--date_posted", type=str, default="month", help="Date posted to search for. Possible values: all, today, 3days, week, month")
@click.option("-t", "--employment_types", type=str, default="FULLTIME", help="Employment types to search for. Possible values: FULLTIME, CONTRACTOR, PARTTIME, INTERN")
@click.option("-r", "--job_requirements", type=str, default="no_experience", help="Job requirements to search for")
@click.option("--work_from_home", is_flag=True, default=False, help="Search for jobs that allow working from home")
@click.option("-o", "--output", type=str, default="jobs.json", help="Output file name")
def web_job_search_cmd(query: str,
page: int,
num_pages: int,
country: str,
language: str,
date_posted: str,
employment_types: str,
job_requirements: str,
work_from_home: bool,
output: str):
"""
Search for jobs using the JSearch API.
Searches for jobs based on query and saves results to a JSON file.
Requires RAPIDAPI_API_KEY environment variable to be set.
Args:
query: Search query for jobs (e.g., "Python developer").
page: Page number to start from (default: 1).
num_pages: Number of pages to scrape (default: 10).
country: Country to search in (default: fr).
language: Language to search in (default: fr).
date_posted: Date posted filter (default: month).
employment_types: Employment types (default: FULLTIME).
job_requirements: Job requirements (default: no_experience).
output: Output JSON file name (default: jobs.json).
Examples:
pocket web job-search "Python developer"
pocket web job-search "Data scientist" -c us -l en -o data_jobs.json
"""
ctx = click.Context(job_search)
ctx.invoke(job_search, query=query, page=page, num_pages=num_pages,
country=country, language=language, date_posted=date_posted,
employment_types=employment_types, job_requirements=job_requirements,
work_from_home=work_from_home, output=output)
add_help_argument(web_job_search_cmd)
@web_group.command(name="favicon", context_settings=CONTEXT_SETTINGS)
@click.argument('input_file', type=click.Path())
@click.option(
'-o', '--output',
type=click.Path(),
help='Output favicon file path.'
)
@click.option(
'--sizes',
type=str,
help='Custom sizes (e.g., "64x64,32x32,16x16")'
)
def web_favicon_cmd(input_file: str, output: str, sizes: str):
"""
Convert an image to a favicon (.ico) file.
Generates a multi-size .ico favicon file from any image format.
Includes standard sizes for optimal browser compatibility.
Args:
input_file: Path to the input image file (PNG, JPG, etc.).
output: Optional output .ico file path (default: favicon.ico).
sizes: Custom sizes as comma-separated WxH values (e.g., "64x64,32x32").
Examples:
pocket web favicon logo.png
pocket web favicon logo.png -o custom-favicon.ico
pocket web favicon logo.png --sizes "64x64,32x32"
"""
input_path = Path(input_file)
if not input_path.exists():
raise click.BadParameter(f"Path '{input_file}' does not exist.", param_hint="'INPUT_FILE'")
output_path = Path(output) if output else None
ctx = click.Context(favicon_convert)
ctx.invoke(favicon_convert, input_file=input_path, output=output_path, sizes=sizes)
add_help_argument(web_favicon_cmd)
# ==================== README Commands ====================
cli.add_command(readme_cli, name="readme")
# ==================== XML Commands ====================
cli.add_command(xml_cmd, name="xml")
cli.add_command(iconify_cli, name="iconify")
# ==================== Help Commands ====================
# Add 'help' subcommand to all groups
add_help_command(cli)
add_help_command(markdown_group)
add_help_command(project_group)
add_help_command(documents_group)
add_help_command(pdf_group)
add_help_command(web_group)
[docs]
def main():
"""Main entry point for the CLI."""
cli()
if __name__ == '__main__':
main()