Get the FREE Ultimate OpenClaw Setup Guide →

devto-post

npx machina-cli add skill PHY041/claude-skill-devto/devto-post --openclaw
Files (1)
SKILL.md
8.4 KB

DEV.to Article Posting Skill (AppleScript Chrome Control)

Publish articles to DEV.to by controlling the user's real Chrome via AppleScript. No Playwright needed.


How It Works

Claude Code → osascript → Chrome (logged into DEV.to) → CSRF API → Published

Prerequisites

  • macOS only (AppleScript is a macOS technology)
  • Chrome: View → Developer → Allow JavaScript from Apple Events (restart after enabling)
  • User logged into DEV.to in Chrome

Method Detection

WINDOWS=$(osascript -e 'tell application "Google Chrome" to return count of windows' 2>/dev/null)
if [ "$WINDOWS" = "0" ] || [ -z "$WINDOWS" ]; then
    echo "METHOD 2 (System Events + Console)"
else
    echo "METHOD 1 (execute javascript)"
fi

Recommended: DEV.to Internal API (CSRF Token)

This is the most reliable method. The editor form has React state issues (tags concatenate, auto-save drafts persist bad state across reloads). Use the CSRF-protected internal API instead:

Step 1: Navigate to DEV.to

osascript -e 'tell application "Google Chrome" to tell active tab of first window to set URL to "https://dev.to"'
sleep 3

Step 2: Publish via CSRF API

(async()=>{
  try {
    var csrf = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
    var resp = await fetch('/articles', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrf
      },
      credentials: 'include',
      body: JSON.stringify({
        article: {
          title: "Your Title",
          body_markdown: "# Full markdown content here...",
          tags: ["opensource", "showdev", "tutorial", "programming"],
          published: true
        }
      })
    });
    var result = await resp.json();
    if (result.current_state_path) {
      document.title = "OK:" + result.current_state_path;
    } else {
      document.title = "ERR:" + JSON.stringify(result);
    }
  } catch(e) {
    document.title = "ERR:" + e.message;
  }
})()

Step 3: Get Published URL

sleep 3
osascript -e 'tell application "Google Chrome" to return title of active tab of first window'

The title will contain OK:/username/article-slug — prepend https://dev.to to get the full URL.

Step 4: Session Summary

Always end with the article link:

PlatformTitleLink
DEV.to"Your Article Title"https://dev.to/username/article-slug

For Long Articles: File-Based Approach

For articles too long to inline in JS, write the body to a temp file and inject:

# Write article content to temp JSON file
python3 -c "
import json
with open('/tmp/devto_body.md') as f:
    body = f.read()
with open('/tmp/devto_body.json', 'w') as f:
    json.dump(body, f)
"

# Use JXA to read the file and publish
osascript -l JavaScript -e '
var chrome = Application("Google Chrome");
var tab = chrome.windows[0].activeTab;
var body = JSON.parse($.NSString.alloc.initWithContentsOfFileEncodingError("/tmp/devto_body.json", $.NSUTF8StringEncoding, null).js);
tab.execute({javascript: "(async()=>{try{var csrf=document.querySelector(\"meta[name=csrf-token]\").getAttribute(\"content\");var resp=await fetch(\"/articles\",{method:\"POST\",headers:{\"Content-Type\":\"application/json\",\"X-CSRF-Token\":csrf},credentials:\"include\",body:JSON.stringify({article:{title:\"YOUR TITLE\",body_markdown:" + JSON.stringify(body) + ",tags:[\"tag1\",\"tag2\"],published:true}})});var r=await resp.json();document.title=r.current_state_path?\"OK:\"+r.current_state_path:\"ERR:\"+JSON.stringify(r)}catch(e){document.title=\"ERR:\"+e.message}})()"});
'

Important Gotchas

Never start body with ---

DEV.to parses standalone --- lines as YAML front matter delimiters. Strip them:

import re
body = re.sub(r'^---$', '', body, flags=re.MULTILINE)

Tag Rules

  • Maximum 4 tags per article
  • Tags must be lowercase
  • Pass as array: tags: ["tag1", "tag2", "tag3", "tag4"]

Why NOT to Use the Editor Form

The DEV.to editor has multiple issues:

  • Tag input concatenation: Enter key doesn't separate tags in the React component
  • Auto-save draft persistence: Bad state (e.g., malformed tags) persists across page reloads
  • React controlled component conflicts: Native value setters can corrupt React state

The CSRF API bypasses all of these. Always prefer the API.


Alternative: Editor Form (Fallback Only)

If the API doesn't work for some reason, you can fill the editor form directly:

Fill Title

