<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Haoyang Pang</title>
    <description>The latest articles on DEV Community by Haoyang Pang (@haoyang_pang_a9f08cdb0b6c).</description>
    <link>https://dev.to/haoyang_pang_a9f08cdb0b6c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3699001%2F380ed64c-8c12-4708-9283-fe5641de6469.png</url>
      <title>DEV Community: Haoyang Pang</title>
      <link>https://dev.to/haoyang_pang_a9f08cdb0b6c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haoyang_pang_a9f08cdb0b6c"/>
    <language>en</language>
    <item>
      <title>We ran MiroFish on a real brand campaign. 105 AI agents. 6 minutes. Here's what happened.</title>
      <dc:creator>Haoyang Pang</dc:creator>
      <pubDate>Wed, 18 Mar 2026 07:12:10 +0000</pubDate>
      <link>https://dev.to/haoyang_pang_a9f08cdb0b6c/we-ran-mirofish-on-a-real-brand-campaign-105-ai-agents-6-minutes-heres-what-happened-4j7b</link>
      <guid>https://dev.to/haoyang_pang_a9f08cdb0b6c/we-ran-mirofish-on-a-real-brand-campaign-105-ai-agents-6-minutes-heres-what-happened-4j7b</guid>
      <description>&lt;p&gt;MiroFish hit GitHub #1 trending last week (28K stars). Everyone's asking: what can you actually &lt;em&gt;build&lt;/em&gt; with it?&lt;/p&gt;

&lt;p&gt;We built &lt;strong&gt;Campaign Pressure Test™&lt;/strong&gt; — a brand safety simulation for marketing teams. Here's a real run on a Singapore fashion brand (Nimisski) launching a spring campaign on Xiaohongshu.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Brands spend $50K–$500K on campaigns that flop because no one tested whether the audience would actually respond. Traditional focus groups take 2–4 weeks and cost $15K+. By the time you know it won't work, you've already bought the media.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Built on Top of MiroFish
&lt;/h2&gt;

&lt;p&gt;Standard MiroFish takes any seed document and spawns AI agents. We added one proprietary layer: &lt;strong&gt;Style Genome™ injection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before simulation, we embed a 768-dimensional brand memory vector into the seed document. This makes every agent brand-aware — they know Nimisski's price positioning ($189–449 SGD), aesthetic language (Korean-minimal), and competitive context before the simulation starts.&lt;/p&gt;

&lt;p&gt;The result: agents don't just react to "a fashion campaign." They react to &lt;em&gt;this brand's&lt;/em&gt; campaign, the way &lt;em&gt;this brand's&lt;/em&gt; target consumers would.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Results (345 seconds, not simulated)
&lt;/h2&gt;

