"""Discovery commands — recommend, recipe."""
from __future__ import annotations
import typer
# --- Preset definitions for recommend ---
[docs]
PRESETS: dict[str, dict[str, list[str]]] = {
"minimal": {
"single_package": ["dev", "linting", "testing"],
"service": ["dev", "linting", "testing", "docker"],
"poly_repo": ["dev", "linting", "testing", "docker", "makefile"],
"script_tool": ["dev", "linting", "testing"],
},
"standard": {
"single_package": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"docs",
"github_templates",
],
"service": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"docs",
"api",
"database",
"docker",
"logging",
"github_templates",
"makefile",
],
"poly_repo": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"docs",
"docker",
"logging",
"makefile",
"github_templates",
"submodules",
],
"script_tool": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"scripts",
"docs",
],
},
"full": {
"single_package": [
"dev",
"dev_extended",
"linting",
"testing",
"testing_extended",
"typecheck",
"coverage",
"security",
"code_quality",
"docs",
"github_templates",
"towncrier",
"reporting",
],
"service": [
"dev",
"dev_extended",
"linting",
"testing",
"testing_extended",
"typecheck",
"coverage",
"security",
"code_quality",
"docs",
"api",
"auth",
"database",
"redis",
"docker",
"k8s",
"logging",
"otel",
"monitoring",
"makefile",
"github_templates",
"towncrier",
"reporting",
],
"poly_repo": [
"dev",
"dev_extended",
"linting",
"testing",
"testing_extended",
"typecheck",
"coverage",
"security",
"code_quality",
"docs",
"docker",
"k8s",
"logging",
"otel",
"makefile",
"github_templates",
"submodules",
"towncrier",
"reporting",
],
"script_tool": [
"dev",
"dev_extended",
"linting",
"testing",
"testing_extended",
"typecheck",
"coverage",
"security",
"scripts",
"docs",
"github_templates",
"towncrier",
],
},
"ai": {
"single_package": [
"dev",
"linting",
"testing",
"langchain",
"langchain_providers",
"langgraph",
"mcp_tools",
"hf",
"ml",
"vector_stores",
"search_tools",
"doc_parsing",
"jupyter",
"notebook",
],
"service": [
"dev",
"linting",
"testing",
"api",
"database",
"redis",
"docker",
"logging",
"otel",
"langchain",
"langchain_providers",
"langgraph",
"mcp_tools",
"hf",
"ml",
"vector_stores",
"search_tools",
"doc_parsing",
"makefile",
],
"poly_repo": [
"dev",
"linting",
"testing",
"docker",
"logging",
"langchain",
"langchain_providers",
"langgraph",
"mcp_tools",
"hf",
"ml",
"vector_stores",
"search_tools",
"makefile",
],
"script_tool": [
"dev",
"linting",
"testing",
"langchain",
"ml",
"search_tools",
"scripts",
],
},
"data": {
"single_package": [
"dev",
"linting",
"testing",
"ml",
"dataviz",
"jupyter",
"notebook",
"database",
"doc_parsing",
],
"service": [
"dev",
"linting",
"testing",
"api",
"database",
"redis",
"docker",
"logging",
"ml",
"dataviz",
"jupyter",
"notebook",
"doc_parsing",
"celery",
"makefile",
],
"poly_repo": [
"dev",
"linting",
"testing",
"docker",
"database",
"redis",
"ml",
"dataviz",
"jupyter",
"notebook",
"celery",
"makefile",
],
"script_tool": [
"dev",
"linting",
"testing",
"ml",
"dataviz",
"scripts",
],
},
"web": {
"service": [
"dev",
"linting",
"testing",
"api",
"auth",
"gateway",
"database",
"redis",
"docker",
"nginx",
"logging",
"otel",
"monitoring",
"frontend",
"makefile",
"github_templates",
],
"poly_repo": [
"dev",
"linting",
"testing",
"api",
"database",
"redis",
"docker",
"nginx",
"logging",
"frontend",
"makefile",
"github_templates",
"submodules",
],
"single_package": [
"dev",
"linting",
"testing",
"api",
"auth",
"database",
],
"script_tool": [
"dev",
"linting",
"testing",
"scripts",
],
},
}
# --- Recipe definitions ---
[docs]
RECIPES: dict[str, dict] = {
"python-lib": {
"description": "Publish-ready Python library with full CI/CD",
"archetype": "single-package",
"groups": [
"dev",
"dev_extended",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"code_quality",
"docs",
"github_templates",
"towncrier",
],
},
"fastapi-service": {
"description": "Production FastAPI service with database, auth, and observability",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"api",
"auth",
"database",
"redis",
"docker",
"logging",
"otel",
"monitoring",
"makefile",
"github_templates",
"error_tracking",
"docs",
],
},
"ai-agent": {
"description": "LangGraph agent with tools, memory, structured output, and MCP",
"archetype": "single-package",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"agents",
"langchain",
"langchain_providers",
"langgraph",
"mcp_tools",
"llm_providers",
"vector_stores",
"embeddings",
"search_tools",
"doc_parsing",
"redis",
"logging",
"eval",
"docs",
],
},
"rag-service": {
"description": "RAG API service with vector store, embeddings, and document ingestion",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"api",
"auth",
"agents",
"langchain",
"langgraph",
"rag",
"vector_stores",
"embeddings",
"llm_providers",
"doc_parsing",
"database",
"redis",
"docker",
"logging",
"otel",
"makefile",
"docs",
],
},
"agent-platform": {
"description": "Multi-agent platform with tools, evaluation, and observability",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"agents",
"langchain",
"langchain_providers",
"langgraph",
"mcp_tools",
"llm_providers",
"vector_stores",
"embeddings",
"search_tools",
"eval",
"api",
"database",
"redis",
"docker",
"logging",
"otel",
"monitoring",
"makefile",
"docs",
],
},
"ml-pipeline": {
"description": "ML training pipeline with experiment tracking and data tools",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"ml",
"hf",
"torch",
"dataviz",
"jupyter",
"notebook",
"docker",
"logging",
"makefile",
"database",
"celery",
],
},
"data-analysis": {
"description": "Data analysis workspace with notebooks and visualization",
"archetype": "single-package",
"groups": [
"dev",
"linting",
"testing",
"ml",
"dataviz",
"jupyter",
"notebook",
"database",
"doc_parsing",
"reporting",
],
},
"cli-tool": {
"description": "Polished CLI tool with rich output and testing",
"archetype": "script-tool",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"scripts",
"docs",
"github_templates",
"towncrier",
],
},
"fullstack-web": {
"description": "Full-stack web app with API, frontend, auth, and infra",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"api",
"auth",
"gateway",
"database",
"redis",
"docker",
"nginx",
"logging",
"otel",
"monitoring",
"frontend",
"frontend_vite",
"makefile",
"github_templates",
"docs",
],
},
"monorepo": {
"description": "Multi-package monorepo with shared infra and CI",
"archetype": "poly-repo",
"groups": [
"dev",
"dev_extended",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"code_quality",
"docs",
"docker",
"k8s",
"logging",
"otel",
"makefile",
"github_templates",
"submodules",
"towncrier",
"reporting",
],
},
"scraper": {
"description": "Web scraping pipeline with crawling, parsing, and storage",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"web_scraping",
"crawling",
"doc_parsing",
"database",
"redis",
"docker",
"logging",
"celery",
"makefile",
],
},
"fintech": {
"description": "Financial data service with payments, compliance, and monitoring",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"api",
"auth",
"database",
"redis",
"docker",
"logging",
"otel",
"monitoring",
"finance",
"payments",
"error_tracking",
"makefile",
"github_templates",
"docs",
],
},
"api-microservice": {
"description": "Lightweight microservice with async API, caching, and health checks",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"api",
"database",
"redis",
"caching",
"docker",
"logging",
"otel",
"async_tools",
"http_client",
"config_mgmt",
"makefile",
"ci_cd",
],
},
"discord-bot": {
"description": "Discord/Slack bot with async handlers and task scheduling",
"archetype": "single-package",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"async_tools",
"http_client",
"config_mgmt",
"messaging",
"redis",
"logging",
"scheduling",
"docker",
],
},
"etl-pipeline": {
"description": "ETL/data pipeline with scheduling, queues, and database",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"database",
"redis",
"celery",
"scheduling",
"docker",
"logging",
"otel",
"doc_parsing",
"elasticsearch",
"makefile",
],
},
"saas-backend": {
"description": "Multi-tenant SaaS backend with auth, billing, email, and webhooks",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"api",
"auth",
"database",
"redis",
"caching",
"docker",
"logging",
"otel",
"monitoring",
"payments",
"email",
"websocket",
"scheduling",
"error_tracking",
"makefile",
"github_templates",
"ci_cd",
"docs",
],
},
"document-processor": {
"description": "Document ingestion, parsing, OCR, and PDF generation pipeline",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"doc_parsing",
"pdf",
"image",
"database",
"redis",
"celery",
"docker",
"logging",
"makefile",
],
},
"media-pipeline": {
"description": "Video/audio/image processing pipeline with ffmpeg and ML",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"video",
"audio",
"image",
"ocr",
"file_utils",
"s3",
"database",
"redis",
"celery",
"docker",
"logging",
"makefile",
],
},
"realtime-api": {
"description": "Real-time API with WebSocket, SSE, rate limiting, and streaming",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"api",
"auth",
"websocket",
"sse",
"rate_limit",
"redis",
"caching",
"database",
"docker",
"logging",
"otel",
"makefile",
],
},
"file-service": {
"description": "File upload/storage service with S3, thumbnails, and metadata",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"api",
"auth",
"file_upload",
"s3",
"image",
"file_utils",
"database",
"redis",
"docker",
"logging",
"makefile",
],
},
"scraper-full": {
"description": "Full scraping platform with MinIO, Postgres, Celery, frontend, Prometheus",
"archetype": "service",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"security",
"api",
"auth",
"database",
"redis",
"caching",
"s3",
"web_scraping",
"crawling",
"doc_parsing",
"file_utils",
"celery",
"docker",
"logging",
"otel",
"monitoring",
"frontend",
"makefile",
"github_templates",
"ci_cd",
"error_tracking",
"docs",
],
},
"tui-app": {
"description": "Terminal UI application with Textual, rich widgets, and async support",
"archetype": "script-tool",
"groups": [
"dev",
"linting",
"testing",
"typecheck",
"coverage",
"textual_tui",
"cli_rich",
"async_tools",
"config_mgmt",
"http_client",
"logging",
"docs",
"github_templates",
],
},
}
[docs]
def recommend(
archetype: str = typer.Argument(
help="Project archetype: single-package, service, poly-repo, script-tool"
),
preset: str = typer.Option(
"",
"--preset",
"-p",
help="Preset profile: minimal, standard, full, ai, data, web",
),
) -> None:
"""Recommend package groups for a project type.
Shows which groups are recommended based on archetype and optional preset.
Copy the output directly into your `pjkm init` command.
"""
from rich.console import Console
from rich.panel import Panel
from pjkm.core.groups.registry import GroupRegistry
console = Console()
archetype = archetype.replace("-", "_")
if preset:
preset = preset.lower()
if preset not in PRESETS:
console.print(
f"[red]Unknown preset: {preset}. Options: {', '.join(PRESETS.keys())}[/red]"
)
raise typer.Exit(1)
groups_for_preset = PRESETS[preset].get(archetype, [])
if not groups_for_preset:
console.print(f"[yellow]No preset '{preset}' for archetype '{archetype}'[/yellow]")
raise typer.Exit(1)
groups_str = " ".join(f"-g {g}" for g in groups_for_preset)
console.print(
Panel(
f"[bold]{preset.title()}[/bold] preset for [cyan]{archetype}[/cyan]\n\n"
f"Groups ({len(groups_for_preset)}): "
+ ", ".join(f"[cyan]{g}[/cyan]" for g in groups_for_preset),
title="Recommended Groups",
)
)
console.print()
console.print("[dim]Copy this command:[/dim]")
console.print(f" pjkm init my-project -a {archetype} {groups_str}")
return
# No preset — show all presets for this archetype
console.print(f"\n[bold]Presets for archetype: {archetype}[/bold]\n")
for preset_name in PRESETS:
groups_for_preset = PRESETS[preset_name].get(archetype, [])
if not groups_for_preset:
continue
console.print(
f" [bold cyan]{preset_name}[/bold cyan] ({len(groups_for_preset)} groups): "
+ ", ".join(groups_for_preset)
)
console.print()
console.print("[dim]Usage: pjkm recommend <archetype> --preset <name>[/dim]")
# Also show all compatible groups
registry = GroupRegistry()
registry.load_all()
compatible = registry.list_for_archetype(archetype)
console.print(f"\n[dim]Total compatible groups for {archetype}: {len(compatible)}[/dim]")
[docs]
def recipe(
name: str = typer.Argument(
"",
help="Recipe name: python-lib, fastapi-service, ai-agent, ml-pipeline, "
"data-analysis, cli-tool, fullstack-web, monorepo, scraper, fintech",
),
show: bool = typer.Option(
False, "--show", "-s", help="Show recipe details without generating a command"
),
) -> None:
"""Generate a full pjkm init command from a named recipe.
Recipes are opinionated, ready-to-use project configurations that combine
an archetype, groups, and fragments into a single command. Use --show to
inspect what a recipe includes before running it.
"""
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
console = Console()
if not name:
table = Table(title="Available Recipes")
table.add_column("Recipe", style="cyan bold")
table.add_column("Archetype", style="green")
table.add_column("Groups", style="dim", justify="right")
table.add_column("Description")
for rname, rdata in RECIPES.items():
table.add_row(
rname,
rdata["archetype"],
str(len(rdata["groups"])),
rdata["description"],
)
console.print(table)
console.print()
console.print("[dim]Usage: pjkm recipe <name> [--show][/dim]")
return
if name not in RECIPES:
console.print(f"[red]Unknown recipe: {name}. Options: {', '.join(RECIPES.keys())}[/red]")
raise typer.Exit(1)
r = RECIPES[name]
archetype = r["archetype"]
groups = r["groups"]
groups_str = " ".join(f"-g {g}" for g in groups)
if show:
console.print(
Panel(
f"[bold]{name}[/bold] — {r['description']}\n\n"
f"Archetype: [green]{archetype}[/green]\n"
f"Groups ({len(groups)}):\n" + "\n".join(f" [cyan]{g}[/cyan]" for g in groups),
title="Recipe Details",
)
)
return
console.print(
Panel(
f"[bold]{name}[/bold] — {r['description']}\n\n"
f"Archetype: [green]{archetype}[/green]\n"
f"Groups: {len(groups)}",
title="Recipe",
)
)
console.print()
console.print("[dim]Run this command:[/dim]")
console.print(f" pjkm init my-project -a {archetype} {groups_str}")
[docs]
def recipe_create(
name: str = typer.Argument(help="Recipe name (e.g. my-stack)"),
archetype: str = typer.Option(
"service",
"--archetype",
"-a",
help="Archetype: single-package, service, poly-repo, script-tool",
),
group: list[str] = typer.Option(
[],
"--group",
"-g",
help="Groups to include (repeatable)",
),
description: str = typer.Option(
"",
"--description",
"-d",
help="Recipe description",
),
output: str = typer.Option(
"",
"--output",
"-o",
help="Output file (default: .pjkm/recipes/<name>.yaml)",
),
) -> None:
"""Create a custom recipe and save it for reuse.
Custom recipes are saved to .pjkm/recipes/ and loaded alongside
built-in recipes. Share them via git or group sources.
Examples:
pjkm recipe-create my-stack -a service -g api -g database -g redis -g docker
pjkm recipe-create ai-rag -a service -g agents -g rag -g vector_stores -g api
"""
from pathlib import Path
import yaml
from rich.console import Console
console = Console()
if not group:
console.print("[red]At least one group is required (use -g)[/red]")
raise typer.Exit(1)
recipe_data = {
"name": name,
"description": description or f"Custom recipe: {name}",
"archetype": archetype,
"groups": list(group),
}
if output:
out_path = Path(output)
else:
out_path = Path.cwd() / ".pjkm" / "recipes" / f"{name.replace('-', '_')}.yaml"
out_path.parent.mkdir(parents=True, exist_ok=True)
with open(out_path, "w") as f:
yaml.dump(recipe_data, f, default_flow_style=False, sort_keys=False)
console.print(f"[green]Created recipe: {out_path}[/green]")
console.print(f" Name: [cyan]{name}[/cyan]")
console.print(f" Archetype: {archetype}")
console.print(f" Groups ({len(group)}): {', '.join(group)}")
console.print()
console.print("[dim]Use it:[/dim]")
groups_str = " ".join(f"-g {g}" for g in group)
console.print(f" pjkm init my-project -a {archetype} {groups_str}")