Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{
"name": "typefully",
"source": "./skills/typefully",
"description": "Draft, schedule, and manage social media posts across X, LinkedIn, Threads, Bluesky, and Mastodon",
"description": "Draft, schedule, and manage social media posts across X, LinkedIn, Threads, Bluesky, Mastodon, and Substack Notes",
"strict": false,
"skills": ["./skills/typefully"]
}
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Overview

This repository contains AI agent skills for Typefully - markdown files that give AI agents specialized workflows for drafting, scheduling, and managing social media posts across X, LinkedIn, Threads, Bluesky, and Mastodon.
This repository contains AI agent skills for Typefully - markdown files that give AI agents specialized workflows for drafting, scheduling, and managing social media posts across X, LinkedIn, Threads, Bluesky, Mastodon, and Substack Notes.

## Repository Structure

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Version](https://img.shields.io/badge/version-1.0.0-green.svg)]()
[![Typefully API](https://img.shields.io/badge/Typefully-API-3B9AF8)](https://typefully.com/docs/api)

AI agent skills for drafting, scheduling, and managing social media posts across X, LinkedIn, Threads, Bluesky, and Mastodon. Give your AI agent the ability to manage your social media scheduling directly from your IDE or terminal.
AI agent skills for drafting, scheduling, and managing social media posts across X, LinkedIn, Threads, Bluesky, Mastodon, and Substack Notes. Give your AI agent the ability to manage your social media scheduling directly from your IDE or terminal.

Built on the [Typefully API](https://typefully.com/docs/api). [Typefully](https://typefully.com) is a writing and scheduling app used by 200k+ top creators and teams to grow on X, LinkedIn, Threads, and Bluesky.

Expand Down Expand Up @@ -95,6 +95,7 @@ Then test it in your agent to verify the latest local changes behave as expected
- Threads
- Bluesky
- Mastodon
- Substack Notes

## Troubleshooting

Expand Down
8 changes: 8 additions & 0 deletions skills/typefully/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable user-facing changes to the Typefully skill and its CLI are documente

The format is based on Keep a Changelog.

## 2026-07-29

### Added

- Substack Notes support: target it with `--platform substack` on `drafts:create` and `drafts:update`, combine it with other platforms in one draft, and filter comments with `--platform substack`. When connected, it is also included in `--all` and in platform auto-selection.
- Substack Notes take a single post per draft: the CLI now rejects thread content (`---` separators or `--append`) targeting `substack` with a clear error before calling the API.
- Link-preview cards now also publish on Substack Notes: API-created notes containing a URL get a rich link card, matching editor behavior (the card is skipped when the note has images). `--hide-link-preview` now supports `substack` alongside LinkedIn and Threads.

## 2026-07-27

### Added
Expand Down
24 changes: 14 additions & 10 deletions skills/typefully/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ name: typefully
description: >
Create, schedule, and manage social media posts via Typefully. ALWAYS use this
skill when asked to draft, schedule, post, or check tweets, posts, threads, or
social media content for Twitter/X, LinkedIn, Threads, Bluesky, or Mastodon,
or when the user drops a Typefully draft URL such as
social media content for Twitter/X, LinkedIn, Threads, Bluesky, Mastodon, or
Substack Notes, or when the user drops a Typefully draft URL such as
https://typefully.com/?a=<social_set_id>&d=<draft_id>.
last-updated: 2026-07-09
last-updated: 2026-07-29
allowed-tools: Bash(./scripts/typefully.js:*)
---

# Typefully Skill

Create, schedule, and publish social media content across X, LinkedIn, Threads, Bluesky, and Mastodon using [Typefully](https://typefully.com). Run everything through `./scripts/typefully.js` (Node.js 18+, no dependencies). All commands output JSON.
Create, schedule, and publish social media content across X, LinkedIn, Threads, Bluesky, Mastodon, and Substack Notes using [Typefully](https://typefully.com). Run everything through `./scripts/typefully.js` (Node.js 18+, no dependencies). All commands output JSON.

> **Script paths** below are relative to this skill's directory. Resolve them based on where the skill is installed.
>
Expand Down Expand Up @@ -71,8 +71,9 @@ To decide which social set to use:
./scripts/typefully.js drafts:create --text "Your post"
```

- If `--platform` is omitted, the first connected platform is auto-selected. Named platforms: `x`, `linkedin`, `threads`, `bluesky`, `mastodon`, `x_article`.
- Split a thread with `---` on its own line.
- If `--platform` is omitted, the first connected platform is auto-selected. Named platforms: `x`, `linkedin`, `threads`, `bluesky`, `mastodon`, `substack` (Substack Notes), `x_article`.
- Split a thread with `---` on its own line. Exception: `substack` takes a single post per draft — no threads. For a thread plus a Substack Note, use the one-draft pattern below to give `substack` its own single-post content.
- Substack Notes media: images and GIFs only (up to 6) — video is rejected.
- Attach media with `--media`, tags with `--tags`, internal notes with `--scratchpad`, an internal name with `--title`.
- Read content from a file with `--file ./post.txt` instead of `--text`.

Expand All @@ -95,6 +96,8 @@ When content should differ per platform (e.g. an X thread plus a tailored Linked
Never create multiple drafts unless the user explicitly wants separate drafts per platform.

> `--all` excludes `x_article`. X Articles are standalone and cannot be combined with any other platform — see [`references/platforms/x-articles.md`](references/platforms/x-articles.md).
>
> `--all` includes `substack` when connected. Since Substack Notes take a single post, `--all` with thread content errors — exclude `substack` via `--platform`, or give it its own single-post content with `drafts:update`.

### Scratchpad notes

Expand All @@ -106,9 +109,9 @@ When the user asks to add notes, ideas, or context to a draft, use `--scratchpad

### Link previews

When a post contains a URL, Typefully automatically fetches Open Graph metadata for the last URL in the text and publishes a rich link-preview card on **LinkedIn, Threads, and Bluesky**. No flag is needed — it just works. X and Mastodon unfurl links themselves after publishing.
When a post contains a URL, Typefully automatically fetches Open Graph metadata for the last URL in the text and publishes a rich link-preview card on **LinkedIn, Threads, Bluesky, and Substack Notes**. No flag is needed — it just works. X and Mastodon unfurl links themselves after publishing. On Substack Notes the card is skipped when the note has images (images and the link card are mutually exclusive there).

To publish the URL as plain text with no card, pass `--hide-link-preview`. Suppression is supported on **LinkedIn and Threads only** (matching the Typefully editor); the flag errors if neither platform is targeted, and is ignored for other platforms in a mixed-platform draft:
To publish the URL as plain text with no card, pass `--hide-link-preview`. Suppression is supported on **LinkedIn, Threads, and Substack Notes only** (matching the Typefully editor); the flag errors if none of these platforms is targeted, and is ignored for other platforms in a mixed-platform draft:

```bash
./scripts/typefully.js drafts:create --platform linkedin,threads --text "Read this https://example.com" --hide-link-preview
Expand Down Expand Up @@ -137,6 +140,7 @@ To publish the URL as plain text with no card, pass `--hide-link-preview`. Suppr
|--------------|--------|
| "Draft a tweet about X" | `drafts:create --text "..."` |
| "Post this to LinkedIn" | `drafts:create --platform linkedin --text "..."` |
| "Post a Substack Note" | `drafts:create --platform substack --text "..."` (single post, no threads) |
| "Post to X and LinkedIn" (same content) | `drafts:create --platform x,linkedin --text "..."` |
| "X thread + tailored LinkedIn post" | One draft, then `drafts:update` to add the platform |
| "What's scheduled?" / "Recent posts?" | `drafts:list --status scheduled` / `--status published` |
Expand Down Expand Up @@ -194,7 +198,7 @@ Add any of these flags to a `drafts:create` or `drafts:update` command. The **Ap
| `--scratchpad "<notes>"` | Attach internal notes (see [Scratchpad notes](#scratchpad-notes)) | create, update |
| `--share` | Generate a public share URL | create, update |
| `--schedule <iso\|next-free-slot\|now>` | Schedule or reschedule the draft | create, update |
| `--hide-link-preview` | Suppress the link-preview card (LinkedIn/Threads only — see [Link previews](#link-previews)) | create, update |
| `--hide-link-preview` | Suppress the link-preview card (LinkedIn/Threads/Substack only — see [Link previews](#link-previews)) | create, update |
| `--exclude-comment-markers` | Render response without anchors (display only; validation still applies) | update |
| `--force-overwrite-comments` | Destructive last resort — see [`comments.md`](references/comments.md) | update |

Expand Down Expand Up @@ -292,7 +296,7 @@ Tags are scoped per social set — a tag in one social set doesn't appear in ano

### Character limits

X 280 · LinkedIn 3000 · Threads 500 · Bluesky 300 · Mastodon 500.
X 280 · LinkedIn 3000 · Threads 500 · Bluesky 300 · Mastodon 500 · Substack Notes 10000.

### Draft URLs

Expand Down
2 changes: 1 addition & 1 deletion skills/typefully/references/platforms/x-articles.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Use this guide when creating, updating, scheduling, publishing, or commenting on
## Core Rules

- Always use `--platform x_article`.
- `x_article` is standalone. Do not combine it with `x`, LinkedIn, Threads, Bluesky, Mastodon, or `--all`.
- `x_article` is standalone. Do not combine it with `x`, LinkedIn, Threads, Bluesky, Mastodon, Substack Notes, or `--all`.
- Use `--content-markdown <markdown>` for article content.
- Use `--cover-media-id <media_id>` to set a cover image.
- Use `--cover-media-id null` on update to remove a cover image.
Expand Down
28 changes: 21 additions & 7 deletions skills/typefully/scripts/typefully.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const LOCAL_CONFIG_FILE = path.join(LOCAL_CONFIG_DIR, 'config.json');
const API_KEY_URL = 'https://typefully.com/?settings=api';
const AUTH_FAILURE_MESSAGE = `Authentication failed: Typefully API key is invalid, expired, or lacks access. Run 'typefully.js setup' to configure a valid key.`;
const X_ARTICLE_PLATFORM = 'x_article';
const POST_PLATFORM_ORDER = ['x', 'linkedin', 'threads', 'bluesky', 'mastodon'];
const SUBSTACK_PLATFORM = 'substack';
const POST_PLATFORM_ORDER = ['x', 'linkedin', 'threads', 'bluesky', 'mastodon', 'substack'];
const X_ARTICLE_POST_ONLY_FLAGS = [
['text', '--text'],
['file', '--file'],
Expand Down Expand Up @@ -513,7 +514,7 @@ function validateXOnlyPostOptions(platformList, { quotePostUrl, disclosures }) {
}

// Platforms where Typefully lets you suppress the link-preview card (matches the web editor).
const HIDE_LINK_PREVIEW_PLATFORMS = ['linkedin', 'threads'];
const HIDE_LINK_PREVIEW_PLATFORMS = ['linkedin', 'threads', 'substack'];

function getHideLinkPreviewFromParsed(parsed) {
return Boolean(parsed['hide-link-preview'] || parsed.hide_link_preview);
Expand All @@ -526,7 +527,16 @@ function addHideLinkPreview(posts, hideLinkPreview) {

function validateHideLinkPreviewOption(platformList, hideLinkPreview) {
if (hideLinkPreview && !platformList.some(p => HIDE_LINK_PREVIEW_PLATFORMS.includes(p))) {
error('--hide-link-preview is only supported for LinkedIn and Threads posts. Include linkedin or threads in --platform or remove the flag.');
error('--hide-link-preview is only supported for LinkedIn, Threads, and Substack posts. Include linkedin, threads, or substack in --platform or remove the flag.');
}
}

// Substack Notes accepts a single post per draft — the API rejects longer
// posts arrays with a 422, so fail fast with a clearer message.
function validateSubstackSinglePost(platformsObj) {
const posts = platformsObj[SUBSTACK_PLATFORM]?.posts;
if (Array.isArray(posts) && posts.length > 1) {
error('substack (Substack Notes) supports a single post per draft — threads are not supported. Use a single post, or target other platforms with --platform.');
}
}

Expand Down Expand Up @@ -1376,6 +1386,8 @@ async function cmdDraftsCreate(args) {

platformsObj[platform] = platformConfig;
}

validateSubstackSinglePost(platformsObj);
}

const body = { platforms: platformsObj };
Expand Down Expand Up @@ -1554,7 +1566,7 @@ async function cmdDraftsUpdate(args) {
existing.platforms[p].posts.length > 0
);
if (targets.length === 0) {
error('Cannot apply --hide-link-preview because this draft has no existing LinkedIn or Threads posts');
error('Cannot apply --hide-link-preview because this draft has no existing LinkedIn, Threads, or Substack posts');
}
postsArray = null;
platformList = targets;
Expand All @@ -1576,6 +1588,7 @@ async function cmdDraftsUpdate(args) {
posts: platformPosts,
};
}
validateSubstackSinglePost(platformsObj);
body.platforms = platformsObj;
}

Expand Down Expand Up @@ -2188,9 +2201,10 @@ COMMANDS:
--use-default Required when using default social set with single arg

drafts:create [social_set_id] [options] Create a new draft (uses default if ID omitted)
--platform <platforms> Comma-separated: x,linkedin,threads,bluesky,mastodon
--platform <platforms> Comma-separated: x,linkedin,threads,bluesky,mastodon,substack
or standalone x_article
(auto-selects first connected platform if omitted)
(auto-selects first connected platform if omitted;
substack = Substack Notes, single post only)
--all Post to all connected post platforms (excludes x_article)
--text <text> Post content (use --- on its own line for threads)
--file, -f <path> Read content from file instead of --text
Expand Down Expand Up @@ -2262,7 +2276,7 @@ COMMANDS:

comments:list <draft_id> [options] List comment threads on a draft
--social-set-id <id> Social set (uses default if omitted)
--platform <platform> Filter by platform: x, linkedin, threads, bluesky, mastodon, x_article
--platform <platform> Filter by platform: x, linkedin, threads, bluesky, mastodon, substack, x_article
--status <status> Filter by: unresolved (default), resolved, all
--limit <n> Max results (default: 10, max: 50)
--offset <n> Skip first N results
Expand Down
Loading
Loading