wewrite/toolkit/cli.py
wangzhuc 1ab34fa450 Initial release — 公众号文章全流程 AI Skill
热点抓取 → 选题 → 框架 → 写作 → SEO → 视觉AI → 排版 → 微信草稿箱,
一句话触发完整流程。适用于 Claude Code skill 格式。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:16:18 +08:00

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()