Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

, stripped):\n if current_block:\n blocks.append('\\n'.join(current_block))\n current_block = []\n blocks.append('___DIVIDER___')\n continue\n\n # Headers, blockquotes are their own blocks\n if stripped.startswith(('#', '>')):\n if current_block:\n blocks.append('\\n'.join(current_block))\n current_block = []\n blocks.append(stripped)\n continue\n\n # Image on its own line is its own block\n if re.match(r'^!\\[.*\\]\\(.*\\)

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

, stripped):\n if current_block:\n blocks.append('\\n'.join(current_block))\n current_block = []\n blocks.append(stripped)\n continue\n\n current_block.append(line)\n\n if current_block:\n blocks.append('\\n'.join(current_block))\n\n # Handle unclosed code block\n if code_block_lines:\n blocks.append('___CODE_BLOCK_START___' + '\\n'.join(code_block_lines) + '___CODE_BLOCK_END___')\n\n return blocks\n\n\ndef extract_images_and_dividers(markdown: str, base_path: Path) -> tuple[list[dict], list[dict], str, int]:\n \"\"\"Extract images and dividers with their block index positions.\n\n Returns:\n (image_list, divider_list, markdown_without_images_and_dividers, total_blocks)\n \"\"\"\n blocks = split_into_blocks(markdown)\n images = []\n dividers = []\n clean_blocks = []\n\n img_pattern = re.compile(r'^!\\[([^\\]]*)\\]\\(([^)]+)\\)

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

)\n\n for i, block in enumerate(blocks):\n block_stripped = block.strip()\n\n # Check for divider\n if block_stripped == '___DIVIDER___':\n # block_index is the index in clean_blocks (without images/dividers)\n block_index = len(clean_blocks)\n\n # Get context from previous block for reference\n after_text = \"\"\n if clean_blocks:\n prev_block = clean_blocks[-1].strip()\n lines = [l for l in prev_block.split('\\n') if l.strip()]\n after_text = lines[-1][:80] if lines else \"\"\n\n dividers.append({\n \"block_index\": block_index,\n \"after_text\": after_text # Keep for reference/debugging\n })\n continue\n\n # Check for image\n match = img_pattern.match(block_stripped)\n if match:\n alt_text = match.group(1)\n img_path = match.group(2)\n\n # Resolve relative paths\n if not os.path.isabs(img_path):\n full_path = str(base_path / img_path)\n else:\n full_path = img_path\n\n # block_index is the index in clean_blocks (without images/dividers)\n block_index = len(clean_blocks)\n\n # Get context from previous block for reference\n after_text = \"\"\n if clean_blocks:\n prev_block = clean_blocks[-1].strip()\n lines = [l for l in prev_block.split('\\n') if l.strip()]\n after_text = lines[-1][:80] if lines else \"\"\n\n images.append({\n \"path\": full_path,\n \"alt\": alt_text,\n \"block_index\": block_index,\n \"after_text\": after_text # Keep for reference/debugging\n })\n continue\n\n # Regular block\n clean_blocks.append(block)\n\n clean_markdown = '\\n\\n'.join(clean_blocks)\n return images, dividers, clean_markdown, len(clean_blocks)\n\n\ndef extract_title(markdown: str) -> tuple[str, str]:\n \"\"\"Extract title from first H1, H2, or first non-empty line.\n\n Returns:\n (title, markdown_without_title): Title string and markdown with H1 title removed.\n If title is from H1, it's removed from markdown to avoid duplication.\n \"\"\"\n lines = markdown.strip().split('\\n')\n title = \"Untitled\"\n title_line_idx = None\n\n for idx, line in enumerate(lines):\n stripped = line.strip()\n if not stripped:\n continue\n # H1 - use as title and mark for removal\n if stripped.startswith('# '):\n title = stripped[2:].strip()\n title_line_idx = idx\n break\n # H2 - use as title but don't remove (it's a section header)\n if stripped.startswith('## '):\n title = stripped[3:].strip()\n break\n # First non-empty, non-image line\n if not stripped.startswith('!['):\n title = stripped[:100]\n break\n\n # Remove H1 title line from markdown to avoid duplication\n if title_line_idx is not None:\n lines.pop(title_line_idx)\n markdown = '\\n'.join(lines)\n\n return title, markdown\n\n\ndef markdown_to_html(markdown: str) -> str:\n \"\"\"Convert markdown to HTML for X Articles rich text paste.\"\"\"\n html = markdown\n\n # Process code blocks first (marked with ___CODE_BLOCK_START___ and ___CODE_BLOCK_END___)\n # Convert to blockquote format since X Articles doesn't support \u003cpre>\u003ccode>\n def convert_code_block(match):\n code_content = match.group(1)\n lines = code_content.strip().split('\\n')\n # Join non-empty lines with \u003cbr> for display\n formatted = '\u003cbr>'.join(line for line in lines if line.strip())\n return f'\u003cblockquote>{formatted}\u003c/blockquote>'\n\n html = re.sub(r'___CODE_BLOCK_START___(.*?)___CODE_BLOCK_END___', convert_code_block, html, flags=re.DOTALL)\n\n # Note: Dividers (---) are extracted separately and inserted via X Articles menu\n # They are NOT converted to \u003chr> tags since X Articles strips them\n\n # Headers (H2 only, H1 is title)\n html = re.sub(r'^## (.+)

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

, r'\u003ch2>\\1\u003c/h2>', html, flags=re.MULTILINE)\n html = re.sub(r'^### (.+)

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

, r'\u003ch3>\\1\u003c/h3>', html, flags=re.MULTILINE)\n\n # Bold\n html = re.sub(r'\\*\\*(.+?)\\*\\*', r'\u003cstrong>\\1\u003c/strong>', html)\n\n # Italic\n html = re.sub(r'\\*([^*]+)\\*', r'\u003cem>\\1\u003c/em>', html)\n\n # Links\n html = re.sub(r'\\[([^\\]]+)\\]\\(([^)]+)\\)', r'\u003ca href=\"\\2\">\\1\u003c/a>', html)\n\n # Blockquotes (regular markdown blockquotes, not code blocks)\n html = re.sub(r'^> (.+)

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

, r'\u003cblockquote>\\1\u003c/blockquote>', html, flags=re.MULTILINE)\n\n # Unordered lists\n html = re.sub(r'^- (.+)

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

, r'\u003cli>\\1\u003c/li>', html, flags=re.MULTILINE)\n\n # Ordered lists\n html = re.sub(r'^\\d+\\. (.+)

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