osascript -e 'tell application "Google Chrome" to tell active tab of first window to execute javascript "
  var titleInput = document.querySelector(\"#article-form-title\");
  if (!titleInput) titleInput = document.querySelector(\"input[placeholder*=\\\"title\\\"]\");
  if (titleInput) {
    var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set;
    nativeInputValueSetter.call(titleInput, \"Your Article Title Here\");
    titleInput.dispatchEvent(new Event(\"input\", { bubbles: true }));
    document.title = \"TITLE_SET\";
  } else {
    document.title = \"TITLE_NOT_FOUND\";
  }
"'

Fill Body

osascript -e 'tell application "Google Chrome" to tell active tab of first window to execute javascript "
  var textarea = document.querySelector(\"#article_body_markdown\");
  if (!textarea) textarea = document.querySelector(\"textarea\");
  if (textarea) {
    var nativeTextareaSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, \"value\").set;
    nativeTextareaSetter.call(textarea, \"YOUR MARKDOWN CONTENT\");
    textarea.dispatchEvent(new Event(\"input\", { bubbles: true }));
    document.title = \"BODY_SET\";
  }
"'

Publish Button

osascript -e 'tell application "Google Chrome" to tell active tab of first window to execute javascript "
  var publishBtn = document.querySelector(\"button[aria-label*=\\\"Publish\\\"]\");
  if (!publishBtn) {
    var buttons = document.querySelectorAll(\"button\");
    for (var b of buttons) { if (b.textContent.trim() === \"Publish\") { publishBtn = b; break; } }
  }
  if (publishBtn) { publishBtn.click(); document.title = \"PUBLISHED\"; }
  else { document.title = \"PUBLISH_NOT_FOUND\"; }
"'

Article Template for Open Source Projects

[Opening hook - 1-2 sentences about what you built and why]

## The Problem

[Describe the pain point you're solving]
- Bullet point 1
- Bullet point 2
- Bullet point 3

## The Solution: [Project Name]

[Brief description of your solution]

1. **Feature 1** - description
2. **Feature 2** - description
3. **Feature 3** - description

## Getting Started

\`\`\`bash
git clone https://github.com/username/repo
cd repo
pip install -r requirements.txt
\`\`\`

## Key Features

### Feature Name
[Code example]

## Why Open Source?

[Personal story about why you're sharing this]

## Links

- **GitHub**: https://github.com/username/repo

Got questions or suggestions? Drop a comment below!

Tag Recommendations

Project TypeSuggested Tags
Python librarypython, opensource, api, showdev
JavaScript/Nodejavascript, node, opensource, showdev
AI/MLai, machinelearning, python, opensource
DevOpsdevops, docker, automation, opensource
Web appwebdev, react, opensource, showdev
Tutorialtutorial, beginners, programming, webdev

Error Handling

IssueSolution
Not logged inNavigate to dev.to/enter, user logs in manually
CSRF token not foundMake sure you're on dev.to domain first
Tags errorMax 4 tags, all lowercase, no spaces
Content too longSplit into series with series: "Series Name" in API body
--- YAML errorStrip standalone --- lines from body

Why AppleScript (Not Playwright)

ToolProblem
PlaywrightExtra setup, may fail on editor interactions
AppleScriptControls real Chrome, uses existing login, reliable

Source

git clone https://github.com/PHY041/claude-skill-devto/blob/main/.claude/skills/devto-post/SKILL.mdView on GitHub

Overview

This skill publishes articles to DEV.to by controlling Chrome through AppleScript, avoiding Playwright. It’s suited for technical blog posts, ShowDev articles, and open source announcements you want to share on DEV.to.

How This Skill Works

Claude executes osascript to drive Google Chrome (logged into DEV.to) and uses a CSRF-protected internal API to post articles. It’s designed to publish via the internal /articles endpoint, then retrieves the published URL, enabling end-to-end DEV.to posting without Playwright.

When to Use It

  • Publish a new technical blog post to DEV.to from a local script or prompt.
  • Post a ShowDev roundup or project announcement on DEV.to.
  • Announce an open-source project release or update on DEV.to.
  • Publish long-form content using the file-based body approach for large articles.
  • Need a Playwright-free automation method to publish quickly from macOS.

Quick Start

  1. Step 1: Prerequisites — ensure macOS, DEV.to login in Chrome, and that Chrome allows JavaScript from Apple Events.
  2. Step 2: Open DEV.to and publish via the internal CSRF API (as shown in the skill) using osascript and fetch with the CSRF token.
  3. Step 3: Read the published URL from the page or response (the current_state_path) and prepend https://dev.to to form the full link.

Best Practices

  • Use the internal CSRF API (/articles) for robust posting and to avoid React state issues.
  • Ensure Chrome is logged into DEV.to and that JavaScript from Apple Events is allowed.
  • Navigate to https://dev.to with AppleScript before posting to establish context.
  • Test with a draft first and verify the result.current_state_path to confirm success.
  • Always end with the published article URL to share a clean link.

Example Use Cases

  • Publish a new DEV.to article about a technical topic with a title, body_markdown, tags, and published: true.
  • Post a ShowDev roundup summarizing a recent demonstration or talk.
  • Announce a new open-source library release on DEV.to with relevant tags.
  • Publish a long-form tutorial by using the file-based body approach for large content.
  • Share a developer blog post or progress update on DEV.to using the CSRF-based publish flow.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers