import os import sys import click import yaml from app import create_app from app.config import load_config from app.services.vault import Vault from app.utils.time import now_iso_utc @click.group() def main(): """PKM CLI.""" pass @main.command("run") @click.option("--vault", "vault_path", type=click.Path(file_okay=False, dir_okay=True, path_type=str), help="Path to the vault directory") @click.option("--host", default="127.0.0.1", show_default=True, help="Host to bind") @click.option("--port", default=5000, show_default=True, help="Port to bind") @click.option("--debug/--no-debug", default=True, show_default=True, help="Enable/disable debug mode") def runserver(vault_path: str | None, host: str, port: int, debug: bool): """Run the development server.""" cfg = load_config(vault_override=vault_path) if cfg.VAULT_PATH: os.environ["KB_VAULT_PATH"] = cfg.VAULT_PATH os.environ["FLASK_DEBUG"] = "1" if debug else "0" app = create_app(cfg) app.run(host=host, port=port, debug=debug) @main.command("init") @click.option( "--vault", "vault_path", required=True, type=click.Path(file_okay=False, dir_okay=True, path_type=str), help="Path to the vault directory to initialize", ) @click.option( "--sample/--no-sample", default=True, show_default=True, help="Create a sample welcome note", ) @click.option( "--force", is_flag=True, default=False, help="Proceed even if the directory is not empty", ) def init_vault(vault_path: str, sample: bool, force: bool): """ Initialize a vault directory with notes/, attachments/, and .kb/. Optionally create a sample note and a .kb/config.yml file. """ vault_path = os.path.abspath(vault_path) exists = os.path.exists(vault_path) if exists: # Check emptiness only if directory exists try: entries = [e for e in os.listdir(vault_path) if not e.startswith(".")] except FileNotFoundError: entries = [] if entries and not force: click.echo( click.style( f"Directory '{vault_path}' is not empty. Use --force to proceed.", fg="yellow", ) ) sys.exit(1) else: os.makedirs(vault_path, exist_ok=True) v = Vault(vault_path) v.ensure_structure() # Write .kb/config.yml if it doesn't exist kb_cfg_path = os.path.join(v.paths.kb, "config.yml") if not os.path.exists(kb_cfg_path): cfg_data = { "version": 1, "created": now_iso_utc(), } with open(kb_cfg_path, "w", encoding="utf-8") as f: yaml.safe_dump(cfg_data, f, sort_keys=True) click.echo(click.style(f"Created {kb_cfg_path}", fg="green")) else: click.echo(click.style(f"Exists {kb_cfg_path}", fg="blue")) # Optionally create a sample note using the app context (reuses note writer) if sample: from app.services.notes_fs import create_note # lazy import to avoid app deps if not needed # Build a minimal app context so create_note can resolve the vault cfg = load_config(vault_override=vault_path) app = create_app(cfg) with app.app_context(): # Try to avoid duplicates by checking for an existing welcome file name title = "Welcome to your vault" body = ( "# Welcome to your vault\n\n" "This is your first note. You can edit or delete it.\n\n" "- Notes live under `notes/`\n" "- Attachments go to `attachments/`\n" "- App internals go to `.kb/`\n" ) note = create_note(title=title, body=body, tags=["welcome"]) click.echo(click.style(f"Created sample note at {os.path.join(v.paths.root, note.rel_path)}", fg="green")) click.echo(click.style(f"Vault initialized at {vault_path}", fg="green")) if __name__ == "__main__": main()