, r'\u003cli>\\1\u003c/li>', html, flags=re.MULTILINE)\n\n # Wrap consecutive \u003cli> in \u003cul>\n html = re.sub(r'((?:\u003cli>.*?\u003c/li>\\n?)+)', r'\u003cul>\\1\u003c/ul>', html)\n\n # Paragraphs - split by double newlines\n parts = html.split('\\n\\n')\n processed_parts = []\n\n for part in parts:\n part = part.strip()\n if not part:\n continue\n # Skip if already a block element\n if part.startswith(('\u003ch2>', '\u003ch3>', '\u003cblockquote>', '\u003cul>', '\u003col>')):\n processed_parts.append(part)\n else:\n # Wrap in paragraph, convert single newlines to \u003cbr>\n part = part.replace('\\n', '\u003cbr>')\n processed_parts.append(f'\u003cp>{part}\u003c/p>')\n\n return ''.join(processed_parts)\n\n\ndef parse_markdown_file(filepath: str) -> dict:\n \"\"\"Parse a markdown file and return structured data.\"\"\"\n path = Path(filepath)\n base_path = path.parent\n\n with open(filepath, 'r', encoding='utf-8') as f:\n content = f.read()\n\n # Skip YAML frontmatter if present\n if content.startswith('---'):\n end_marker = content.find('---', 3)\n if end_marker != -1:\n content = content[end_marker + 3:].strip()\n\n # Extract title first (and remove H1 from markdown)\n title, content = extract_title(content)\n\n # Extract images and dividers with block indices\n images, dividers, clean_markdown, total_blocks = extract_images_and_dividers(content, base_path)\n\n # Convert to HTML\n html = markdown_to_html(clean_markdown)\n\n # Separate cover image from content images\n cover_image = images[0][\"path\"] if images else None\n content_images = images[1:] if len(images) > 1 else []\n\n return {\n \"title\": title,\n \"cover_image\": cover_image,\n \"content_images\": content_images,\n \"dividers\": dividers,\n \"html\": html,\n \"total_blocks\": total_blocks,\n \"source_file\": str(path.absolute())\n }\n\n\ndef main():\n parser = argparse.ArgumentParser(description='Parse Markdown for X Articles')\n parser.add_argument('file', help='Markdown file to parse')\n parser.add_argument('--output', choices=['json', 'html'], default='json',\n help='Output format (default: json)')\n parser.add_argument('--html-only', action='store_true',\n help='Output only HTML content')\n\n args = parser.parse_args()\n\n if not os.path.exists(args.file):\n print(f\"Error: File not found: {args.file}\", file=sys.stderr)\n sys.exit(1)\n\n result = parse_markdown_file(args.file)\n\n if args.html_only:\n print(result['html'])\n elif args.output == 'json':\n print(json.dumps(result, ensure_ascii=False, indent=2))\n else:\n print(result['html'])\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":11728,"content_sha256":"01ef0a6577c760acd259c8d6753cfe00cb3692942b93a30d162076476cadd79d"},{"filename":"scripts/table_to_image.py","content":"#!/usr/bin/env python3\n\"\"\"\nConvert Markdown table to PNG image.\nUsage: python3 table_to_image.py \u003cinput.md> \u003coutput.png> [--scale 2]\n\"\"\"\n\nimport sys\nimport re\nfrom pathlib import Path\n\ntry:\n from PIL import Image, ImageDraw, ImageFont\nexcept ImportError:\n print(\"Error: Pillow not installed. Run: pip install pillow\")\n sys.exit(1)\n\n\ndef parse_markdown_table(content: str) -> tuple[list[str], list[list[str]], list[str]]:\n \"\"\"Parse markdown table into headers, rows, and alignments.\"\"\"\n lines = [line.strip() for line in content.strip().split('\\n') if line.strip()]\n\n if len(lines) \u003c 2:\n raise ValueError(\"Table must have at least 2 rows\")\n\n def parse_cells(line: str) -> list[str]:\n line = line.strip()\n if line.startswith('|'):\n line = line[1:]\n if line.endswith('|'):\n line = line[:-1]\n return [cell.strip() for cell in line.split('|')]\n\n # Find separator line\n separator_idx = -1\n for i, line in enumerate(lines):\n if re.match(r'^\\|?[\\s]*:?-{2,}:?[\\s]*(\\|[\\s]*:?-{2,}:?[\\s]*)*\\|?

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…