&lt;p&gt;Brief fed to MiroFish:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spring Awakening campaign. Platform: Xiaohongshu. Market: Singapore. Korean-minimal fashion. Target: 25–35 urban women."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7 posts&lt;/strong&gt; generated organically by agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;17 comments&lt;/strong&gt;, &lt;strong&gt;105 total actions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brand Safety Score: 9.2/10&lt;/strong&gt; ✅&lt;/li&gt;
&lt;li&gt;Engagement rate: 40% (likes ÷ total actions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The competitor risk it caught — unprompted:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agents independently introduced Pomelo with the message "wide range of trendy and affordable options." Zero instructions to mention competitors. The simulation surfaced that Nimisski's $189–449 price range would face organic competitive framing from Pomelo unless campaign copy proactively justified the premium.&lt;/p&gt;

&lt;p&gt;That's signal you can't get from a survey.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 9-step MiroFish pipeline
&lt;/span&gt;&lt;span class="mf"&gt;1.&lt;/span&gt; &lt;span class="n"&gt;ontology&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;    &lt;span class="c1"&gt;# Build knowledge graph from seed
&lt;/span&gt;&lt;span class="mf"&gt;2.&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;         &lt;span class="c1"&gt;# GraphRAG construction
&lt;/span&gt;&lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="n"&gt;poll&lt;/span&gt;     &lt;span class="c1"&gt;# Wait for graph ready
&lt;/span&gt;&lt;span class="mf"&gt;4.&lt;/span&gt; &lt;span class="n"&gt;simulation&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;   &lt;span class="c1"&gt;# Initialize sim with agent count
&lt;/span&gt;&lt;span class="mf"&gt;5.&lt;/span&gt; &lt;span class="n"&gt;simulation&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;prepare&lt;/span&gt;  &lt;span class="c1"&gt;# Inject Style Genome seed here
&lt;/span&gt;&lt;span class="mf"&gt;6.&lt;/span&gt; &lt;span class="n"&gt;prepare&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;poll&lt;/span&gt; &lt;span class="c1"&gt;# Wait for agents to load
&lt;/span&gt;&lt;span class="mf"&gt;7.&lt;/span&gt; &lt;span class="n"&gt;simulation&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;    &lt;span class="c1"&gt;# Launch the swarm
&lt;/span&gt;&lt;span class="mf"&gt;8.&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;poll&lt;/span&gt;     &lt;span class="c1"&gt;# Monitor until complete
&lt;/span&gt;&lt;span class="mf"&gt;9.&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;agent_stats&lt;/span&gt;  &lt;span class="c1"&gt;# Extract results
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key gotcha&lt;/strong&gt;: Use &lt;code&gt;gemini-2.0-flash&lt;/code&gt;, NOT &lt;code&gt;gemini-2.5-flash&lt;/code&gt;. The thinking tokens in 2.5 cause 500 errors on MiroFish's simulation endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brand Safety Score formula:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positive_pct&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; \
        &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;negative_pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; \
        &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;safety_flag_pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;penalty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Matters Now
&lt;/h2&gt;

&lt;p&gt;MiroFish is still a raw engine — Docker + API key setup, no UI, no reporting. Most devs who clone it give up before getting value.&lt;/p&gt;

&lt;p&gt;We wrapped it into a one-command CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python run_pressure_test.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--brief&lt;/span&gt; &lt;span class="s2"&gt;"Your campaign brief here"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--platform&lt;/span&gt; XHS &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--industry&lt;/span&gt; fashion &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--market&lt;/span&gt; Singapore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6 minutes → HTML/PDF report → Brand Safety Score.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Competitive Moat
&lt;/h2&gt;

&lt;p&gt;Every US synthetic research platform (Aaru $1B, Simile $100M) uses generic AI personas. None of them have brand memory. Our Style Genome™ vectors are trained on real campaign data from CanMarket's existing client base — Haleon, Starbucks, ByteDance, McCann.&lt;/p&gt;

&lt;p&gt;An agent that knows your brand's DNA produces fundamentally different simulation outputs than a generic consumer persona.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;$499/simulation&lt;/strong&gt; — DM for Stripe link or free demo&lt;/li&gt;
&lt;li&gt;Enterprise Monthly Guard: $2,500/month&lt;/li&gt;
&lt;li&gt;Free demo for your next campaign: comment below or DM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Starbucks Harry Potter APAC campaign launches in 5 days. We're running a simulation tonight.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CanMarket is a Brand Operating System. "Run performance marketing without breaking your brand." MWC 2025 Global Runner-up.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>marketing</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Every AI Agent Skills Platform You Need to Know in 2026</title>
      <dc:creator>Haoyang Pang</dc:creator>
      <pubDate>Thu, 19 Feb 2026 16:55:47 +0000</pubDate>
      <link>https://dev.to/haoyang_pang_a9f08cdb0b6c/every-ai-agent-skills-platform-you-need-to-know-in-2026-4alg</link>
      <guid>https://dev.to/haoyang_pang_a9f08cdb0b6c/every-ai-agent-skills-platform-you-need-to-know-in-2026-4alg</guid>
      <description>&lt;p&gt;In December 2025, Anthropic released the &lt;strong&gt;SKILL.md open standard&lt;/strong&gt; — and OpenAI immediately adopted it for Codex CLI. Within weeks, an entire ecosystem exploded: 96,000+ skills on SkillsMP, 5,700+ on ClawHub, 17,000+ MCP servers on MCP.so.&lt;/p&gt;

&lt;p&gt;But here's the problem: &lt;strong&gt;finding the right skill feels like drinking from a firehose.&lt;/strong&gt; Some platforms have great search but terrible security. Others are safe but tiny. And 7% of one major registry literally leaks your API keys.&lt;/p&gt;

&lt;p&gt;I spent a week mapping every platform, scanning for security, and testing quality. Here's the complete guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Landscape at a Glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    AI Agent Skills Ecosystem (Feb 2026)

    ┌─────────────────────────────────────────────────────────┐
    │                    SKILL.md Standard                     │
    │         (Claude Code / Codex CLI / Gemini CLI)          │
    └──────────────────────┬──────────────────────────────────┘
                           │
         ┌─────────────────┼─────────────────┐
         │                 │                 │
    Claude Code        OpenClaw           MCP Servers
    Skills             Skills             (Universal)
         │                 │                 │
    SkillsMP (96K)    ClawHub (5.7K)    MCP.so (17K)
    SkillHub (7K)     awesome-oc (3K)   mcpservers.org
    awesome-* (many)  LobeHub            LobeHub MCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 1: Claude Code Skills Platforms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tier S: Official &amp;amp; Verified
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Why Use It&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/anthropics/skills" rel="noopener noreferrer"&gt;&lt;strong&gt;Anthropic Official Skills&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reference&lt;/td&gt;
&lt;td&gt;The gold standard. Study these to learn how to write good skills.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/VoltAgent/awesome-agent-skills" rel="noopener noreferrer"&gt;&lt;strong&gt;awesome-agent-skills&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;380+&lt;/td&gt;
&lt;td&gt;Skills from official dev teams: Anthropic, Vercel, Stripe, Cloudflare, Sentry, HuggingFace, Expo. &lt;strong&gt;Start here.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are the only platforms where you can install without reading the source code first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier A: Large Directories
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Strength&lt;/th&gt;
&lt;th&gt;Watch Out&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://skillsmp.com" rel="noopener noreferrer"&gt;&lt;strong&gt;SkillsMP&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;96,751+&lt;/td&gt;
&lt;td&gt;Largest directory. Smart search. Claude/Codex/ChatGPT compatible.&lt;/td&gt;
&lt;td&gt;No security audit. Quantity over quality.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.skillhub.club/" rel="noopener noreferrer"&gt;&lt;strong&gt;SkillHub&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;7,000+&lt;/td&gt;
&lt;td&gt;AI scoring on 5 dimensions (S/A/B/C rank). Multi-platform.&lt;/td&gt;
&lt;td&gt;Score doesn't check for security flaws.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; On SkillHub, filter by S-Rank only. On SkillsMP, search by keyword then check the GitHub repo before installing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier B: Curated Awesome Lists
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;th&gt;Notable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/travisvn/awesome-claude-skills" rel="noopener noreferrer"&gt;travisvn/awesome-claude-skills&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Claude Code specific&lt;/td&gt;
&lt;td&gt;Well-organized, includes resources and tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/ComposioHQ/awesome-claude-skills" rel="noopener noreferrer"&gt;ComposioHQ/awesome-claude-skills&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Workflow customization&lt;/td&gt;
&lt;td&gt;Good for automation-heavy setups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/hesreallyhim/awesome-claude-code" rel="noopener noreferrer"&gt;hesreallyhim/awesome-claude-code&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Full ecosystem&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Includes Trail of Bits security skills&lt;/strong&gt; (20+ auditing tools)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sickn33/antigravity-awesome-skills" rel="noopener noreferrer"&gt;sickn33/antigravity-awesome-skills&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;800+ skills&lt;/td&gt;
&lt;td&gt;Antigravity/Cursor compatible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/karanb192/awesome-claude-skills" rel="noopener noreferrer"&gt;karanb192/awesome-claude-skills&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;50+ verified&lt;/td&gt;
&lt;td&gt;Every skill tested before inclusion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Part 2: OpenClaw Skills Platforms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Official Registry
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://clawhub.ai/" rel="noopener noreferrer"&gt;&lt;strong&gt;ClawHub&lt;/strong&gt;&lt;/a&gt; — 5,705 skills, semantic search, CLI install (&lt;code&gt;openclaw skill install &amp;lt;name&amp;gt;&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;As of February 2026, ClawHub now integrates &lt;strong&gt;VirusTotal scanning&lt;/strong&gt; for all new submissions. This was a direct response to the ClawHavoc incident (more on that below).&lt;/p&gt;

&lt;h3&gt;
  
  
  Community Collections
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Focus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/VoltAgent/awesome-openclaw-skills" rel="noopener noreferrer"&gt;awesome-openclaw-skills&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3,002 curated skills. Higher average quality than ClawHub.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/BankrBot/openclaw-skills" rel="noopener noreferrer"&gt;BankrBot/openclaw-skills&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Crypto/DeFi/trading automation niche&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://lobehub.com/skills/openclaw-skills-openclaws" rel="noopener noreferrer"&gt;LobeHub OpenClaw&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;LobeChat ecosystem integration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Part 3: MCP Server Platforms (Works With Everything)
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) servers work with Claude Code, OpenClaw, and most AI agents. They're complementary to skills — skills teach behavior, MCP servers provide tool access.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://mcp.so" rel="noopener noreferrer"&gt;&lt;strong&gt;MCP.so&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;17,749&lt;/td&gt;
&lt;td&gt;Largest directory. Community-driven.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://mcpmarket.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;MCP Market&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Clean UI, category browsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://mcpservers.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;mcpservers.org&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The "awesome list" of MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://lobehub.com/mcp" rel="noopener noreferrer"&gt;&lt;strong&gt;LobeHub MCP&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;If you're in the LobeChat ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/cline/mcp-marketplace" rel="noopener noreferrer"&gt;&lt;strong&gt;Cline Marketplace&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;One-click install for Cline users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/modelcontextprotocol/servers" rel="noopener noreferrer"&gt;&lt;strong&gt;Official MCP Servers&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Anthropic-maintained. Reference implementations.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Part 4: Security — Read This Before Installing Anything
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Incidents
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ClawHavoc (Feb 2026):&lt;/strong&gt; 341 malicious skills on ClawHub distributed macOS malware. Skills looked legitimate but contained obfuscated download-and-execute payloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snyk ToxicSkills:&lt;/strong&gt; Scanning the entire ClawHub registry revealed &lt;strong&gt;7.1% of skills (283) leak API keys&lt;/strong&gt; — hardcoded credentials in source code that get copied into your environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Register investigation:&lt;/strong&gt; Demonstrated how easy it is to backdoor OpenClaw skills and exfiltrate data through seemingly innocent file operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Tools
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/adversa-ai/secureclaw" rel="noopener noreferrer"&gt;&lt;strong&gt;SecureClaw&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;55 automated audit checks, maps to OWASP Agentic Top 10 and MITRE ATLAS. &lt;strong&gt;Free.&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://safeclaw.io/openclaw-skills-security-scanner" rel="noopener noreferrer"&gt;&lt;strong&gt;SafeClaw Scanner&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;Pre-install scan for malicious patterns, data exfiltration, excessive permissions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ClawHub VirusTotal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Automatic virus scanning on ClawHub (added Feb 2026).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  My Install Checklist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before installing ANY skill from a non-official source:

