#!/usr/bin/env python3 """ CLI entry point for media-agent. Usage: python cli.py preview article.md --theme professional-clean python cli.py publish article.md --appid wx123 --secret abc123 python cli.py themes """ import argparse import sys import webbrowser from pathlib import Path import yaml from converter import WeChatConverter, preview_html from theme import load_theme, list_themes from wechat_api import get_access_token, upload_image, upload_thumb from publisher import create_draft # Config file search order CONFIG_PATHS = [ Path.cwd() / "config.yaml", Path(__file__).parent.parent / "config.yaml", # skill root Path(__file__).parent / "config.yaml", # toolkit dir Path.home() / ".config" / "media-agent" / "config.yaml", ] def load_config() -> dict: """Load config from first found config.yaml.""" for p in CONFIG_PATHS: if p.exists(): with open(p, "r", encoding="utf-8") as f: return yaml.safe_load(f) or {} return {} def cmd_preview(args): """Generate HTML preview and open in browser.""" theme = load_theme(args.theme) converter = WeChatConverter(theme=theme) result = converter.convert_file(args.input) # Wrap in full HTML for browser preview full_html = preview_html(result.html, theme) # Write to temp file input_path = Path(args.input) output = args.output or str(input_path.with_suffix(".html")) Path(output).write_text(full_html, encoding="utf-8") print(f"Title: {result.title}") print(f"Digest: {result.digest}") print(f"Images: {len(result.images)}") print(f"Output: {output}") if not args.no_open: webbrowser.open(f"file://{Path(output).absolute()}") print("Opened in browser.") def cmd_publish(args): """Convert, upload images, and create WeChat draft.""" cfg = load_config() wechat_cfg = cfg.get("wechat", {}) # Resolve from CLI args → config.yaml fallback appid = args.appid or wechat_cfg.get("appid") secret = args.secret or wechat_cfg.get("secret") theme_name = args.theme or cfg.get("theme", "professional-clean") author = args.author or wechat_cfg.get("author") if not appid or not secret: print("Error: --appid and --secret required (or set in config.yaml)", file=sys.stderr) sys.exit(1) theme = load_theme(theme_name) converter = WeChatConverter(theme=theme) result = converter.convert_file(args.input) print(f"Title: {result.title}") print(f"Digest: {result.digest}") print(f"Images found: {len(result.images)}") # Get access token token = get_access_token(appid, secret) print("Access token obtained.") # Upload images referenced in article and replace src # Resolve relative paths against the markdown file's directory md_dir = Path(args.input).resolve().parent html = result.html for img_src in result.images: if img_src.startswith(("http://", "https://")): print(f"Skipping remote image: {img_src}") continue # Try: absolute → relative to CWD → relative to markdown file img_path = Path(img_src) if not img_path.is_absolute(): if not img_path.exists(): img_path = md_dir / img_src if img_path.exists(): print(f"Uploading image: {img_src}") wechat_url = upload_image(token, str(img_path)) html = html.replace(img_src, wechat_url) print(f" -> {wechat_url}") else: print(f"Warning: image not found: {img_src} (searched {md_dir})") # Upload cover image if provided thumb_media_id = None if args.cover: print(f"Uploading cover: {args.cover}") thumb_media_id = upload_thumb(token, args.cover) print(f" -> media_id: {thumb_media_id}") # Create draft title = args.title or result.title or Path(args.input).stem digest = result.digest draft = create_draft( access_token=token, title=title, html=html, digest=digest, thumb_media_id=thumb_media_id, author=author, ) print(f"\nDraft created! media_id: {draft.media_id}") def cmd_themes(args): """List available themes.""" names = list_themes() for name in names: theme = load_theme(name) print(f" {name:24s} {theme.description}") def main(): parser = argparse.ArgumentParser( prog="media-agent", description="Markdown to WeChat HTML converter and publisher", ) sub = parser.add_subparsers(dest="command", required=True) # preview p_preview = sub.add_parser("preview", help="Generate HTML and open in browser") p_preview.add_argument("input", help="Markdown file path") p_preview.add_argument("-t", "--theme", default="professional-clean", help="Theme name") p_preview.add_argument("-o", "--output", help="Output HTML file path") p_preview.add_argument("--no-open", action="store_true", help="Don't open browser") # publish p_publish = sub.add_parser("publish", help="Convert and publish as WeChat draft") p_publish.add_argument("input", help="Markdown file path") p_publish.add_argument("-t", "--theme", default=None, help="Theme name") p_publish.add_argument("--appid", default=None, help="WeChat AppID (or set in config.yaml)") p_publish.add_argument("--secret", default=None, help="WeChat AppSecret (or set in config.yaml)") p_publish.add_argument("--cover", help="Cover image file path") p_publish.add_argument("--title", help="Override article title") p_publish.add_argument("--author", default=None, help="Article author") # themes sub.add_parser("themes", help="List available themes") args = parser.parse_args() try: if args.command == "preview": cmd_preview(args) elif args.command == "publish": cmd_publish(args) elif args.command == "themes": cmd_themes(args) except Exception as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()