热点抓取 → 选题 → 框架 → 写作 → SEO → 视觉AI → 排版 → 微信草稿箱, 一句话触发完整流程。适用于 Claude Code skill 格式。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
187 lines
6 KiB
Python
187 lines
6 KiB
Python
#!/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()
|