Files
our-claude-skills/ourdigital-custom-skills/31-ourdigital-research/code/scripts/export_to_ulysses.py
Andrew Yim eea49f9f8c refactor(skills): Restructure skills to dual-platform architecture
Major refactoring of ourdigital-custom-skills with new numbering system:

## Structure Changes
- Each skill now has code/ (Claude Code) and desktop/ (Claude Desktop) versions
- New progressive numbering: 01-09 General, 10-19 SEO, 20-29 GTM, 30-39 OurDigital, 40-49 Jamie

## Skill Reorganization
- 01-notion-organizer (from 02)
- 10-18: SEO tools split into focused skills (technical, on-page, local, schema, vitals, gsc, gateway)
- 20-21: GTM audit and manager
- 30-32: OurDigital designer, research, presentation
- 40-41: Jamie brand editor and audit

## New Files
- .claude/commands/: Slash command definitions for all skills
- CLAUDE.md: Updated with new skill structure documentation
- REFACTORING_PLAN.md: Migration documentation
- COMPATIBILITY_REPORT.md, SKILLS_COMPARISON.md: Analysis docs

## Removed
- Old skill directories (02-05, 10-14, 20-21 old numbering)
- Consolidated into new structure with _archive/ for reference

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-22 01:58:24 +09:00

114 lines
3.9 KiB
Python

#!/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()