"""CLI for viewing and editing plan.json files."""
import argparse
import sys
from pathlib import Path
from plan_view.commands.edit import (
cmd_add_phase,
cmd_add_task,
cmd_block,
cmd_bug,
cmd_compact,
cmd_defer,
cmd_done,
cmd_idea,
cmd_init,
cmd_rm,
cmd_set,
cmd_skip,
cmd_start,
)
from plan_view.commands.view import (
HELP_TEXT,
cmd_bugs,
cmd_current,
cmd_deferred,
cmd_future,
cmd_get,
cmd_ideas,
cmd_last,
cmd_next,
cmd_overview,
cmd_phase,
cmd_summary,
cmd_validate,
)
# Re-export all public API for backward compatibility with tests
from plan_view.formatting import (
BOLD,
CYAN,
DIM,
GREEN,
ICONS,
RESET,
VALID_STATUSES,
YELLOW,
bold,
bold_cyan,
bold_yellow,
dim,
green,
now_iso,
)
from plan_view.io import load_plan, load_schema, save_plan
from plan_view.state import (
find_phase,
find_task,
get_current_phase,
get_next_task,
recalculate_progress,
task_to_dict,
)
# Explicit __all__ for documentation and IDE support
__all__ = [
# Constants
"ICONS",
"VALID_STATUSES",
"RESET",
"BOLD",
"DIM",
"GREEN",
"YELLOW",
"CYAN",
"HELP_TEXT",
# Formatting
"bold",
"dim",
"green",
"bold_cyan",
"bold_yellow",
"now_iso",
# I/O
"load_plan",
"save_plan",
"load_schema",
# State
"recalculate_progress",
"get_current_phase",
"get_next_task",
"find_task",
"find_phase",
"task_to_dict",
# View Commands
"cmd_overview",
"cmd_current",
"cmd_next",
"cmd_phase",
"cmd_get",
"cmd_last",
"cmd_future",
"cmd_summary",
"cmd_bugs",
"cmd_deferred",
"cmd_ideas",
"cmd_validate",
# Edit Commands
"cmd_init",
"cmd_add_phase",
"cmd_add_task",
"cmd_set",
"cmd_done",
"cmd_start",
"cmd_block",
"cmd_skip",
"cmd_defer",
"cmd_bug",
"cmd_idea",
"cmd_rm",
"cmd_compact",
# Entry point
"main",
]
[docs]
def main() -> None:
"""CLI entry point for pv command."""
parser = argparse.ArgumentParser(
prog="pv",
description="View and edit plan.json for task tracking",
formatter_class=argparse.RawDescriptionHelpFormatter,
usage="pv [-f FILE] [--json] <command> [args]",
add_help=False,
)
parser.add_argument(
"-f",
"--file",
type=Path,
default=Path("plan.json"),
help=argparse.SUPPRESS,
)
parser.add_argument(
"--json",
action="store_true",
help=argparse.SUPPRESS,
)
parser.add_argument(
"-h",
"--help",
action="store_true",
help=argparse.SUPPRESS,
)
subparsers = parser.add_subparsers(dest="command", metavar="command")
# View commands (all support --json, default=None to not override parent)
for name, aliases in [("current", ["c"]), ("next", ["n"]), ("phase", ["p"]), ("validate", ["v"])]:
sp = subparsers.add_parser(name, aliases=aliases, add_help=False, parents=[])
sp.add_argument("--json", action="store_true", default=None)
get_p = subparsers.add_parser("get", aliases=["g"], add_help=False)
get_p.add_argument("id")
get_p.add_argument("--json", action="store_true", default=None)
last_p = subparsers.add_parser("last", aliases=["l"], add_help=False)
last_p.add_argument("-n", "--count", type=int, default=5)
last_p.add_argument("-a", "--all", action="store_true")
last_p.add_argument("--json", action="store_true", default=None)
future_p = subparsers.add_parser("future", aliases=["f"], add_help=False)
future_p.add_argument("-n", "--count", type=int, default=5)
future_p.add_argument("-a", "--all", action="store_true")
future_p.add_argument("--json", action="store_true", default=None)
summary_p = subparsers.add_parser("summary", aliases=["s"], add_help=False)
summary_p.add_argument("--json", action="store_true", default=None)
bugs_p = subparsers.add_parser("bugs", aliases=["b"], add_help=False)
bugs_p.add_argument("--json", action="store_true", default=None)
deferred_p = subparsers.add_parser("deferred", aliases=["d"], add_help=False)
deferred_p.add_argument("--json", action="store_true", default=None)
ideas_p = subparsers.add_parser("ideas", aliases=["i"], add_help=False)
ideas_p.add_argument("--json", action="store_true", default=None)
subparsers.add_parser("help", aliases=["h"], add_help=False)
# Init
init_p = subparsers.add_parser("init", add_help=False)
init_p.add_argument("name")
init_p.add_argument("--force", action="store_true")
init_p.add_argument("-q", "--quiet", action="store_true")
init_p.add_argument("-d", "--dry-run", action="store_true")
# Add phase
add_phase_p = subparsers.add_parser("add-phase", add_help=False)
add_phase_p.add_argument("name")
add_phase_p.add_argument("--desc")
add_phase_p.add_argument("-q", "--quiet", action="store_true")
add_phase_p.add_argument("-d", "--dry-run", action="store_true")
# Add task
add_task_p = subparsers.add_parser("add-task", add_help=False)
add_task_p.add_argument("phase")
add_task_p.add_argument("title")
add_task_p.add_argument("--agent")
add_task_p.add_argument("--deps")
add_task_p.add_argument("-q", "--quiet", action="store_true")
add_task_p.add_argument("-d", "--dry-run", action="store_true")
# Set field
set_p = subparsers.add_parser("set", add_help=False)
set_p.add_argument("id")
set_p.add_argument("field")
set_p.add_argument("value")
set_p.add_argument("-q", "--quiet", action="store_true")
set_p.add_argument("-d", "--dry-run", action="store_true")
# Shortcuts
done_p = subparsers.add_parser("done", add_help=False)
done_p.add_argument("id")
done_p.add_argument("-q", "--quiet", action="store_true")
done_p.add_argument("-d", "--dry-run", action="store_true")
start_p = subparsers.add_parser("start", add_help=False)
start_p.add_argument("id")
start_p.add_argument("-q", "--quiet", action="store_true")
start_p.add_argument("-d", "--dry-run", action="store_true")
block_p = subparsers.add_parser("block", add_help=False)
block_p.add_argument("id")
block_p.add_argument("-q", "--quiet", action="store_true")
block_p.add_argument("-d", "--dry-run", action="store_true")
skip_p = subparsers.add_parser("skip", add_help=False)
skip_p.add_argument("id")
skip_p.add_argument("-q", "--quiet", action="store_true")
skip_p.add_argument("-d", "--dry-run", action="store_true")
defer_p = subparsers.add_parser("defer", add_help=False)
defer_p.add_argument("id")
defer_p.add_argument("-r", "--reason", help="Reason for deferring the task")
defer_p.add_argument("-q", "--quiet", action="store_true")
defer_p.add_argument("-d", "--dry-run", action="store_true")
bug_p = subparsers.add_parser("bug", add_help=False)
bug_p.add_argument("id")
bug_p.add_argument("-q", "--quiet", action="store_true")
bug_p.add_argument("-d", "--dry-run", action="store_true")
idea_p = subparsers.add_parser("idea", add_help=False)
idea_p.add_argument("id")
idea_p.add_argument("-q", "--quiet", action="store_true")
idea_p.add_argument("-d", "--dry-run", action="store_true")
# Remove
rm_p = subparsers.add_parser("rm", add_help=False)
rm_p.add_argument("type", choices=["phase", "task"])
rm_p.add_argument("id")
rm_p.add_argument("-q", "--quiet", action="store_true")
rm_p.add_argument("-d", "--dry-run", action="store_true")
# Compact
compact_p = subparsers.add_parser("compact", add_help=False)
compact_p.add_argument("-n", "--max-backups", type=int, default=5)
compact_p.add_argument("-q", "--quiet", action="store_true")
compact_p.add_argument("-d", "--dry-run", action="store_true")
args = parser.parse_args()
# Help command
if args.help or args.command in ("help", "h"):
print(HELP_TEXT)
return
# Handle edit commands that don't need to load plan first
match args.command:
case "init":
cmd_init(args)
return
case "add-phase":
cmd_add_phase(args)
return
case "add-task":
cmd_add_task(args)
return
case "set":
cmd_set(args)
return
case "done":
cmd_done(args)
return
case "start":
cmd_start(args)
return
case "block":
cmd_block(args)
return
case "skip":
cmd_skip(args)
return
case "defer":
cmd_defer(args)
return
case "bug":
cmd_bug(args)
return
case "idea":
cmd_idea(args)
return
case "rm":
cmd_rm(args)
return
case "compact":
cmd_compact(args)
return
# View commands need to load plan
plan = load_plan(args.file)
if plan is None:
sys.exit(1)
assert plan is not None # Help type checker after sys.exit
# --json can appear anywhere in args
as_json = "--json" in sys.argv
match args.command:
case "current" | "c":
cmd_current(plan, as_json=as_json)
case "next" | "n":
cmd_next(plan, as_json=as_json)
case "phase" | "p":
cmd_phase(plan, as_json=as_json)
case "get" | "g":
cmd_get(plan, args.id, as_json=as_json)
case "last" | "l":
cmd_last(plan, None if args.all else args.count, as_json=as_json)
case "future" | "f":
cmd_future(plan, None if args.all else args.count, as_json=as_json)
case "summary" | "s":
cmd_summary(plan, as_json=as_json)
case "bugs" | "b":
cmd_bugs(plan, as_json=as_json)
case "deferred" | "d":
cmd_deferred(plan, as_json=as_json)
case "ideas" | "i":
cmd_ideas(plan, as_json=as_json)
case "validate" | "v":
cmd_validate(plan, args.file, as_json=as_json)
case _:
cmd_overview(plan, as_json=as_json)
if __name__ == "__main__":
main()