, line):\n separator_idx = i\n break\n\n if separator_idx > 0:\n # Has headers\n headers = parse_cells(lines[separator_idx - 1])\n sep_cells = parse_cells(lines[separator_idx])\n alignments = []\n for cell in sep_cells:\n cell = cell.strip()\n if cell.startswith(':') and cell.endswith(':'):\n alignments.append('center')\n elif cell.endswith(':'):\n alignments.append('right')\n else:\n alignments.append('left')\n rows = [parse_cells(line) for line in lines[separator_idx + 1:]]\n else:\n # No headers - all data rows\n headers = []\n rows = [parse_cells(line) for line in lines]\n alignments = ['left'] * (len(rows[0]) if rows else 0)\n\n return headers, rows, alignments\n\n\ndef get_font(size: int, bold: bool = False):\n \"\"\"Get a font, falling back to default if system fonts unavailable.\"\"\"\n font_paths = [\n # macOS\n \"/System/Library/Fonts/SFNSMono.ttf\",\n \"/System/Library/Fonts/Helvetica.ttc\",\n \"/Library/Fonts/Arial.ttf\",\n # Linux\n \"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\",\n \"/usr/share/fonts/TTF/DejaVuSans.ttf\",\n # Windows\n \"C:/Windows/Fonts/arial.ttf\",\n ]\n\n for path in font_paths:\n try:\n return ImageFont.truetype(path, size)\n except (OSError, IOError):\n continue\n\n # Fallback to default\n return ImageFont.load_default()\n\n\ndef render_table_to_image(\n headers: list[str],\n rows: list[list[str]],\n alignments: list[str],\n scale: int = 2\n) -> Image.Image:\n \"\"\"Render table data to a PIL Image.\"\"\"\n\n # Configuration\n base_font_size = 14\n font_size = base_font_size * scale\n padding_x = 16 * scale\n padding_y = 12 * scale\n border_radius = 8 * scale\n margin = 20 * scale\n\n # Colors\n bg_color = (255, 255, 255)\n header_bg = (249, 250, 251)\n text_color = (55, 65, 81)\n header_text_color = (17, 24, 39)\n border_color = (229, 231, 235)\n\n # Fonts\n regular_font = get_font(font_size)\n bold_font = get_font(font_size, bold=True)\n\n # Calculate column widths\n col_count = len(headers) if headers else (len(rows[0]) if rows else 0)\n col_widths = [0] * col_count\n\n # Create temp image for text measurement\n temp_img = Image.new('RGB', (1, 1))\n temp_draw = ImageDraw.Draw(temp_img)\n\n # Measure headers\n for i, header in enumerate(headers):\n bbox = temp_draw.textbbox((0, 0), header, font=bold_font)\n col_widths[i] = max(col_widths[i], bbox[2] - bbox[0])\n\n # Measure data cells\n for row in rows:\n for i, cell in enumerate(row):\n if i \u003c col_count:\n bbox = temp_draw.textbbox((0, 0), cell, font=regular_font)\n col_widths[i] = max(col_widths[i], bbox[2] - bbox[0])\n\n # Add padding to widths\n col_widths = [w + padding_x * 2 for w in col_widths]\n\n # Calculate dimensions\n row_height = font_size + padding_y * 2\n header_height = row_height if headers else 0\n table_width = sum(col_widths)\n table_height = header_height + len(rows) * row_height\n\n img_width = table_width + margin * 2\n img_height = table_height + margin * 2\n\n # Create image\n img = Image.new('RGB', (img_width, img_height), bg_color)\n draw = ImageDraw.Draw(img)\n\n # Draw table border (rounded rectangle)\n x0, y0 = margin, margin\n x1, y1 = margin + table_width, margin + table_height\n draw.rounded_rectangle([x0, y0, x1, y1], radius=border_radius, outline=border_color, width=scale)\n\n y = margin\n\n # Draw header\n if headers:\n # Header background\n draw.rounded_rectangle(\n [x0, y0, x1, y0 + row_height],\n radius=border_radius,\n fill=header_bg\n )\n # Cover bottom corners of header (they should be square)\n draw.rectangle([x0, y0 + row_height - border_radius, x1, y0 + row_height], fill=header_bg)\n\n # Header border\n draw.line([(x0, y + row_height), (x1, y + row_height)], fill=border_color, width=scale)\n\n # Header text\n x = margin\n for i, header in enumerate(headers):\n bbox = draw.textbbox((0, 0), header, font=bold_font)\n text_width = bbox[2] - bbox[0]\n\n if alignments[i] == 'center':\n text_x = x + (col_widths[i] - text_width) // 2\n elif alignments[i] == 'right':\n text_x = x + col_widths[i] - text_width - padding_x\n else:\n text_x = x + padding_x\n\n text_y = y + padding_y\n draw.text((text_x, text_y), header, fill=header_text_color, font=bold_font)\n x += col_widths[i]\n\n y += row_height\n\n # Draw data rows\n for row_idx, row in enumerate(rows):\n # Row border (except last row)\n if row_idx \u003c len(rows) - 1:\n draw.line([(x0, y + row_height), (x1, y + row_height)], fill=border_color, width=scale)\n\n # Cell text\n x = margin\n for i, cell in enumerate(row):\n if i >= col_count:\n break\n\n bbox = draw.textbbox((0, 0), cell, font=regular_font)\n text_width = bbox[2] - bbox[0]\n\n if i \u003c len(alignments):\n if alignments[i] == 'center':\n text_x = x + (col_widths[i] - text_width) // 2\n elif alignments[i] == 'right':\n text_x = x + col_widths[i] - text_width - padding_x\n else:\n text_x = x + padding_x\n else:\n text_x = x + padding_x\n\n text_y = y + padding_y\n draw.text((text_x, text_y), cell, fill=text_color, font=regular_font)\n x += col_widths[i]\n\n y += row_height\n\n return img\n\n\ndef main():\n if len(sys.argv) \u003c 3:\n print(\"Usage: python3 table_to_image.py \u003cinput.md> \u003coutput.png> [--scale N]\")\n sys.exit(1)\n\n input_path = sys.argv[1]\n output_path = sys.argv[2]\n\n # Parse optional scale argument\n scale = 2\n if '--scale' in sys.argv:\n scale_idx = sys.argv.index('--scale')\n if scale_idx + 1 \u003c len(sys.argv):\n try:\n scale = int(sys.argv[scale_idx + 1])\n except ValueError:\n pass\n\n # Read input\n content = Path(input_path).read_text()\n\n # Parse table\n try:\n headers, rows, alignments = parse_markdown_table(content)\n except Exception as e:\n print(f\"Error parsing table: {e}\")\n sys.exit(1)\n\n if not rows:\n print(\"Error: No data rows found in table\")\n sys.exit(1)\n\n # Render image\n img = render_table_to_image(headers, rows, alignments, scale)\n\n # Save\n output = Path(output_path)\n img.save(output, 'PNG')\n print(f\"Saved: {output} ({img.width}x{img.height})\")\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":8011,"content_sha256":"cf73c3e96d4930f80c658d36937f0dda81c0bd215ddf8660b0f877243440f523"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Publish X Article","type":"text"}]},{"type":"paragraph","content":[{"text":"Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Credits","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill is inspired by and based on ","type":"text"},{"text":"wshuyi/x-article-publisher-skill","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/wshuyi/x-article-publisher-skill","title":null}}]},{"text":". Thank you to the original author for the foundational work.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Interactive Setup: Ask Subscription Type","type":"text"}]},{"type":"paragraph","content":[{"text":"IMPORTANT","type":"text","marks":[{"type":"strong"}]},{"text":": Before processing the article, ask the user about their X subscription type if not already known.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Prompt the User","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Before publishing, I need to know your X subscription type to handle formatting correctly:\n\n1. **X Premium** - Basic tier ($8/month)\n2. **X Premium+** - Plus tier ($16/month)\n\nWhich subscription do you have? (Premium / Premium+)","type":"text"}]},{"type":"paragraph","content":[{"text":"For Chinese users:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"在发布之前,我需要了解您的 X 订阅类型以正确处理格式:\n\n1. **X Premium** - 基础版 ($8/月)\n2. **X Premium+** - 高级版 ($16/月)\n\n您使用的是哪个版本?(Premium / Premium+)","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Remember the Answer","type":"text"}]},{"type":"paragraph","content":[{"text":"Once the user answers, remember their subscription type for the rest of the session. Don't ask again unless they explicitly want to change it.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"X Subscription Feature Comparison","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Feature","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"X Premium","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"X Premium+","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"H1 headers (","type":"text"},{"text":"#","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Title only","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Title only","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"H2 headers (","type":"text"},{"text":"##","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"H3+ headers (","type":"text"},{"text":"###","type":"text","marks":[{"type":"code_inline"}]},{"text":", etc.)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Markdown tables","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mermaid diagrams","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No","type":"text","marks":[{"type":"strong"}]},{"text":" (not supported by X)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Code blocks","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Blockquotes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Blockquotes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bold, italic, links","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lists","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Blockquotes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Images","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Yes","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pre-Processing Required by Subscription","type":"text"}]},{"type":"paragraph","content":[{"text":"X Premium (Basic):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Convert H3+ headers → H2 or bold","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Convert tables → PNG images","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Convert mermaid → PNG images","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"X Premium+ (Plus):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep H3+ headers as-is","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep tables as-is (rendered natively)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Convert mermaid → PNG images (still not supported)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Playwright MCP for browser automation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User logged into X with Premium subscription","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Python 3.9+ with dependencies:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"macOS: ","type":"text"},{"text":"pip install Pillow pyobjc-framework-Cocoa markdown","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Windows: ","type":"text"},{"text":"pip install Pillow pywin32 clip-util markdown","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"diagram-to-image skill (for converting tables/mermaid to PNG via diagramless.xyz API)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scripts","type":"text"}]},{"type":"paragraph","content":[{"text":"Located in ","type":"text"},{"text":"~/.claude/skills/publish-x-article/scripts/","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"parse_markdown.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Parse Markdown and extract structured data:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python parse_markdown.py \u003cmarkdown_file> [--output json|html] [--html-only]","type":"text"}]},{"type":"paragraph","content":[{"text":"Returns JSON with: title, cover_image, content_images (with block_index for positioning), html, total_blocks","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"copy_to_clipboard.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Copy image or HTML to system clipboard:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Copy image (with optional compression)\npython copy_to_clipboard.py image /path/to/image.jpg [--quality 80]\n\n# Copy HTML for rich text paste\npython copy_to_clipboard.py html --file /path/to/content.html","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"diagram-to-image.mjs (from diagram-to-image skill)","type":"text"}]},{"type":"paragraph","content":[{"text":"Convert Mermaid diagrams and Markdown tables to PNG images via diagramless.xyz API:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table.md -o /tmp/table.png\nnode ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/diagram.mmd -o /tmp/diagram.png --theme ocean","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pre-Processing: Handle Unsupported Elements","type":"text"}]},{"type":"paragraph","content":[{"text":"Before","type":"text","marks":[{"type":"strong"}]},{"text":" publishing, scan the Markdown for unsupported elements and convert them to images.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 0: Analyze Content for Limitations","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Check the markdown file for unsupported elements\ncat /path/to/article.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Look for:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Deep headers (H3+)","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"###","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"####","type":"text","marks":[{"type":"code_inline"}]},{"text":", etc.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Markdown tables","type":"text","marks":[{"type":"strong"}]},{"text":": Lines with ","type":"text"},{"text":"|","type":"text","marks":[{"type":"code_inline"}]},{"text":" characters forming table structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mermaid code blocks","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":" ```mermaid","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Converting Unsupported Elements to Images","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"1. Markdown Tables → PNG","type":"text"}]},{"type":"paragraph","content":[{"text":"When a table is detected:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. Extract table to temp file\ncat > /tmp/table.md \u003c\u003c 'TABLE_EOF'\n| Column 1 | Column 2 | Column 3 |\n|----------|----------|----------|\n| Data 1 | Data 2 | Data 3 |\nTABLE_EOF\n\n# 2. Convert to image via diagramless.xyz API\nnode ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table.md -o /tmp/table-001.png\n\n# 3. Replace table in markdown with image reference\n# ![Table description](/tmp/table-001.png)","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"2. Mermaid Diagrams → PNG","type":"text"}]},{"type":"paragraph","content":[{"text":"When a mermaid block is detected:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. Extract mermaid to temp file\ncat > /tmp/diagram.mmd \u003c\u003c 'MERMAID_EOF'\nflowchart TD\n A[Start] --> B[Process]\n B --> C[End]\nMERMAID_EOF\n\n# 2. Convert to image via diagramless.xyz API\nnode ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/diagram.mmd -o /tmp/diagram-001.png\n\n# 3. Replace mermaid block in markdown with image reference\n# ![Diagram description](/tmp/diagram-001.png)","type":"text"}]},{"type":"heading","attrs":{"level":4},"content":[{"text":"3. Deep Headers (H3+) → Simplified Structure","type":"text"}]},{"type":"paragraph","content":[{"text":"Goal:","type":"text","marks":[{"type":"strong"}]},{"text":" Preserve the article's logical structure and readability while working within X Premium's H2-only limitation.","type":"text"}]},{"type":"paragraph","content":[{"text":"Guidelines for the AI:","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"When you encounter H3, H4, or deeper headers in a Premium user's article, think about what the author intended:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If the header introduces a distinct subtopic","type":"text","marks":[{"type":"strong"}]},{"text":" under a section, convert it to ","type":"text"},{"text":"bold text","type":"text","marks":[{"type":"strong"}]},{"text":" as a paragraph opener. This maintains visual hierarchy without breaking X's formatting.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If the header is a major section that happens to be H3","type":"text","marks":[{"type":"strong"}]},{"text":", consider promoting it to H2 — but only if this doesn't create a flat, meaningless structure. The article should still flow logically.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If there's a deep hierarchy","type":"text","marks":[{"type":"strong"}]},{"text":" (H2 → H3 → H4), flatten thoughtfully:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep H2 as H2","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Convert H3 to ","type":"text"},{"text":"bold paragraph","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Convert H4 to ","type":"text"},{"text":"italic","type":"text","marks":[{"type":"em"}]},{"text":" or just merge into the paragraph naturally","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Preserve meaning over structure.","type":"text","marks":[{"type":"strong"}]},{"text":" A header like ","type":"text"},{"text":"### Why This Matters","type":"text","marks":[{"type":"code_inline"}]},{"text":" might become a bold lead-in: ","type":"text"},{"text":"**Why does this matter?**","type":"text","marks":[{"type":"code_inline"}]},{"text":" followed by the content. Use your judgment.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read the content.","type":"text","marks":[{"type":"strong"}]},{"text":" If an H3 header is just \"Example\" or \"Note\", it might work better as a blockquote or inline emphasis rather than a standalone bold line.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"The goal is not mechanical conversion — it's creating an article that reads well on X while honoring the author's intent.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pre-Processing Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"Before publishing, read through the article and prepare it for X:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Understand the article's structure","type":"text","marks":[{"type":"strong"}]},{"text":" — Read it first. What are the main sections? How does the author use headers to organize ideas?","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Handle tables and mermaid diagrams","type":"text","marks":[{"type":"strong"}]},{"text":" — These need to become images. Extract each one, convert to PNG, and note where they should be inserted.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Adapt headers for the subscription tier:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For ","type":"text"},{"text":"Premium+ users","type":"text","marks":[{"type":"strong"}]},{"text":": Keep headers as-is (H3+ supported)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For ","type":"text"},{"text":"Premium users","type":"text","marks":[{"type":"strong"}]},{"text":": Thoughtfully restructure H3+ headers while preserving the article's flow and intent (see guidelines above)","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create the modified markdown","type":"text","marks":[{"type":"strong"}]},{"text":" — Save your adapted version to a temp file, ready for parsing.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"The goal is an article that reads naturally on X, not a mechanically transformed document.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Main Workflow","type":"text"}]},{"type":"paragraph","content":[{"text":"Strategy: \"先文后图后分割线\" (Text First, Images Second, Dividers Last)","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"For articles with images and dividers, paste ALL text content first, then insert images and dividers at correct positions using block index.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pre-process","type":"text","marks":[{"type":"strong"}]},{"text":": Convert tables/mermaid to images, flatten deep headers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parse modified Markdown with Python script → get title, images, dividers (all with block_index), HTML","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Navigate to X Articles editor","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Upload cover image (first image)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fill title","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Copy HTML to clipboard (Python) → Paste with Cmd+V","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Insert content images at positions specified by block_index","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Insert dividers at positions specified by block_index","type":"text","marks":[{"type":"strong"}]},{"text":" (via Insert > Divider menu)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Save as draft (NEVER auto-publish)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"高效执行原则 (Efficiency Guidelines)","type":"text"}]},{"type":"paragraph","content":[{"text":"目标","type":"text","marks":[{"type":"strong"}]},{"text":": 最小化操作之间的等待时间,实现流畅的自动化体验。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. 避免不必要的 browser_snapshot","type":"text"}]},{"type":"paragraph","content":[{"text":"大多数浏览器操作(click, type, press_key 等)都会在返回结果中包含页面状态。","type":"text"},{"text":"不要","type":"text","marks":[{"type":"strong"}]},{"text":"在每次操作后单独调用 ","type":"text"},{"text":"browser_snapshot","type":"text","marks":[{"type":"code_inline"}]},{"text":",直接使用操作返回的页面状态即可。","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"❌ 错误做法:\nbrowser_click → browser_snapshot → 分析 → browser_click → browser_snapshot → ...\n\n✅ 正确做法:\nbrowser_click → 从返回结果中获取页面状态 → browser_click → ...","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. 避免不必要的 browser_wait_for","type":"text"}]},{"type":"paragraph","content":[{"text":"只在以下情况使用 ","type":"text"},{"text":"browser_wait_for","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"等待图片上传完成(","type":"text"},{"text":"textGone=\"正在上传媒体\"","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"等待页面初始加载(极少数情况)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"不要","type":"text","marks":[{"type":"strong"}]},{"text":"使用 ","type":"text"},{"text":"browser_wait_for","type":"text","marks":[{"type":"code_inline"}]},{"text":" 来等待按钮或输入框出现 - 它们在页面加载完成后立即可用。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. 并行执行独立操作","type":"text"}]},{"type":"paragraph","content":[{"text":"当两个操作没有依赖关系时,可以在同一个消息中并行调用多个工具:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"✅ 可以并行:\n- 填写标题 (browser_type) + 复制HTML到剪贴板 (Bash)\n- 解析Markdown生成JSON + 生成HTML文件\n\n❌ 不能并行(有依赖):\n- 必须先点击create才能上传封面图\n- 必须先粘贴内容才能插入图片","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. 连续执行浏览器操作","type":"text"}]},{"type":"paragraph","content":[{"text":"每个浏览器操作返回的页面状态包含所有需要的元素引用。直接使用这些引用进行下一步操作:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"# 理想流程(每步直接执行,不额外等待):\nbrowser_navigate → 从返回状态找create按钮 → browser_click(create)\n→ 从返回状态找上传按钮 → browser_click(上传) → browser_file_upload\n→ 从返回状态找应用按钮 → browser_click(应用)\n→ 从返回状态找标题框 → browser_type(标题)\n→ 点击编辑器 → browser_press_key(Meta+v)\n→ ...","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. 准备工作前置","type":"text"}]},{"type":"paragraph","content":[{"text":"在开始浏览器操作之前,先完成所有准备工作:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"扫描不支持的元素(表格、Mermaid、深层标题)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"转换表格/Mermaid 为图片","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"解析 Markdown 获取 JSON 数据","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"生成 HTML 文件到 /tmp/","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"记录 title、cover_image、content_images 等信息","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"这样浏览器操作阶段可以连续执行,不需要中途停下来处理数据。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 0: Read and Adapt the Article","type":"text"}]},{"type":"paragraph","content":[{"text":"First, read the article.","type":"text","marks":[{"type":"strong"}]},{"text":" Understand what it's about, how it's structured, and what the author is trying to communicate.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Preserve the Original","type":"text"}]},{"type":"paragraph","content":[{"text":"IMPORTANT:","type":"text","marks":[{"type":"strong"}]},{"text":" Never modify the user's original file. If adaptations are needed:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Save the adapted version as a copy (e.g., ","type":"text"},{"text":"/tmp/article_adapted.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" or alongside the original as ","type":"text"},{"text":"article_for_x.md","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tell the user what was changed and where both files are:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"I've adapted your article for X Premium. Here's what changed:\n- Converted 2 tables to images\n- Restructured 3 H3 headers to bold text for better flow\n\nOriginal preserved: /path/to/article.md\nAdapted version: /path/to/article_for_x.md","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Proceed with the adapted copy for publishing","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"This ensures the user can review the changes and keeps their original work intact.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Adaptation Based on Subscription","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"For Premium Users (Basic Tier)","type":"text"}]},{"type":"paragraph","content":[{"text":"Ask yourself:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Are there tables? → Convert each to a PNG image","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Are there mermaid diagrams? → Convert each to a PNG image","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Are there H3+ headers? → Restructure them thoughtfully (see header guidelines above)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Create a modified version of the markdown that will work within Premium's limitations while preserving readability.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"For Premium+ Users","type":"text"}]},{"type":"paragraph","content":[{"text":"Ask yourself:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Are there mermaid diagrams? → Convert to PNG (still not supported on any tier)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tables and H3+ headers can stay as-is","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Converting Elements to Images","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Tables → PNG (auto-detected as table)\nnode ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table-1.md -o /tmp/table-1.png\n\n# Mermaid → PNG (auto-detected as mermaid)\nnode ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/diagram-1.mmd -o /tmp/diagram-1.png","type":"text"}]},{"type":"paragraph","content":[{"text":"Replace these elements in the markdown with image references, positioning them where the original element was.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 1: Parse Markdown (Python)","type":"text"}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"parse_markdown.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" to extract all structured data:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python ~/.claude/skills/publish-x-article/scripts/parse_markdown.py /path/to/modified_article.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Output JSON:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"title\": \"Article Title\",\n \"cover_image\": \"/path/to/first-image.jpg\",\n \"content_images\": [\n {\"path\": \"/tmp/table-1.png\", \"block_index\": 5, \"after_text\": \"context...\"},\n {\"path\": \"/tmp/mermaid-1.png\", \"block_index\": 12, \"after_text\": \"another context...\"}\n ],\n \"dividers\": [\n {\"block_index\": 7, \"after_text\": \"context before divider...\"},\n {\"block_index\": 15, \"after_text\": \"another context...\"}\n ],\n \"html\": \"\u003cp>Content...\u003c/p>\u003ch2>Section\u003c/h2>...\",\n \"total_blocks\": 45\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Key fields:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"block_index","type":"text","marks":[{"type":"code_inline"}]},{"text":": The image/divider should be inserted AFTER block element at this index (0-indexed)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"total_blocks","type":"text","marks":[{"type":"code_inline"}]},{"text":": Total number of block elements in the HTML","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"after_text","type":"text","marks":[{"type":"code_inline"}]},{"text":": Kept for reference/debugging only, NOT for positioning","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"dividers","type":"text","marks":[{"type":"code_inline"}]},{"text":": Array of divider positions (markdown ","type":"text"},{"text":"---","type":"text","marks":[{"type":"code_inline"}]},{"text":" must be inserted via X's menu, not HTML ","type":"text"},{"text":"\u003chr>","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Save HTML to temp file for clipboard:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python parse_markdown.py modified_article.md --html-only > /tmp/article_html.html","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 2: Open X Articles Editor","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"browser_navigate: https://x.com/compose/articles","type":"text"}]},{"type":"paragraph","content":[{"text":"重要","type":"text","marks":[{"type":"strong"}]},{"text":": 页面加载后会显示草稿列表,不是编辑器。需要:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"等待页面加载完成","type":"text","marks":[{"type":"strong"}]},{"text":": 使用 ","type":"text"},{"text":"browser_snapshot","type":"text","marks":[{"type":"code_inline"}]},{"text":" 检查页面状态","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"立即点击 \"create\" 按钮","type":"text","marks":[{"type":"strong"}]},{"text":": 不要等待 \"添加标题\" 等编辑器元素,它们只有点击 create 后才出现","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"等待编辑器加载","type":"text","marks":[{"type":"strong"}]},{"text":": 点击 create 后,等待编辑器元素出现","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"# 1. 导航到页面\nbrowser_navigate: https://x.com/compose/articles\n\n# 2. 获取页面快照,找到 create 按钮\nbrowser_snapshot\n\n# 3. 点击 create 按钮(通常 ref 类似 \"create\" 或带有 create 标签)\nbrowser_click: element=\"create button\", ref=\u003ccreate_button_ref>\n\n# 4. 现在编辑器应该打开了,可以继续上传封面图等操作","type":"text"}]},{"type":"paragraph","content":[{"text":"注意","type":"text","marks":[{"type":"strong"}]},{"text":": 不要使用 ","type":"text"},{"text":"browser_wait_for text=\"添加标题\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" 来等待页面加载,因为这个文本只有在点击 create 后才出现,会导致超时。","type":"text"}]},{"type":"paragraph","content":[{"text":"If login needed, prompt user to log in manually.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 3: Upload Cover Image","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click \"添加照片或视频\" button","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use browser_file_upload with the cover image path (from JSON output)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify image uploaded","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 4: Fill Title","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Find textbox with \"添加标题\" placeholder","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use browser_type to input title (from JSON output)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 5: Paste Text Content (Python Clipboard)","type":"text"}]},{"type":"paragraph","content":[{"text":"Copy HTML to system clipboard using Python, then paste:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Copy HTML to clipboard\npython ~/.claude/skills/publish-x-article/scripts/copy_to_clipboard.py html --file /tmp/article_html.html","type":"text"}]},{"type":"paragraph","content":[{"text":"Then in browser:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"browser_click on editor textbox\nbrowser_press_key: Meta+v","type":"text"}]},{"type":"paragraph","content":[{"text":"This preserves all rich text formatting (H2, bold, links, lists).","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 6: Insert Content Images (Block Index Positioning)","type":"text"}]},{"type":"paragraph","content":[{"text":"关键改进","type":"text","marks":[{"type":"strong"}]},{"text":": 使用 ","type":"text"},{"text":"block_index","type":"text","marks":[{"type":"code_inline"}]},{"text":" 精确定位,而非依赖文字匹配。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"定位原理","type":"text"}]},{"type":"paragraph","content":[{"text":"粘贴 HTML 后,编辑器中的内容结构为一系列块元素(段落、标题、引用等)。每张图片的 ","type":"text"},{"text":"block_index","type":"text","marks":[{"type":"code_inline"}]},{"text":" 表示它应该插入在第 N 个块元素之后。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"操作步骤","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"获取所有块元素","type":"text","marks":[{"type":"strong"}]},{"text":": 使用 browser_snapshot 获取编辑器内容,找到 textbox 下的所有子元素","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"按索引定位","type":"text","marks":[{"type":"strong"}]},{"text":": 根据 ","type":"text"},{"text":"block_index","type":"text","marks":[{"type":"code_inline"}]},{"text":" 点击对应的块元素","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"粘贴图片","type":"text","marks":[{"type":"strong"}]},{"text":": 复制图片到剪贴板后粘贴","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"For each content image (from ","type":"text"},{"text":"content_images","type":"text","marks":[{"type":"code_inline"}]},{"text":" array):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# 1. Copy image to clipboard (with compression)\npython ~/.claude/skills/publish-x-article/scripts/copy_to_clipboard.py image /path/to/img.jpg --quality 85","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"# 2. Click the block element at block_index\n# Example: if block_index=5, click the 6th block element (0-indexed)\nbrowser_click on the element at position block_index in the editor\n\n# 3. Paste image\nbrowser_press_key: Meta+v\n\n# 4. Wait for upload (use short time, returns immediately when done)\nbrowser_wait_for textGone=\"正在上传媒体\" time=2","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"反向插入","type":"text"}]},{"type":"paragraph","content":[{"text":"注意","type":"text","marks":[{"type":"strong"}]},{"text":": 每插入一张图片后,后续图片的实际位置会偏移。建议按 ","type":"text"},{"text":"block_index","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"从大到小","type":"text","marks":[{"type":"strong"}]},{"text":"的顺序插入图片。","type":"text"}]},{"type":"paragraph","content":[{"text":"如果有3张图片,block_index 分别为 5, 12, 27:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"先插入 block_index=27 的图片","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"再插入 block_index=12 的图片","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"最后插入 block_index=5 的图片","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 6.5: Insert Dividers (Via Menu)","type":"text"}]},{"type":"paragraph","content":[{"text":"重要","type":"text","marks":[{"type":"strong"}]},{"text":": Markdown 中的 ","type":"text"},{"text":"---","type":"text","marks":[{"type":"code_inline"}]},{"text":" 分割线不能通过 HTML ","type":"text"},{"text":"\u003chr>","type":"text","marks":[{"type":"code_inline"}]},{"text":" 标签粘贴(X Articles 会忽略它)。必须通过 X Articles 的 Insert 菜单插入。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"为什么需要特殊处理","type":"text"}]},{"type":"paragraph","content":[{"text":"X Articles 有自己的原生分割线元素,只能通过 Insert > Divider 菜单插入。HTML ","type":"text"},{"text":"\u003chr>","type":"text","marks":[{"type":"code_inline"}]},{"text":" 标签会被完全忽略。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"操作步骤","type":"text"}]},{"type":"paragraph","content":[{"text":"For each divider (from ","type":"text"},{"text":"dividers","type":"text","marks":[{"type":"code_inline"}]},{"text":" array), in ","type":"text"},{"text":"reverse order of block_index","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"# 1. Click the block element at block_index position\nbrowser_click on the element at position block_index in the editor\n\n# 2. Open Insert menu\nbrowser_click on \"Insert\" button (Add Media button)\n\n# 3. Click Divider menu item\nbrowser_click on \"Divider\" menuitem\n\n# Divider is inserted at cursor position","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"反向插入","type":"text"}]},{"type":"paragraph","content":[{"text":"和图片一样,按 ","type":"text"},{"text":"block_index","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"从大到小","type":"text","marks":[{"type":"strong"}]},{"text":"的顺序插入分割线,避免位置偏移问题。","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"与图片的插入顺序","type":"text"}]},{"type":"paragraph","content":[{"text":"建议先插入所有图片,再插入所有分割线。两者都按 block_index 从大到小的顺序:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"插入所有图片(从最大 block_index 开始)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"插入所有分割线(从最大 block_index 开始)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 7: Save Draft","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify content pasted (check word count indicator)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Draft auto-saves, or click Save button if needed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click \"预览\" to verify formatting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Report: \"Draft saved. Review and publish manually.\"","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Critical Rules","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"NEVER publish","type":"text","marks":[{"type":"strong"}]},{"text":" - Only save draft","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pre-process first","type":"text","marks":[{"type":"strong"}]},{"text":" - Convert tables/mermaid/deep headers before parsing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"First image = cover","type":"text","marks":[{"type":"strong"}]},{"text":" - Upload first image as cover image","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rich text conversion","type":"text","marks":[{"type":"strong"}]},{"text":" - Always convert Markdown to HTML before pasting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use clipboard API","type":"text","marks":[{"type":"strong"}]},{"text":" - Paste via clipboard for proper formatting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Block index positioning","type":"text","marks":[{"type":"strong"}]},{"text":" - Use block_index for precise image/divider placement","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reverse order insertion","type":"text","marks":[{"type":"strong"}]},{"text":" - Insert images and dividers from highest to lowest block_index","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"H1 title handling","type":"text","marks":[{"type":"strong"}]},{"text":" - H1 is used as title only, not included in body","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dividers via menu","type":"text","marks":[{"type":"strong"}]},{"text":" - Markdown ","type":"text"},{"text":"---","type":"text","marks":[{"type":"code_inline"}]},{"text":" must be inserted via Insert > Divider menu (HTML ","type":"text"},{"text":"\u003chr>","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ignored)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Supported Formatting (After Pre-Processing)","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Element","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Support","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"H2 (","type":"text"},{"text":"##","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Section headers","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bold (","type":"text"},{"text":"**","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Strong emphasis","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Italic (","type":"text"},{"text":"*","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Emphasis","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Links (","type":"text"},{"text":"[](url)","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hyperlinks","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Ordered lists","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"1. 2. 3.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Unordered lists","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"- bullets","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Blockquotes (","type":"text"},{"text":">","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quoted text","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Code blocks","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Converted","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"→ Blockquotes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tables","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Converted","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"→ PNG images","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mermaid","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Converted","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"→ PNG images","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"H3+ headers","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Converted","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"→ H2 or bold","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dividers (","type":"text"},{"text":"---","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Menu insert","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"→ Insert > Divider","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Example Flow","type":"text"}]},{"type":"paragraph","content":[{"text":"User: \"Publish /path/to/article.md to X\"","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Step 0: Analyze content\n# Found: 1 table, 1 mermaid diagram, 2 H3 headers\n\n# Step 0.1: Convert table to image\nnode ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table-1.md -o /tmp/table-1.png\n\n# Step 0.2: Convert mermaid to image\nnode ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/mermaid-1.mmd -o /tmp/mermaid-1.png\n\n# Step 0.3: Create modified markdown with image refs and flattened headers\n# Save to /tmp/article_modified.md\n\n# Step 1: Parse modified markdown\npython ~/.claude/skills/publish-x-article/scripts/parse_markdown.py /tmp/article_modified.md > /tmp/article.json\npython ~/.claude/skills/publish-x-article/scripts/parse_markdown.py /tmp/article_modified.md --html-only > /tmp/article_html.html","type":"text"}]},{"type":"ordered_list","attrs":{"order":2,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Navigate to https://x.com/compose/articles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click create, upload cover image (browser_file_upload for cover only)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fill title (from JSON: ","type":"text"},{"text":"title","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Copy & paste HTML:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python ~/.claude/skills/publish-x-article/scripts/copy_to_clipboard.py html --file /tmp/article_html.html","type":"text"}]},{"type":"paragraph","content":[{"text":"Then: browser_press_key Meta+v","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each content image (including converted table/mermaid PNGs), ","type":"text"},{"text":"in reverse order of block_index","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python copy_to_clipboard.py image /path/to/img.jpg --quality 85","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Click block element at ","type":"text"},{"text":"block_index","type":"text","marks":[{"type":"code_inline"}]},{"text":" position","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"browser_press_key Meta+v","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wait until upload complete","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify in preview","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Draft saved. Please review and publish manually.\"","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"为什么用 block_index 而非文字匹配?","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"精确定位","type":"text","marks":[{"type":"strong"}]},{"text":": 不依赖文字内容,即使多处文字相似也能正确定位","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"可靠性","type":"text","marks":[{"type":"strong"}]},{"text":": 索引是确定性的,不会因为文字相似而混淆","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"调试方便","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"after_text","type":"text","marks":[{"type":"code_inline"}]},{"text":" 仍保留用于人工核验","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"为什么用 Python 而非浏览器内 JavaScript?","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"本地处理更可靠","type":"text","marks":[{"type":"strong"}]},{"text":": Python 直接操作系统剪贴板,不受浏览器沙盒限制","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"图片压缩","type":"text","marks":[{"type":"strong"}]},{"text":": 上传前压缩图片 (--quality 85),减少上传时间","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"代码复用","type":"text","marks":[{"type":"strong"}]},{"text":": 脚本固定不变,无需每次重新编写转换逻辑","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"调试方便","type":"text","marks":[{"type":"strong"}]},{"text":": 脚本可单独测试,问题易定位","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"等待策略","type":"text"}]},{"type":"paragraph","content":[{"text":"关键理解","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"browser_wait_for","type":"text","marks":[{"type":"code_inline"}]},{"text":" 的 ","type":"text"},{"text":"textGone","type":"text","marks":[{"type":"code_inline"}]},{"text":" 参数会在文字消失时","type":"text"},{"text":"立即返回","type":"text","marks":[{"type":"strong"}]},{"text":",","type":"text"},{"text":"time","type":"text","marks":[{"type":"code_inline"}]},{"text":" 只是最大等待时间,不是固定等待时间。","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"# 正确用法:短 time 值,条件满足立即返回\nbrowser_wait_for textGone=\"正在上传媒体\" time=2\n\n# 错误用法:固定长时间等待\nbrowser_wait_for time=5 # 无条件等待5秒,浪费时间","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"封面图 vs 内容图","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"封面图","type":"text","marks":[{"type":"strong"}]},{"text":": 使用 browser_file_upload(因为有专门的上传按钮)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"内容图","type":"text","marks":[{"type":"strong"}]},{"text":": 使用 Python 剪贴板 + 粘贴(更高效)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Troubleshooting","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Table not rendering correctly","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ensure Pillow is installed: ","type":"text"},{"text":"pip install pillow","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check table markdown syntax is valid","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mermaid conversion fails","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check that diagramless.xyz is reachable","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check mermaid syntax is valid","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Try with explicit type: ","type":"text"},{"text":"--type mermaid","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Deep headers still showing","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Manually flatten ","type":"text"},{"text":"###","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"##","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"**bold**","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Re-run pre-processing","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Image upload timeout","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Compress images with ","type":"text"},{"text":"--quality 70","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use shorter ","type":"text"},{"text":"browser_wait_for time=2","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"publish-x-article","author":"@skillopedia","source":{"stars":104,"repo_name":"01coder-agent-skills","origin_url":"https://github.com/sugarforever/01coder-agent-skills/blob/HEAD/skills/publish-x-article/SKILL.md","repo_owner":"sugarforever","body_sha256":"03beadd45be6b095471749f14bacfc1c8982dd0c4d60cc5cc22638d6cac20049","cluster_key":"9bce075a55e4ccca983c873be03e4348dfed45b9e360774dc1f6328f4bb68a19","clean_bundle":{"format":"clean-skill-bundle-v1","source":"sugarforever/01coder-agent-skills/skills/publish-x-article/SKILL.md","attachments":[{"id":"e50972a7-645d-53b3-ab57-3e3b4e5c5e49","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e50972a7-645d-53b3-ab57-3e3b4e5c5e49/attachment.py","path":"scripts/copy_to_clipboard.py","size":9554,"sha256":"a9869731357753dac38f1f5ea44674b1cf6d108ff1447d1f33669ab604422ec2","contentType":"text/x-python; charset=utf-8"},{"id":"825f7ec1-3a3c-5486-bab0-e0a74ef304a5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/825f7ec1-3a3c-5486-bab0-e0a74ef304a5/attachment.py","path":"scripts/parse_markdown.py","size":11728,"sha256":"01ef0a6577c760acd259c8d6753cfe00cb3692942b93a30d162076476cadd79d","contentType":"text/x-python; charset=utf-8"},{"id":"58841447-c809-5800-aa90-9620abfcf09a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/58841447-c809-5800-aa90-9620abfcf09a/attachment.py","path":"scripts/table_to_image.py","size":8011,"sha256":"cf73c3e96d4930f80c658d36937f0dda81c0bd215ddf8660b0f877243440f523","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"6a6023a52b1f75eacbbb7846da9e68ef6124d278913a6e80ef519ef4c8f6b9a8","attachment_count":3,"text_attachments":3,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/publish-x-article/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"media-content","category_label":"Media"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"media-content","import_tag":"clean-skills-v1","description":"Publish Markdown articles to X (Twitter) Articles editor with proper formatting. Use when user wants to publish a Markdown file/URL to X Articles, or mentions \"publish to X\", \"post article to Twitter\", \"X article\", or wants help with X Premium article publishing. Handles cover image upload, converts Markdown to rich text, and automatically converts unsupported elements (tables, mermaid diagrams, deep headers) to images."}},"renderedAt":1782981080108}

Publish X Article Publish Markdown content to X (Twitter) Articles editor, preserving formatting with rich text conversion. Automatically handles X Premium limitations by converting unsupported elements to images. Credits This skill is inspired by and based on wshuyi/x-article-publisher-skill. Thank you to the original author for the foundational work. Interactive Setup: Ask Subscription Type IMPORTANT : Before processing the article, ask the user about their X subscription type if not already known. Prompt the User For Chinese users: Remember the Answer Once the user answers, remember their…