[ ] Check the author's GitHub profile (age, other repos, stars)
[ ] Read SKILL.md — does the description match what the code does?
[ ] Search for network calls (fetch, http, curl, requests)
[ ] Search for file system writes outside the project directory
[ ] Search for environment variable reads (process.env, os.environ)
[ ] Run SecureClaw if it's an OpenClaw skill
[ ] Check if the skill asks for permissions it shouldn't need
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 5: The SKILL.md Standard (Quick Reference)
&lt;/h2&gt;

&lt;p&gt;All platforms now use the same format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.claude/skills/my-skill/SKILL.md    # Claude Code (personal)
.claude/skills/my-skill/SKILL.md      # Claude Code (project)
~/.codex/skills/my-skill/SKILL.md     # Codex CLI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;
name: my-skill          # becomes /my-skill slash command
description: "..."      # used for auto-discovery
license: MIT

&lt;span class="gh"&gt;# My Skill&lt;/span&gt;

Instructions for the agent go here.
Keep under 500 lines / 5,000 tokens.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Compatible with:&lt;/strong&gt; Claude Code, Codex CLI, Antigravity, Gemini CLI, Cursor, OpenCode.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  If you want safety first
&lt;/h3&gt;

&lt;p&gt;Start with &lt;a href="https://github.com/VoltAgent/awesome-agent-skills" rel="noopener noreferrer"&gt;awesome-agent-skills&lt;/a&gt;. 380+ skills from Anthropic, Vercel, Stripe, Cloudflare, Sentry, HuggingFace, Trail of Bits. Every skill is from an official dev team.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you want volume
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://skillsmp.com" rel="noopener noreferrer"&gt;SkillsMP&lt;/a&gt; has 96K+ skills with good search. But &lt;strong&gt;always check the source repo&lt;/strong&gt; before installing.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you want quality scoring
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.skillhub.club/" rel="noopener noreferrer"&gt;SkillHub&lt;/a&gt; S-Rank skills (9.0+/10) are generally solid. The AI scoring isn't perfect but filters out the worst.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you're on OpenClaw
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://clawhub.ai/" rel="noopener noreferrer"&gt;ClawHub&lt;/a&gt; + &lt;a href="https://github.com/adversa-ai/secureclaw" rel="noopener noreferrer"&gt;SecureClaw&lt;/a&gt;. Install SecureClaw first, then use it to audit everything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you need MCP servers
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://mcp.so" rel="noopener noreferrer"&gt;MCP.so&lt;/a&gt; for discovery, &lt;a href="https://github.com/modelcontextprotocol/servers" rel="noopener noreferrer"&gt;official repo&lt;/a&gt; for reference implementations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total Claude Code skills across all platforms&lt;/td&gt;
&lt;td&gt;~100,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total OpenClaw skills (ClawHub)&lt;/td&gt;
&lt;td&gt;5,705&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total MCP servers (MCP.so)&lt;/td&gt;
&lt;td&gt;17,749&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official/verified skills (awesome-agent-skills)&lt;/td&gt;
&lt;td&gt;380+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known malicious skills removed (ClawHavoc)&lt;/td&gt;
&lt;td&gt;341&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills leaking API keys (Snyk scan)&lt;/td&gt;
&lt;td&gt;283&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The agent skills ecosystem is growing fast — faster than anyone can curate. The SKILL.md standard was the inflection point that unified everything. But with 100K+ skills and counting, the real skill is knowing where to look and what to avoid.&lt;/p&gt;

&lt;p&gt;Start small. Start official. Audit everything else.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with Claude Code. If you found this useful, follow me for more AI tooling deep dives.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Open-Sourced Claude Code Skills for Reddit and DEV.to Automation (AppleScript + Chrome)</title>
      <dc:creator>Haoyang Pang</dc:creator>
      <pubDate>Mon, 09 Feb 2026 18:19:57 +0000</pubDate>
      <link>https://dev.to/haoyang_pang_a9f08cdb0b6c/i-open-sourced-claude-code-skills-for-reddit-and-devto-automation-applescript-chrome-1k56</link>
      <guid>https://dev.to/haoyang_pang_a9f08cdb0b6c/i-open-sourced-claude-code-skills-for-reddit-and-devto-automation-applescript-chrome-1k56</guid>
      <description>&lt;p&gt;Every browser automation tool — Playwright, Selenium, Puppeteer — sets &lt;code&gt;navigator.webdriver=true&lt;/code&gt;. Reddit detects it instantly. DEV.to's React editor fights back against programmatic input. I needed a different approach.&lt;/p&gt;

&lt;p&gt;Turns out macOS has had the answer all along: &lt;strong&gt;AppleScript can execute JavaScript inside your real Chrome browser&lt;/strong&gt;. Same cookies, same fingerprint, same everything. Sites literally cannot tell the difference between you and the script.&lt;/p&gt;

