#!/usr/bin/env python3 """ Ulysses Export Helper Exports markdown files to iCloud folder for Ulysses sync. Designed for OurDigital research-to-publisher workflow. Usage: python export_to_ulysses.py --content "# Title\n\nContent..." --filename "my-post.md" python export_to_ulysses.py --file /path/to/draft.md --channel blog """ import os import sys import argparse from pathlib import Path from datetime import datetime # Default iCloud paths (user should customize) ICLOUD_BASE = Path.home() / "Library/Mobile Documents/com~apple~CloudDocs" # Channel-specific folders (customize based on Ulysses library structure) CHANNEL_FOLDERS = { "blog": "Ulysses/OurDigital/Blog Drafts", "journal": "Ulysses/OurDigital/Journal Drafts", "ourstory": "Ulysses/OurDigital/OurStory Drafts", "medium": "Ulysses/OurDigital/Medium Drafts", "default": "Ulysses/Blog Drafts" } def get_export_path(channel: str = "default") -> Path: """Get the export path for a specific channel.""" folder = CHANNEL_FOLDERS.get(channel, CHANNEL_FOLDERS["default"]) export_path = ICLOUD_BASE / folder # Create directory if it doesn't exist export_path.mkdir(parents=True, exist_ok=True) return export_path def generate_filename(title: str = None) -> str: """Generate a filename with timestamp.""" timestamp = datetime.now().strftime("%Y%m%d_%H%M") if title: # Clean title for filename clean_title = "".join(c if c.isalnum() or c in "- " else "" for c in title) clean_title = clean_title.replace(" ", "-").lower()[:50] return f"{timestamp}_{clean_title}.md" return f"{timestamp}_draft.md" def export_content(content: str, filename: str, channel: str = "default") -> Path: """Export content to the appropriate iCloud folder.""" export_path = get_export_path(channel) file_path = export_path / filename # Add Ulysses-friendly frontmatter if not present if not content.startswith("---"): frontmatter = f"""--- created: {datetime.now().isoformat()} channel: {channel} status: draft --- """ content = frontmatter + content file_path.write_text(content, encoding="utf-8") return file_path def export_file(source_path: str, channel: str = "default") -> Path: """Copy an existing file to the iCloud folder.""" source = Path(source_path) if not source.exists(): raise FileNotFoundError(f"Source file not found: {source_path}") content = source.read_text(encoding="utf-8") filename = source.name return export_content(content, filename, channel) def main(): parser = argparse.ArgumentParser(description="Export markdown to Ulysses via iCloud") parser.add_argument("--content", help="Markdown content to export") parser.add_argument("--file", help="Path to existing markdown file") parser.add_argument("--filename", help="Output filename (auto-generated if not provided)") parser.add_argument("--channel", choices=list(CHANNEL_FOLDERS.keys()), default="default", help="Target channel/folder") parser.add_argument("--list-paths", action="store_true", help="List configured export paths") args = parser.parse_args() if args.list_paths: print("Configured export paths:") for channel, folder in CHANNEL_FOLDERS.items(): full_path = ICLOUD_BASE / folder exists = "✓" if full_path.exists() else "✗" print(f" [{exists}] {channel}: {full_path}") return if args.file: result = export_file(args.file, args.channel) print(f"✓ Exported to: {result}") elif args.content: filename = args.filename or generate_filename() result = export_content(args.content, filename, args.channel) print(f"✓ Exported to: {result}") else: parser.print_help() sys.exit(1) if __name__ == "__main__": main()