2026-02-10 16:56:17 +08:00
|
|
|
"""Plan command implementation."""
|
|
|
|
|
|
2026-02-10 18:07:38 +08:00
|
|
|
import json
|
2026-02-10 16:56:17 +08:00
|
|
|
from pathlib import Path
|
2026-02-10 18:07:38 +08:00
|
|
|
from typing import Optional
|
2026-02-10 16:56:17 +08:00
|
|
|
|
|
|
|
|
import click
|
|
|
|
|
|
|
|
|
|
from vlm.context import CLIContext
|
2026-02-10 18:07:38 +08:00
|
|
|
from vlm.io import identities_to_plan_input, load_identities_json, load_analysis_json
|
2026-02-10 16:56:17 +08:00
|
|
|
from vlm.planner import generate_plan, save_plan
|
|
|
|
|
|
|
|
|
|
|
2026-02-10 18:07:38 +08:00
|
|
|
def plan_cmd(ctx: CLIContext, input: Path, output: Path, analysis: Optional[Path] = None) -> None:
|
|
|
|
|
"""Generate execution plan from identities; optionally use analysis for duplicate handling."""
|
2026-02-10 16:56:17 +08:00
|
|
|
config = ctx.config
|
|
|
|
|
logger = ctx.logger
|
|
|
|
|
|
|
|
|
|
click.echo(f"Generating execution plan from: {input}")
|
2026-02-10 18:07:38 +08:00
|
|
|
if analysis:
|
|
|
|
|
click.echo(f"Using analysis: {analysis}")
|
2026-02-10 16:56:17 +08:00
|
|
|
click.echo()
|
|
|
|
|
|
|
|
|
|
identities_data = load_identities_json(input)
|
|
|
|
|
movies_data = identities_data.get("movies", [])
|
|
|
|
|
series_data = identities_data.get("series", [])
|
|
|
|
|
anime_data = identities_data.get("anime", [])
|
|
|
|
|
other_data = identities_data.get("other", [])
|
|
|
|
|
|
|
|
|
|
click.echo(
|
|
|
|
|
f"Loaded {len(movies_data)} movies, {len(series_data)} series, "
|
|
|
|
|
f"{len(anime_data)} anime, {len(other_data)} other"
|
|
|
|
|
)
|
|
|
|
|
click.echo()
|
|
|
|
|
|
|
|
|
|
identities_list = identities_to_plan_input(identities_data)
|
|
|
|
|
|
2026-02-10 18:07:38 +08:00
|
|
|
analysis_data = None
|
|
|
|
|
if analysis:
|
|
|
|
|
if not analysis.exists():
|
|
|
|
|
click.echo(f"Error: Analysis file not found: {analysis}", err=True)
|
|
|
|
|
click.echo("Run 'vlm analyze' first to generate analysis.json.", err=True)
|
|
|
|
|
raise FileNotFoundError(analysis)
|
|
|
|
|
try:
|
|
|
|
|
analysis_data = load_analysis_json(analysis)
|
|
|
|
|
except (json.JSONDecodeError, KeyError) as e:
|
|
|
|
|
click.echo(f"Error: Invalid or incomplete analysis file: {e}", err=True)
|
|
|
|
|
click.echo("Run 'vlm analyze' to regenerate analysis.json.", err=True)
|
|
|
|
|
raise
|
|
|
|
|
|
2026-02-10 16:56:17 +08:00
|
|
|
click.echo("Generating execution plan...")
|
2026-02-10 18:07:38 +08:00
|
|
|
execution_plan = generate_plan(identities_list, config, analysis_data=analysis_data)
|
2026-02-10 16:56:17 +08:00
|
|
|
|
|
|
|
|
click.echo()
|
|
|
|
|
click.echo("Plan generation complete!")
|
|
|
|
|
click.echo()
|
|
|
|
|
click.echo("Operation summary:")
|
|
|
|
|
click.echo(f" Total operations: {execution_plan.summary['total']}")
|
|
|
|
|
click.echo(f" Move operations: {execution_plan.summary['move']}")
|
|
|
|
|
click.echo(f" Rename operations: {execution_plan.summary['rename']}")
|
|
|
|
|
click.echo(f" Quarantine operations: {execution_plan.summary['quarantine']}")
|
|
|
|
|
click.echo(f" No-op (skipped): {execution_plan.summary['no-op']}")
|
|
|
|
|
|
|
|
|
|
conflicts = sum(1 for op in execution_plan.operations if op.has_conflict)
|
|
|
|
|
if conflicts > 0:
|
|
|
|
|
click.echo()
|
|
|
|
|
click.echo(f" ⚠️ Conflicts detected: {conflicts}")
|
|
|
|
|
click.echo(" Review the plan file for details on conflicting operations.")
|
|
|
|
|
|
|
|
|
|
click.echo()
|
|
|
|
|
click.echo(f"Saving execution plan to: {output}")
|
|
|
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
save_plan(execution_plan, output)
|
|
|
|
|
|
|
|
|
|
click.echo("Execution plan saved successfully!")
|
|
|
|
|
click.echo()
|
|
|
|
|
click.echo("Next steps:")
|
|
|
|
|
click.echo(f" 1. Review the plan: {output}")
|
|
|
|
|
click.echo(" 2. Edit the plan if needed (it's JSON)")
|
|
|
|
|
click.echo(f" 3. Dry-run: vlm execute --plan {output}")
|
|
|
|
|
click.echo(f" 4. Execute: vlm execute --plan {output} --confirm")
|
|
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
f"Plan generated: {execution_plan.summary['total']} operations, "
|
|
|
|
|
f"{conflicts} conflicts, saved to {output}"
|
|
|
|
|
)
|