&lt;p&gt;I built two Claude Code skill packages around this technique and open-sourced them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skills
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Reddit Automation (&lt;code&gt;claude-skill-reddit&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Two skills in one repo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;reddit-cultivate&lt;/strong&gt; — Automated karma building&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scans rising posts across target subreddits via &lt;code&gt;/r/{sub}/rising.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Drafts natural, value-first comments&lt;/li&gt;
&lt;li&gt;Posts via Reddit's internal &lt;code&gt;/api/comment&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;Outputs a summary table with direct links to every comment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;reddit-post&lt;/strong&gt; — Submit posts to any subreddit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text posts or link posts via &lt;code&gt;/api/submit&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Automatic modhash (CSRF) handling&lt;/li&gt;
&lt;li&gt;Flair detection and application&lt;/li&gt;
&lt;li&gt;Spam filter avoidance guidelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. DEV.to Publishing (&lt;code&gt;claude-skill-devto&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;devto-post&lt;/strong&gt; — Publish articles via CSRF API&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bypasses DEV.to's buggy React editor entirely&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;POST /articles&lt;/code&gt; with &lt;code&gt;X-CSRF-Token&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Single API call = article published&lt;/li&gt;
&lt;li&gt;Handles long articles via temp file + JXA injection&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Core Technique
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Execute JavaScript in Chrome's active tab&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to tell active tab of first window to execute javascript "fetch(\"/api/me.json\").then(r=&amp;gt;r.json()).then(d=&amp;gt;{document.title=JSON.stringify(d)})"'&lt;/span&gt;

&lt;span class="c"&gt;# Read async result back via document.title&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;2
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to return title of active tab of first window'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;document.title&lt;/code&gt; trick is key — since AppleScript can't directly await async JS, we write results to the page title and read it back.&lt;/p&gt;

&lt;p&gt;For complex JavaScript (avoiding escaping hell), use JXA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-l&lt;/span&gt; JavaScript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
var chrome = Application("Google Chrome");
var tab = chrome.windows[0].activeTab;
tab.execute({javascript: "(" + function() {
    // Any JS here — no escaping needed
    fetch("/api/endpoint", {credentials: "include"})
        .then(r =&amp;gt; r.json())
        .then(d =&amp;gt; { document.title = JSON.stringify(d); });
} + ")();"});
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Not Just Use the APIs Directly?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reddit API&lt;/strong&gt; requires OAuth app registration and gets rate-limited/IP-blocked fast&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEV.to editor&lt;/strong&gt; has React state bugs (tags concatenate, auto-save drafts persist bad state)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AppleScript + Chrome&lt;/strong&gt; uses your existing login session — no tokens, no registration, no detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add PHY041/claude-skill-reddit
npx skills add PHY041/claude-skill-devto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or clone directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/PHY041/claude-skill-reddit" rel="noopener noreferrer"&gt;claude-skill-reddit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PHY041/claude-skill-devto" rel="noopener noreferrer"&gt;claude-skill-devto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt; macOS + Chrome with "Allow JavaScript from Apple Events" enabled (Chrome → View → Developer → toggle it on, then restart Chrome).&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned Building These
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;document.title&lt;/code&gt; trick is universal&lt;/strong&gt; — works for any async operation on any site&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JXA &amp;gt; AppleScript&lt;/strong&gt; for complex JS — no escaping, template literals work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEV.to's CSRF API is way more reliable than the editor&lt;/strong&gt; — one POST call vs fighting React state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reddit's internal API is the same one their frontend uses&lt;/strong&gt; — same-origin fetch with cookies just works&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome multi-profile can break AppleScript&lt;/strong&gt; — always check &lt;code&gt;count of windows&lt;/code&gt; first and fall back to System Events + Console keyboard automation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These skills are the real deal — I use them daily for my own Reddit cultivation and blog publishing. Feedback and PRs welcome!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The Browser Automation Cheat Code Nobody Talks About: AppleScript + Chrome</title>
      <dc:creator>Haoyang Pang</dc:creator>
      <pubDate>Mon, 09 Feb 2026 18:02:03 +0000</pubDate>
      <link>https://dev.to/haoyang_pang_a9f08cdb0b6c/the-browser-automation-cheat-code-nobody-talks-about-applescript-chrome-52ha</link>
      <guid>https://dev.to/haoyang_pang_a9f08cdb0b6c/the-browser-automation-cheat-code-nobody-talks-about-applescript-chrome-52ha</guid>
      <description>&lt;h2&gt;
  
  
  Part 1: The Problem — Why Every Browser Automation Tool is Broken
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Arms Race
&lt;/h3&gt;

&lt;p&gt;Every major website (Reddit, Twitter, LinkedIn, Facebook, Amazon) has invested millions in detecting automated browsers. They check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;navigator.webdriver&lt;/code&gt; flag (Playwright/Selenium set this to &lt;code&gt;true&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Chrome DevTools Protocol fingerprint&lt;/li&gt;
&lt;li&gt;Missing browser plugins (real Chrome has extensions, automated Chrome doesn't)&lt;/li&gt;
&lt;li&gt;Canvas/WebGL fingerprinting differences&lt;/li&gt;
&lt;li&gt;Mouse movement patterns (or lack thereof)&lt;/li&gt;
&lt;li&gt;Request timing patterns (bots are too fast, too regular)&lt;/li&gt;
&lt;li&gt;TLS fingerprinting (headless browsers have different TLS signatures)&lt;/li&gt;
&lt;li&gt;CDP (Chrome DevTools Protocol) artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tool-by-Tool Breakdown: Why They All Fail
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Selenium
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Sets &lt;code&gt;navigator.webdriver = true&lt;/code&gt; — instant detection&lt;/li&gt;
&lt;li&gt;Different browser fingerprint from real Chrome&lt;/li&gt;
&lt;li&gt;Launches a fresh profile every time (no cookies, no history = suspicious)&lt;/li&gt;
&lt;li&gt;Detectable through JavaScript: &lt;code&gt;window.chrome.runtime&lt;/code&gt; is undefined&lt;/li&gt;
&lt;li&gt;Many sites block it outright (Cloudflare, Akamai, PerimeterX)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Playwright
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Same &lt;code&gt;navigator.webdriver&lt;/code&gt; problem&lt;/li&gt;
&lt;li&gt;Uses a patched Chromium, not real Chrome — fingerprint differs&lt;/li&gt;
&lt;li&gt;Even with stealth plugins (&lt;code&gt;playwright-extra&lt;/code&gt;, &lt;code&gt;puppeteer-extra-plugin-stealth&lt;/code&gt;), sophisticated sites detect it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reddit specifically blocks Playwright&lt;/strong&gt; — returns 403 "Blocked" on page load&lt;/li&gt;
&lt;li&gt;Can't use the user's existing cookies/session without complex injection&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Puppeteer
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Shares Playwright's problems (both use CDP)&lt;/li&gt;
&lt;li&gt;Headless mode has distinct fingerprint vs headed mode&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HeadlessChrome&lt;/code&gt; in User-Agent is a dead giveaway&lt;/li&gt;
&lt;li&gt;Even &lt;code&gt;--headless=new&lt;/code&gt; (new headless mode) gets caught by advanced detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Chrome DevTools Protocol (CDP) Direct
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Requires Chrome launched with &lt;code&gt;--remote-debugging-port&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Chrome REFUSES to enable debugging on the default profile: "DevTools remote debugging requires a non-default data directory"&lt;/li&gt;
&lt;li&gt;Non-default data directory = fresh profile = no cookies = no login&lt;/li&gt;
&lt;li&gt;Copying the profile is complex (multi-GB, encryption issues)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  curl / HTTP Client Libraries
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;No browser fingerprint at all — easily identified as a script&lt;/li&gt;
&lt;li&gt;No JavaScript execution capability&lt;/li&gt;
&lt;li&gt;Reddit rate-limits aggressively (IP block after a few rapid requests)&lt;/li&gt;
&lt;li&gt;Can't handle JavaScript-rendered content&lt;/li&gt;
&lt;li&gt;Session cookies (HttpOnly) can't be set from outside the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Undetected-Chromedriver / Stealth Plugins
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Cat-and-mouse game — patches lag behind detection updates&lt;/li&gt;
&lt;li&gt;Still launches a separate browser instance&lt;/li&gt;
&lt;li&gt;Doesn't have the user's real cookies, extensions, history&lt;/li&gt;
&lt;li&gt;Only delays detection, doesn't prevent it&lt;/li&gt;
&lt;li&gt;Breaks with every Chrome update&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Core Problem
&lt;/h3&gt;

&lt;p&gt;All these tools share the same fundamental flaw: &lt;strong&gt;they create a synthetic browser environment&lt;/strong&gt;. No matter how good the emulation, it's never identical to a real user's browser. Detection systems don't need to find one smoking gun — they correlate dozens of subtle signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: The Discovery — AppleScript is the Cheat Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is AppleScript Chrome Control?
&lt;/h3&gt;

&lt;p&gt;macOS has a built-in automation framework called AppleScript. Chrome (and other apps) expose an AppleScript interface that allows external scripts to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate&lt;/strong&gt; tabs to URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute JavaScript&lt;/strong&gt; in any tab&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read&lt;/strong&gt; tab properties (title, URL)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control&lt;/strong&gt; windows (open, close, resize)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The critical insight: &lt;strong&gt;this JavaScript executes inside the user's actual Chrome process&lt;/strong&gt;. Not a copy. Not an emulation. The real thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This is Fundamentally Different
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional Automation:
  Script → Launches new browser → Controls it → Website detects fake browser

AppleScript Automation:
  Script → Talks to EXISTING Chrome → Chrome does the work → Website sees real user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The website cannot distinguish AppleScript-triggered JavaScript from user-triggered JavaScript because &lt;strong&gt;there is no difference at the browser level&lt;/strong&gt;. The JavaScript runs in the exact same V8 context, with the exact same cookies, the same extensions, the same fingerprint. Chrome doesn't know or care that the command came from AppleScript instead of a keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Means
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;The website sees...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Browser fingerprint&lt;/td&gt;
&lt;td&gt;The user's real Chrome (with all extensions, plugins)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cookies&lt;/td&gt;
&lt;td&gt;The user's real cookies (including HttpOnly ones!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS fingerprint&lt;/td&gt;
&lt;td&gt;Chrome's real TLS stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;navigator.webdriver&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;false&lt;/code&gt; (not automation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login session&lt;/td&gt;
&lt;td&gt;Already authenticated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP address&lt;/td&gt;
&lt;td&gt;The user's real IP (same as their normal browsing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canvas/WebGL&lt;/td&gt;
&lt;td&gt;Real GPU rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;There is literally nothing to detect.&lt;/strong&gt; The request IS from a real browser. Because it IS a real browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: The Full Technical Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 One-Time Setup
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Enable JavaScript from Apple Events
&lt;/h4&gt;

&lt;p&gt;Chrome menu → View → Developer → &lt;strong&gt;Allow JavaScript from Apple Events&lt;/strong&gt; ✓&lt;/p&gt;

&lt;p&gt;Then &lt;strong&gt;restart Chrome&lt;/strong&gt; (Cmd+Q, reopen).&lt;/p&gt;

&lt;p&gt;This is a security feature — Chrome requires explicit opt-in before allowing external JavaScript execution. It persists across restarts.&lt;/p&gt;

&lt;h4&gt;
  
  
  Verify It Works
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to tell active tab of first window to execute javascript "document.title"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should return the current tab's title. If you get an error about "JavaScript through AppleScript is turned off", restart Chrome.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Three Core Operations
&lt;/h3&gt;

&lt;p&gt;Everything you can do reduces to three operations:&lt;/p&gt;

&lt;h4&gt;
  
  
  Operation 1: Navigate
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "Google Chrome"
    tell active tab of first window
        set URL to "https://www.reddit.com"
    end tell
end tell'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Operation 2: Execute JavaScript
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "Google Chrome"
    tell active tab of first window
        execute javascript "1 + 1"
    end tell
end tell'&lt;/span&gt;
&lt;span class="c"&gt;# Returns: 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Operation 3: Read Tab Properties
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "Google Chrome"
    return title of active tab of first window
end tell'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.3 The document.title Trick — Getting Complex Data Out
&lt;/h3&gt;

&lt;p&gt;AppleScript's &lt;code&gt;execute javascript&lt;/code&gt; returns the result of the expression. But for async operations (like &lt;code&gt;fetch()&lt;/code&gt;), we need a workaround:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run async JS that writes the result to &lt;code&gt;document.title&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Wait for it to complete&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;document.title&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Execute async JS, store result in title&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "Google Chrome"
    tell active tab of first window
        execute javascript "fetch(\"/api/me.json\", {credentials: \"include\"}).then(r =&amp;gt; r.json()).then(d =&amp;gt; { document.title = \"RESULT:\" + JSON.stringify(d) })"
    end tell
end tell'&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Wait&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;2

&lt;span class="c"&gt;# Step 3: Read result&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "Google Chrome"
    return title of active tab of first window
end tell'&lt;/span&gt;
&lt;span class="c"&gt;# Returns: RESULT:{"data":{"name":"myuser","karma":123,...}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why document.title?&lt;/strong&gt; It's the simplest channel to pass data from the browser JS context back to the shell. The title has a practical limit of a few KB, which is enough for most API responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Making Authenticated API Calls
&lt;/h3&gt;

&lt;p&gt;Since the browser is already logged in, we can use &lt;code&gt;fetch()&lt;/code&gt; with &lt;code&gt;credentials: "include"&lt;/code&gt; to make API calls that automatically include all cookies — even HttpOnly ones that JavaScript can't read directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This runs inside the logged-in Chrome — cookies are sent automatically&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/endpoint.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;// This is the magic — sends all cookies&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For POST requests (posting, commenting, voting), most sites require a CSRF token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Step 1: Get CSRF/modhash token&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/me.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;csrf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modhash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Reddit calls it "modhash"&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2: POST with the token&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`thing_id=t3_abc123&amp;amp;text=Hello&amp;amp;uh=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;csrf&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;api_type=json`&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.5 JXA (JavaScript for Automation) — Avoiding Escaping Hell
&lt;/h3&gt;

&lt;p&gt;AppleScript has painful string escaping (nested quotes are a nightmare). JXA is macOS's JavaScript-based alternative that solves this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-l&lt;/span&gt; JavaScript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
var chrome = Application("Google Chrome");
var tab = chrome.windows[0].activeTab;
tab.execute({javascript: "(" + function() {
    // Write normal JavaScript here — no escaping needed!
    var data = {hello: "world"};
    document.title = JSON.stringify(data);
} + ")();"});
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick: define a JS function, &lt;code&gt;.toString()&lt;/code&gt; it (via &lt;code&gt;+&lt;/code&gt; concatenation), and pass that string to &lt;code&gt;execute&lt;/code&gt;. This means you write clean JavaScript without worrying about AppleScript string escaping.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.6 Targeting Specific Tabs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Active tab of first window (most common)&lt;/span&gt;
tell active tab of first window

&lt;span class="c"&gt;# Specific tab by index&lt;/span&gt;
tell tab 3 of first window

&lt;span class="c"&gt;# Specific window&lt;/span&gt;
tell active tab of window 2

&lt;span class="c"&gt;# Find tab by URL&lt;/span&gt;
tell application &lt;span class="s2"&gt;"Google Chrome"&lt;/span&gt;
    repeat with t &lt;span class="k"&gt;in &lt;/span&gt;tabs of first window
        &lt;span class="k"&gt;if &lt;/span&gt;URL of t contains &lt;span class="s2"&gt;"reddit.com"&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;execute javascript &lt;span class="s2"&gt;"document.title"&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;t
        end &lt;span class="k"&gt;if
    &lt;/span&gt;end repeat
end tell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.7 Opening New Tabs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "Google Chrome"
    tell first window
        make new tab with properties {URL:"https://www.reddit.com"}
    end tell
end tell'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 4: Our Reddit Automation — The Full Story
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Journey (What We Tried)
&lt;/h3&gt;

&lt;p&gt;We needed to automate Reddit posting and commenting. Here's every approach we tried, in order:&lt;/p&gt;

&lt;h4&gt;
  
  
  Attempt 1: Playwright MCP Browser
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Opened a new Playwright browser&lt;/li&gt;
&lt;li&gt;Navigated to reddit.com&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result: Reddit returned 403 "Blocked"&lt;/strong&gt; — detected automation immediately&lt;/li&gt;
&lt;li&gt;Even with injected cookies via &lt;code&gt;context.addCookies()&lt;/code&gt;, Reddit still blocked the page&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Attempt 2: Cookie Injection
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;User exported all cookies (including HttpOnly) from Cookie Editor extension&lt;/li&gt;
&lt;li&gt;Injected 14 cookies into Playwright via &lt;code&gt;context.addCookies()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Navigated to reddit.com&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result: Still 403 "Blocked"&lt;/strong&gt; — cookie injection doesn't fix the browser fingerprint&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Attempt 3: Chrome DevTools MCP
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Launched Chrome with &lt;code&gt;--remote-debugging-port=9222&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Chrome error: "DevTools remote debugging requires a non-default data directory"&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;--user-data-dir=/tmp/chrome-debug&lt;/code&gt; → works but fresh profile, no cookies&lt;/li&gt;
&lt;li&gt;Could navigate to Reddit (not blocked!) but couldn't log in&lt;/li&gt;
&lt;li&gt;Tried injecting HttpOnly cookies via &lt;code&gt;document.cookie&lt;/code&gt; — can't set HttpOnly from JS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result: Dead end&lt;/strong&gt; — can't authenticate&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Attempt 4: Reddit API via curl
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Used &lt;code&gt;token_v2&lt;/code&gt; (JWT from browser cookies) as Bearer token&lt;/li&gt;
&lt;li&gt;First call: &lt;strong&gt;HTTP 200&lt;/strong&gt; — it works!&lt;/li&gt;
&lt;li&gt;Second call: HTTP 200 (features only, no user data)&lt;/li&gt;
&lt;li&gt;Third call: &lt;strong&gt;403 "Blocked"&lt;/strong&gt; — IP rate-limited&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result: Token works but IP gets blocked after a few rapid requests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Attempt 5: AppleScript Chrome Control (THE WIN)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Remembered macOS can control Chrome via AppleScript&lt;/li&gt;
&lt;li&gt;One prerequisite: enable "Allow JavaScript from Apple Events"&lt;/li&gt;
&lt;li&gt;Needed Chrome restart after enabling&lt;/li&gt;
&lt;li&gt;First test: &lt;code&gt;execute javascript "document.title"&lt;/code&gt; → &lt;strong&gt;works!&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Navigate to reddit.com → &lt;strong&gt;not blocked&lt;/strong&gt; (it's the real Chrome)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fetch("/api/me.json")&lt;/code&gt; → &lt;strong&gt;returns user data&lt;/strong&gt; (already logged in!)&lt;/li&gt;
&lt;li&gt;Post comment via &lt;code&gt;fetch("/api/comment")&lt;/code&gt; → &lt;strong&gt;comment posted successfully&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result: Full automation with zero detection&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Working Pipeline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: Navigate to reddit.com (if not already there)
    osascript → Chrome → set URL to reddit.com

Step 2: Scan for opportunities
    osascript → Chrome → fetch("/r/SideProject/rising.json")
    → Filter: score &amp;gt; 2, comments &amp;lt; 15

Step 3: Generate comment
    Claude Code / GPT-5.2 → draft value-first comment

Step 4: Human review
    Show comment to user → user approves

Step 5: Get CSRF token (modhash)
    osascript → Chrome → fetch("/api/me.json") → extract modhash

Step 6: Post comment
    osascript → Chrome → fetch("/api/comment", {method: "POST", body: ...})

Step 7: Verify
    Read API response → confirm comment ID and permalink
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Actual Commands Used
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check login status&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'..execute javascript "fetch(\"/api/me.json\",{credentials:\"include\"}).then(r=&amp;gt;r.json()).then(d=&amp;gt;{document.title=\"USER:\"+JSON.stringify({name:d.data.name,karma:d.data.total_karma})})"'&lt;/span&gt;
&lt;span class="c"&gt;# → USER:{"name":"BP041","karma":35}&lt;/span&gt;

&lt;span class="c"&gt;# Scan rising posts&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'..execute javascript "fetch(\"/r/SideProject/rising.json?limit=5\",{credentials:\"include\"}).then(r=&amp;gt;r.json()).then(d=&amp;gt;{let posts=d.data.children.map(c=&amp;gt;({t:c.data.title.substring(0,60),s:c.data.score,c:c.data.num_comments,id:c.data.name}));document.title=\"POSTS:\"+JSON.stringify(posts)})"'&lt;/span&gt;

&lt;span class="c"&gt;# Get modhash&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'..execute javascript "fetch(\"/api/me.json\",{credentials:\"include\"}).then(r=&amp;gt;r.json()).then(d=&amp;gt;{document.title=\"MH:\"+d.data.modhash})"'&lt;/span&gt;

&lt;span class="c"&gt;# Post comment (using JXA to avoid escaping issues)&lt;/span&gt;
osascript &lt;span class="nt"&gt;-l&lt;/span&gt; JavaScript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
var chrome = Application("Google Chrome");
var tab = chrome.windows[0].activeTab;
tab.execute({javascript: "(" + function() {
    var body = new URLSearchParams({
        thing_id: "t3_1qzoq6p",
        text: "Your comment here",
        uh: "modhash_here",
        api_type: "json"
    });
    fetch("/api/comment", {
        method: "POST",
        credentials: "include",
        headers: {"Content-Type": "application/x-www-form-urlencoded"},
        body: body.toString()
    }).then(r=&amp;gt;r.json()).then(d=&amp;gt;{document.title="POSTED:"+JSON.stringify(d)});
} + ")();"});
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 5: Beyond Reddit — This Works Everywhere
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Pattern is Universal
&lt;/h3&gt;

&lt;p&gt;Any website that you can use in Chrome, you can automate with this method:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;What you can automate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reddit&lt;/td&gt;
&lt;td&gt;Post, comment, upvote, browse, subscribe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Twitter/X&lt;/td&gt;
&lt;td&gt;Tweet, reply, like, retweet, DM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinkedIn&lt;/td&gt;
&lt;td&gt;Post, comment, connect, message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instagram&lt;/td&gt;
&lt;td&gt;Like, comment (web version)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;Create issues, PRs, review code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any SaaS&lt;/td&gt;
&lt;td&gt;Any action you can do in the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Same Three Steps Apply
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Navigate&lt;/strong&gt; to the site (or verify you're already there)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt; fetch() calls using &lt;code&gt;credentials: "include"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read&lt;/strong&gt; results via &lt;code&gt;document.title&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  For Sites with Complex SPAs
&lt;/h3&gt;

&lt;p&gt;Some sites (Twitter, LinkedIn) are heavy SPAs where the API isn't as clean as Reddit's. For these, you can also use DOM manipulation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Click a button&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="tweetButton"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Fill an input&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="tweetTextarea"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Trigger React's change event&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;bubbles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 6: Chrome Multi-Profile Bug &amp;amp; The System Events Fallback
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Chrome on macOS supports multiple user profiles (e.g. "Haoyang", "Work", "Personal"). When a user has multiple profiles, &lt;strong&gt;AppleScript's &lt;code&gt;tell application "Google Chrome"&lt;/code&gt; can only see windows from ONE profile&lt;/strong&gt; — and it's not always the one you need.&lt;/p&gt;

&lt;p&gt;Symptoms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to return count of windows'&lt;/span&gt;
&lt;span class="c"&gt;# Returns: 0&lt;/span&gt;
&lt;span class="c"&gt;# But the user clearly has Chrome windows open!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user sees multiple Chrome windows with tabs, logged into various sites. But AppleScript reports 0 windows. This happens because Chrome's AppleScript interface only exposes windows from one profile context.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Discovery
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;tell application "Google Chrome"&lt;/code&gt; is blind, &lt;strong&gt;System Events can see ALL Chrome windows&lt;/strong&gt; regardless of profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "System Events"
    tell process "Google Chrome"
        return name of window 1
    end tell
end tell'&lt;/span&gt;
&lt;span class="c"&gt;# Returns: "Reddit - The heart of the internet - Google Chrome - Haoyang"&lt;/span&gt;
&lt;span class="c"&gt;# ^^^^ System Events sees it!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Solution: Console + Clipboard
&lt;/h3&gt;

&lt;p&gt;Since we can't use &lt;code&gt;execute javascript&lt;/code&gt; on an invisible-to-AppleScript window, we use a keyboard-based approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Copy&lt;/strong&gt; JavaScript code to clipboard via &lt;code&gt;pbcopy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open&lt;/strong&gt; Chrome Console via keyboard shortcut &lt;code&gt;Cmd+Option+J&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select all&lt;/strong&gt; in console &lt;code&gt;Cmd+A&lt;/code&gt; (clear previous)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paste&lt;/strong&gt; from clipboard &lt;code&gt;Cmd+V&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute&lt;/strong&gt; by pressing &lt;code&gt;Enter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Close&lt;/strong&gt; console &lt;code&gt;Cmd+Option+J&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read&lt;/strong&gt; &lt;code&gt;document.title&lt;/code&gt; via System Events&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Copy JS to clipboard (use python3 to avoid shell escaping)&lt;/span&gt;
python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import subprocess
js = '''(async()=&amp;gt;{
    let r = await fetch('/api/me.json', {credentials: 'include'});
    let d = await r.json();
    document.title = 'USER:' + JSON.stringify({name: d.data.name, karma: d.data.total_karma});
})()'''
subprocess.run(['pbcopy'], input=js.encode(), check=True)
"&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Open console, paste, execute, close console&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "System Events"
    tell process "Google Chrome"
        set frontmost to true
        delay 0.3
        -- Open JS console (Cmd+Option+J)
        key code 38 using {command down, option down}
        delay 1
        -- Select all (clear previous code)
        keystroke "a" using {command down}
        delay 0.2
        -- Paste JS from clipboard
        keystroke "v" using {command down}
        delay 0.5
        -- Press Enter to execute
        key code 36
        delay 0.3
        -- Close console
        key code 38 using {command down, option down}
    end tell
end tell'&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: Wait and read result from window title&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;3
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "System Events"
    tell process "Google Chrome"
        return name of window 1
    end tell
end tell'&lt;/span&gt;
&lt;span class="c"&gt;# Returns: "USER:{"name":"BP041","karma":37} - Google Chrome - Haoyang"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  POST Example (Posting a Reddit Comment)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import subprocess
js = '''(async()=&amp;gt;{
    let me = await fetch('/api/me.json', {credentials: 'include'}).then(r=&amp;gt;r.json());
    let uh = me.data.modhash;
    let body = new URLSearchParams({
        thing_id: 't3_1qzhcyg',
        text: 'Your comment text here',
        uh: uh,
        api_type: 'json'
    });
    let r = await fetch('/api/comment', {
        method: 'POST',
        credentials: 'include',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        body: body.toString()
    });
    let d = await r.json();
    let ok = d.json &amp;amp;&amp;amp; d.json.errors.length === 0;
    document.title = 'RESULT:' + (ok ? 'SUCCESS' : 'FAIL:' + JSON.stringify(d.json.errors));
})()'''
subprocess.run(['pbcopy'], input=js.encode(), check=True)
"&lt;/span&gt;

osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "System Events"
    tell process "Google Chrome"
        set frontmost to true
        delay 0.3
        key code 38 using {command down, option down}
        delay 1
        keystroke "a" using {command down}
        delay 0.2
        keystroke "v" using {command down}
        delay 0.5
        key code 36
        delay 0.3
        key code 38 using {command down, option down}
    end tell
end tell'&lt;/span&gt;

&lt;span class="nb"&gt;sleep &lt;/span&gt;5

osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tell application "System Events"
    tell process "Google Chrome"
        return name of window 1
    end tell
end tell'&lt;/span&gt;
&lt;span class="c"&gt;# Returns: "RESULT:SUCCESS - Google Chrome - Haoyang"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auto-Detection: Which Method to Use
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Detect if Method 1 or Method 2 is needed&lt;/span&gt;
&lt;span class="nv"&gt;WINDOWS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to return count of windows'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WINDOWS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WINDOWS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Method 1: AppleScript execute javascript (direct, fast)"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Check if System Events can see Chrome windows&lt;/span&gt;
    &lt;span class="nv"&gt;SE_WINDOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "System Events" to tell process "Google Chrome" to return name of window 1'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SE_WINDOW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Method 2: System Events + Console + Clipboard (multi-profile fallback)"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No Chrome windows found at all"&lt;/span&gt;
    &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Happens
&lt;/h3&gt;

&lt;p&gt;Chrome's AppleScript dictionary (&lt;code&gt;Google Chrome.sdef&lt;/code&gt;) exposes windows per-profile. When Chrome starts, it associates AppleScript access with one profile context. Windows from other profiles exist at the macOS level (visible to System Events, Accessibility API, and the user) but are invisible to &lt;code&gt;tell application "Google Chrome"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is likely a Chrome bug, not a feature. But the System Events fallback is reliable and battle-tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Method 1: execute javascript&lt;/th&gt;
&lt;th&gt;Method 2: System Events + Console&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Fast (~2s per call)&lt;/td&gt;
&lt;td&gt;Slower (~5s per call)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliability&lt;/td&gt;
&lt;td&gt;High (when it works)&lt;/td&gt;
&lt;td&gt;High (always works)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-profile&lt;/td&gt;
&lt;td&gt;Breaks&lt;/td&gt;
&lt;td&gt;Works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escaping&lt;/td&gt;
&lt;td&gt;Painful (use JXA)&lt;/td&gt;
&lt;td&gt;Easy (use python3 + pbcopy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read results&lt;/td&gt;
&lt;td&gt;&lt;code&gt;title of active tab&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;name of window 1&lt;/code&gt; (includes " - Google Chrome - Profile")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prerequisites&lt;/td&gt;
&lt;td&gt;Allow JS from Apple Events&lt;/td&gt;
&lt;td&gt;Allow JS from Apple Events + Console access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Part 7: Limitations and Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Known Limitations
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limitation&lt;/th&gt;
&lt;th&gt;Workaround&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS only&lt;/td&gt;
&lt;td&gt;No workaround — AppleScript is Apple-only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chrome must be running&lt;/td&gt;
&lt;td&gt;Script can launch Chrome: &lt;code&gt;open -a "Google Chrome"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Must be logged in already&lt;/td&gt;
&lt;td&gt;Log in manually once, session persists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;document.title size limit (~few KB)&lt;/td&gt;
&lt;td&gt;Chunk large responses, or use multiple calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async results need sleep/polling&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sleep 2&lt;/code&gt; between execute and read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AppleScript string escaping is painful&lt;/td&gt;
&lt;td&gt;Use JXA (JavaScript for Automation) instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One command at a time per tab&lt;/td&gt;
&lt;td&gt;Use multiple tabs for parallelism&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Security Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Allow JavaScript from Apple Events" means ANY AppleScript can execute JS in your Chrome — be aware of this if you run untrusted scripts&lt;/li&gt;
&lt;li&gt;Your browser cookies are used directly — treat this script with the same security as your browser&lt;/li&gt;
&lt;li&gt;Don't hardcode sensitive data in scripts — use environment variables&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rate Limiting Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Wait 2+ seconds between API calls&lt;/li&gt;
&lt;li&gt;Don't post more than 5 comments per session&lt;/li&gt;
&lt;li&gt;Space out sessions (don't run hourly)&lt;/li&gt;
&lt;li&gt;Monitor for 429/403 responses and back off&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 8: Quick Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cheat Sheet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Is Chrome running?&lt;/span&gt;
pgrep &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"Google Chrome"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Running"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Not running"&lt;/span&gt;

&lt;span class="c"&gt;# Launch Chrome&lt;/span&gt;
open &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"Google Chrome"&lt;/span&gt;

&lt;span class="c"&gt;# Quit Chrome&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to quit'&lt;/span&gt;

&lt;span class="c"&gt;# Get current URL&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to return URL of active tab of first window'&lt;/span&gt;

&lt;span class="c"&gt;# Get page title&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to return title of active tab of first window'&lt;/span&gt;

&lt;span class="c"&gt;# Navigate&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to tell active tab of first window to set URL to "https://example.com"'&lt;/span&gt;

&lt;span class="c"&gt;# Execute JS (sync)&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to tell active tab of first window to execute javascript "1+1"'&lt;/span&gt;

&lt;span class="c"&gt;# Execute JS (async) — write to title, then read&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to tell active tab of first window to execute javascript "fetch(\"/api/data\").then(r=&amp;gt;r.json()).then(d=&amp;gt;{document.title=JSON.stringify(d)})"'&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;2
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to return title of active tab of first window'&lt;/span&gt;

&lt;span class="c"&gt;# New tab&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to tell first window to make new tab with properties {URL:"https://example.com"}'&lt;/span&gt;

&lt;span class="c"&gt;# Count tabs&lt;/span&gt;
osascript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'tell application "Google Chrome" to return count of tabs of first window'&lt;/span&gt;

&lt;span class="c"&gt;# JXA (for complex JS without escaping pain)&lt;/span&gt;
osascript &lt;span class="nt"&gt;-l&lt;/span&gt; JavaScript &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
var chrome = Application("Google Chrome");
var tab = chrome.windows[0].activeTab;
tab.execute({javascript: "document.title"});
'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Golden Rule
&lt;/h3&gt;

&lt;p&gt;If you can do it in Chrome DevTools Console, you can automate it with AppleScript. Same context, same cookies, same permissions. The website literally cannot tell the difference.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Discovered on 2026-02-09 while trying to automate Reddit posting. After Playwright, Selenium, Chrome DevTools Protocol, and curl all failed to bypass Reddit's anti-bot detection, AppleScript Chrome control worked on the first try — because it doesn't try to fake a browser. It IS the browser.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Multi-Provider Social Posting API with Automatic Fallback (Open Source)</title>
      <dc:creator>Haoyang Pang</dc:creator>
      <pubDate>Wed, 07 Jan 2026 17:46:21 +0000</pubDate>
      <link>https://dev.to/haoyang_pang_a9f08cdb0b6c/building-a-multi-provider-social-posting-api-with-automatic-fallback-open-source-h28</link>
      <guid>https://dev.to/haoyang_pang_a9f08cdb0b6c/building-a-multi-provider-social-posting-api-with-automatic-fallback-open-source-h28</guid>
      <description>&lt;p&gt;I built a Python library that handles social media posting across multiple providers with automatic fallback. If one API fails, it seamlessly switches to the backup. Here's why and how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When building a social media scheduling tool, I hit a common pain point: &lt;strong&gt;API reliability&lt;/strong&gt;. Each social posting service has its quirks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate limits&lt;/li&gt;
&lt;li&gt;Occasional downtime&lt;/li&gt;
&lt;li&gt;Different pricing tiers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relying on a single provider felt risky. What if they go down right when your scheduled post needs to go out?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Multi-Provider Architecture
&lt;/h2&gt;

&lt;p&gt;I created &lt;code&gt;social-posting-api&lt;/code&gt; - a unified interface that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tries the primary provider first&lt;/strong&gt; (PostForMe - cheaper, volume-friendly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatically falls back&lt;/strong&gt; to LATE if the primary fails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uses a consistent API&lt;/strong&gt; regardless of which provider succeeds
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;social_posting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SocialPostingClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SocialPostingClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Posts via PostForMe, falls back to LATE if needed
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from my multi-provider API!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;platforms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linkedin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instagram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;media_urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/image.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Posted via: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# "PostForMe" or "LATE"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Automatic Fallback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Try each provider with fallback
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;platforms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;media_urls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="n"&gt;last_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PostResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All providers failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;last_error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Unified Account Management
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Get connected accounts from any provider
&lt;/span&gt;&lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_accounts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Media Upload with Presigned URLs
&lt;/h3&gt;

&lt;p&gt;Both providers use presigned URL patterns for secure media uploads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_media&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# Download image
&lt;/span&gt;    &lt;span class="n"&gt;img_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

    &lt;span class="c1"&gt;# Get presigned upload URL from provider
&lt;/span&gt;    &lt;span class="n"&gt;upload_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_get_presigned_url&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Upload directly to cloud storage
&lt;/span&gt;    &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hosted_url&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Image Carousel Support
&lt;/h3&gt;

&lt;p&gt;Instagram carousels work seamlessly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check out these photos!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;platforms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instagram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;media_urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/photo1.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/photo2.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/photo3.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Supported Platforms
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Twitter/X&lt;/li&gt;
&lt;li&gt;LinkedIn&lt;/li&gt;
&lt;li&gt;Instagram (requires media)&lt;/li&gt;
&lt;li&gt;Facebook&lt;/li&gt;
&lt;li&gt;TikTok&lt;/li&gt;
&lt;li&gt;Threads&lt;/li&gt;
&lt;li&gt;Bluesky&lt;/li&gt;
&lt;li&gt;Pinterest&lt;/li&gt;
&lt;li&gt;YouTube&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/PHY041/social-posting-api
&lt;span class="nb"&gt;cd &lt;/span&gt;social-posting-api
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set your API keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;POSTFORME_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key_here
&lt;span class="nv"&gt;LATE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python social_posting.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Open Source?
&lt;/h2&gt;

&lt;p&gt;I'm building &lt;a href="https://canmarket.ai" rel="noopener noreferrer"&gt;CanMarket&lt;/a&gt;, an AI-powered marketing tool. This social posting module is one piece of the puzzle, and I figured others might find it useful.&lt;/p&gt;

&lt;p&gt;If you're building anything that needs reliable social media posting, feel free to use this as a starting point!&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/PHY041/social-posting-api" rel="noopener noreferrer"&gt;https://github.com/PHY041/social-posting-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostForMe API&lt;/strong&gt;: &lt;a href="https://postforme.dev" rel="noopener noreferrer"&gt;https://postforme.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LATE API&lt;/strong&gt;: &lt;a href="https://getlate.dev" rel="noopener noreferrer"&gt;https://getlate.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Got questions or suggestions? Drop a comment below!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>api</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
