superx-cli 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +4 -3
- package/dist/index.js +74 -25
- package/package.json +1 -1
- package/skills/superx/SKILL.md +2 -1
- package/skills/superx/references/commands.md +12 -4
- package/skills/superx/references/skills/viral-format-remix/recipe.md +1 -1
- package/skills/superx/references/skills/worth-a-reply/card.json +22 -0
- package/skills/superx/references/skills/worth-a-reply/recipe.md +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 (2026-10-07)
|
|
4
|
+
|
|
5
|
+
- `posts:remix` gains `--author-handle <@handle>`, the handle that originally posted the text. Pass the account's own handle when remixing one of its own posts: the remix then keeps that post's subject and products instead of re-grounding them in the profile. The API (`author_handle` on `POST /v1/posts/remix`) and MCP `remix_post` take the same field
|
|
6
|
+
- Deleting a scheduled post (`scheduled:delete`, `scheduled:bulk-delete`, MCP `delete_scheduled_post` / `bulk_delete_scheduled_posts`) or moving one to draft (`scheduled:update --status draft`) now re-points scheduled posts that quote it to the post it quoted when that is one of your own posts (still scheduled earlier, or already published); otherwise they lose the quote
|
|
7
|
+
- Image-only posts: `scheduled:create` and `posts:publish` take `--media <key>` without `--text`, `scheduled:update` takes `--text "" --media <key>` (it replaces the whole post, so the empty text is explicit), and `--parts-json` parts may leave out `text` when they carry `media`. The API and the MCP `schedule_post`, `update_scheduled_post` and `publish_post` tools accept a part with media and no text the same way. Nothing accepted before changes; a post with no text and no media at all is still refused
|
|
8
|
+
- `inspiration:search` gains `--min-outlier-score <n>` (0-1000) and `--min-length <n>`. `outlier_score` is a post's engagement relative to what is expected for its author's follower count: 1.0 = expected, 3 = three times expected. `--min-outlier-score 3` keeps only posts at or above 3, and posts whose author's follower count is unknown are left out. `--min-length` keeps posts with at least that many characters
|
|
9
|
+
- MCP `find_inspiration` gained the same filters as the CLI and API: `min_followers` / `max_followers`, `min_outlier_score`, `sort` (including `outlier`), `min_reposts`, `min_replies`, `min_bookmarks`, `min_impressions`, `min_length`, `exclude_topics` and `page`. Each post now returns `outlier_score` and `bookmarks`, and the result carries `page` and `has_more`. Reconnect the server to see the new schema
|
|
10
|
+
|
|
11
|
+
## 0.7.1 (2026-09-18)
|
|
12
|
+
|
|
13
|
+
- New skill Worth a Reply (worth-a-reply): `posts:triage` reads a topic, then the skill hands back the posts worth replying to, ordered by how much room there is to add something in a reply, each with its link, one line on why and the angle a reply could take. Skim posts come back as one-liners and the Pass pile stays out unless the person asks. It drafts nothing until they pick a post. Skills are now 32.
|
|
14
|
+
|
|
15
|
+
## 0.7.0 (2026-09-18)
|
|
16
|
+
|
|
17
|
+
- `posts:triage "<query>"` searches recent public posts on a topic and sorts them into Read, Pass or Not sure, up to 40 posts a run. Every post comes back with its lane, a `pct`, a kind (insight, story, data, progress, news, question, intro, launch, joke or opinion) and the yes/no answers behind the call: specific and informative, says something new, engagement bait, a plug, a platitude, and room to add something in a reply. `pct` is how CLEAR the call was and not how good the post is: on `read` and `pass` it runs 50 to 99, so a `pass` at 99 is confidently not worth reading, and only on `unsure` is it the raw worth-reading score. Read it with the lane, never alone. `--days 1-7` sets how far back to look (default 3), `--account` attributes the run. What is judged is the TEXT of a post and never who wrote it, so it is a call on the writing, not a rating of a person. The search asks for original posts, English only, with a floor of 30 likes, and never returns replies or reposts. 2 credits a run, a daily cap per plan, and 1 live lookup for a single-word query or 2 for a multi-word one. Nothing is posted, saved or sent
|
|
18
|
+
- MCP `triage_posts` does the same thing with the same fields. Reconnect the server to pick it up
|
|
19
|
+
|
|
3
20
|
## 0.6.0 (2026-09-18)
|
|
4
21
|
|
|
5
22
|
- Skills layout: the skill now lives at `skills/superx/`, one folder per SuperX skill under `skills/superx/references/skills/<id>/` (`recipe.md` for the agent, `card.json` for the app). `SKILL.md` is route-first and under 500 lines: the command list moved verbatim to `references/commands.md`, the growth strategy guide to `references/growth-strategy.md`, and the skills index is generated. The root `SKILL.md`, `PLAYBOOK.md` and `PLAYBOOKS.md` are gone, and "playbook" is now only the name of the SuperX blog freebie
|
package/README.md
CHANGED
|
@@ -126,9 +126,10 @@ Every stored reply your audience has sent you across all your posts: the reply t
|
|
|
126
126
|
superx inspiration:search "build in public" --limit 10
|
|
127
127
|
superx inspiration:search "indie hackers" --sort outlier --min-likes 500
|
|
128
128
|
superx inspiration:search "AI tools" --min-followers 1000 --max-followers 50000
|
|
129
|
+
superx inspiration:search "SaaS pricing" --min-outlier-score 3 --min-length 100
|
|
129
130
|
```
|
|
130
131
|
|
|
131
|
-
Searches a library of 50M+ real high-performing posts by topic. Options: `--sort relevant|recent|likes|reposts|impressions|outlier`, `--min-likes/--min-reposts/--min-replies/--min-bookmarks/--min-impressions`, `--min-followers/--max-followers` (author size), `--since/--until` (UTC ISO-8601), `--lang` (default en), `--exclude-topics "crypto,politics"`, `--limit` (max 50), `--page` (1-7). Each result carries an `outlier_score`: how far the post outperformed the norm for its author's follower tier. Results are relevance-ranked, strongest matches first, with weak and promotional matches filtered out, so a page may return fewer than `--limit` posts. Use them for structures and hooks to remix, never to copy.
|
|
132
|
+
Searches a library of 50M+ real high-performing posts by topic. Options: `--sort relevant|recent|likes|reposts|impressions|outlier`, `--min-likes/--min-reposts/--min-replies/--min-bookmarks/--min-impressions`, `--min-length` (characters), `--min-followers/--max-followers` (author size), `--min-outlier-score` (0-1000), `--since/--until` (UTC ISO-8601), `--lang` (default en), `--exclude-topics "crypto,politics"`, `--limit` (max 50), `--page` (1-7). Each result carries an `outlier_score`: how far the post outperformed the norm for its author's follower tier (1.0 = expected, 3 = three times expected). `--min-outlier-score` keeps only posts at or above that score. Results are relevance-ranked, strongest matches first, with weak and promotional matches filtered out, so a page may return fewer than `--limit` posts. Use them for structures and hooks to remix, never to copy.
|
|
132
133
|
|
|
133
134
|
### Contacts (who engages with you)
|
|
134
135
|
|
|
@@ -206,7 +207,7 @@ superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" --idempotency-
|
|
|
206
207
|
|
|
207
208
|
superx scheduled:list --status draft,scheduled # draft | scheduled | sent | error
|
|
208
209
|
superx scheduled:list --from "2026-08-01T00:00:00Z" --to "2026-08-08T00:00:00Z"
|
|
209
|
-
superx scheduled:delete <post-id>
|
|
210
|
+
superx scheduled:delete <post-id> # posts quoting it re-point to its quoted post if yours (scheduled earlier or published), else lose the quote
|
|
210
211
|
```
|
|
211
212
|
|
|
212
213
|
Images attach in two steps: upload, then reference the `object_key`.
|
|
@@ -217,7 +218,7 @@ superx scheduled:create --text "Chart of the week" --media "$KEY" --alt-text "We
|
|
|
217
218
|
superx scheduled:create --parts-json '[{"text":"1/ Hook","media":[{"object_key":"'"$KEY"'"}]},{"text":"2/ Detail"}]'
|
|
218
219
|
```
|
|
219
220
|
|
|
220
|
-
`media:upload` accepts JPG, PNG, and WEBP up to 5MB and GIF up to 15MB; a post part carries up to 4 images or exactly 1 GIF. `--media` takes a comma list of keys; `--alt-text` (max 1,000 chars) works with a single key, and `--parts-json` covers threads and per-image alt text. Uploads are capped at 100 per day and expire after 24 hours if never attached. Video is not supported yet.
|
|
221
|
+
`media:upload` accepts JPG, PNG, and WEBP up to 5MB and GIF up to 15MB; a post part carries up to 4 images or exactly 1 GIF. `--media` takes a comma list of keys; `--alt-text` (max 1,000 chars) works with a single key, and `--parts-json` covers threads and per-image alt text. A post can be image-only: `--media` without `--text` on `scheduled:create` and `posts:publish`, `--text "" --media <key>` on `scheduled:update`, or a `--parts-json` part with `media` and no `text`. Uploads are capped at 100 per day and expire after 24 hours if never attached. Video is not supported yet.
|
|
221
222
|
|
|
222
223
|
Drafts can carry organizer fields: `--title` (max 300 chars) and `--scratchpad` (max 30,000 chars) are shown in the SuperX app and never posted; `--tag <id>` (repeatable, max 20) attaches tags from `tags:list`.
|
|
223
224
|
|
package/dist/index.js
CHANGED
|
@@ -201,6 +201,10 @@ var SuperXAPI = class {
|
|
|
201
201
|
async viralScore(body) {
|
|
202
202
|
return (await this.request("/posts/viral-score", { method: "POST", body })).json;
|
|
203
203
|
}
|
|
204
|
+
/** Sort recent public posts on a topic into Read, Pass or Not sure. */
|
|
205
|
+
async triage(body) {
|
|
206
|
+
return (await this.request("/posts/triage", { method: "POST", body })).json;
|
|
207
|
+
}
|
|
204
208
|
/** Draft ONE reply to a post. Text only: a person posts it. */
|
|
205
209
|
async draftReply(body) {
|
|
206
210
|
return (await this.request("/engage/reply-draft", { method: "POST", body })).json;
|
|
@@ -796,6 +800,7 @@ async function postsRemix(argv) {
|
|
|
796
800
|
closeness: argv.closeness
|
|
797
801
|
};
|
|
798
802
|
if (argv.instructions) body.instructions = argv.instructions;
|
|
803
|
+
if (argv.authorHandle) body.author_handle = argv.authorHandle;
|
|
799
804
|
if (argv.account) body.account_id = argv.account;
|
|
800
805
|
const api = new SuperXAPI(getConfig());
|
|
801
806
|
printJson(await api.remixPost(body));
|
|
@@ -811,6 +816,23 @@ async function postsViralScore(argv) {
|
|
|
811
816
|
const api = new SuperXAPI(getConfig());
|
|
812
817
|
printJson(await api.viralScore(body));
|
|
813
818
|
}
|
|
819
|
+
async function postsTriage(argv) {
|
|
820
|
+
if (!argv.query || !argv.query.trim()) {
|
|
821
|
+
note('Provide a query: superx posts:triage "coding agents".');
|
|
822
|
+
process.exit(1);
|
|
823
|
+
}
|
|
824
|
+
if (argv.days !== void 0) {
|
|
825
|
+
if (!Number.isInteger(argv.days) || argv.days < 1 || argv.days > 7) {
|
|
826
|
+
note("--days must be a whole number of days between 1 and 7.");
|
|
827
|
+
process.exit(1);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const body = { query: argv.query };
|
|
831
|
+
if (argv.days !== void 0) body.max_age_days = argv.days;
|
|
832
|
+
if (argv.account) body.account_id = argv.account;
|
|
833
|
+
const api = new SuperXAPI(getConfig());
|
|
834
|
+
printJson(await api.triage(body));
|
|
835
|
+
}
|
|
814
836
|
|
|
815
837
|
// src/commands/tools.ts
|
|
816
838
|
async function toolsInlineEdit(argv) {
|
|
@@ -853,6 +875,8 @@ async function inspirationSearch(argv) {
|
|
|
853
875
|
min_impressions: argv.minImpressions,
|
|
854
876
|
min_followers: argv.minFollowers,
|
|
855
877
|
max_followers: argv.maxFollowers,
|
|
878
|
+
min_outlier_score: argv.minOutlierScore,
|
|
879
|
+
min_length: argv.minLength,
|
|
856
880
|
since: argv.since,
|
|
857
881
|
until: argv.until,
|
|
858
882
|
lang: argv.lang,
|
|
@@ -1538,8 +1562,10 @@ function parsePartsJson(raw) {
|
|
|
1538
1562
|
process.exit(1);
|
|
1539
1563
|
throw new Error("unreachable");
|
|
1540
1564
|
}
|
|
1541
|
-
if (!Array.isArray(parsed) || parsed.some(
|
|
1542
|
-
|
|
1565
|
+
if (!Array.isArray(parsed) || parsed.some(
|
|
1566
|
+
(p) => !p || typeof p !== "object" || typeof p.text !== "string" && !(p.text === void 0 && Array.isArray(p.media) && p.media.length > 0)
|
|
1567
|
+
)) {
|
|
1568
|
+
note("--parts-json must be an array of { text, media? } objects (text may be left out on a part with media).");
|
|
1543
1569
|
process.exit(1);
|
|
1544
1570
|
}
|
|
1545
1571
|
return parsed;
|
|
@@ -1661,21 +1687,21 @@ async function scheduledCreate(argv) {
|
|
|
1661
1687
|
note("Use exactly one of --text (single post), --part (thread), or --parts-json.");
|
|
1662
1688
|
process.exit(1);
|
|
1663
1689
|
}
|
|
1664
|
-
if (sourceCount === 0) {
|
|
1690
|
+
if (sourceCount === 0 && argv.media === void 0) {
|
|
1665
1691
|
note("Provide --text for a single post, --part flags for a thread, or --parts-json.");
|
|
1666
1692
|
process.exit(1);
|
|
1667
1693
|
}
|
|
1668
|
-
if (argv.media !== void 0 &&
|
|
1694
|
+
if (argv.media !== void 0 && (parts.length > 0 || argv["parts-json"] !== void 0)) {
|
|
1669
1695
|
note("--media applies to the --text single-post form. For threads, put media in --parts-json.");
|
|
1670
1696
|
process.exit(1);
|
|
1671
1697
|
}
|
|
1672
1698
|
const body = {};
|
|
1673
1699
|
if (argv["parts-json"] !== void 0) {
|
|
1674
1700
|
body.parts = parsePartsJson(argv["parts-json"]);
|
|
1675
|
-
} else if (argv.text) {
|
|
1701
|
+
} else if (argv.text || argv.media !== void 0) {
|
|
1676
1702
|
const media = mediaFromFlags(argv.media, argv["alt-text"]);
|
|
1677
1703
|
if (media) {
|
|
1678
|
-
body.parts = [{ text: argv.text, media }];
|
|
1704
|
+
body.parts = [{ text: argv.text || "", media }];
|
|
1679
1705
|
} else {
|
|
1680
1706
|
body.text = argv.text;
|
|
1681
1707
|
}
|
|
@@ -1707,8 +1733,12 @@ async function scheduledUpdate(argv) {
|
|
|
1707
1733
|
note("Use exactly one of --text (single post), --part (thread), or --parts-json.");
|
|
1708
1734
|
process.exit(1);
|
|
1709
1735
|
}
|
|
1710
|
-
if (argv.media !== void 0 &&
|
|
1711
|
-
|
|
1736
|
+
if (argv.media !== void 0 && argv.text === void 0) {
|
|
1737
|
+
if (parts.length > 0 || argv["parts-json"] !== void 0) {
|
|
1738
|
+
note("--media applies to the --text single-post form. For threads, put media in --parts-json.");
|
|
1739
|
+
} else {
|
|
1740
|
+
note('--media replaces the whole post, text included. Pass --text with it, or --text "" for a media-only post.');
|
|
1741
|
+
}
|
|
1712
1742
|
process.exit(1);
|
|
1713
1743
|
}
|
|
1714
1744
|
if (argv.title !== void 0 && argv["clear-title"]) {
|
|
@@ -1727,10 +1757,10 @@ async function scheduledUpdate(argv) {
|
|
|
1727
1757
|
const body = {};
|
|
1728
1758
|
if (argv["parts-json"] !== void 0) {
|
|
1729
1759
|
body.parts = parsePartsJson(argv["parts-json"]);
|
|
1730
|
-
} else if (argv.text) {
|
|
1760
|
+
} else if (argv.text || argv.media !== void 0) {
|
|
1731
1761
|
const media = mediaFromFlags(argv.media, argv["alt-text"]);
|
|
1732
1762
|
if (media) {
|
|
1733
|
-
body.parts = [{ text: argv.text, media }];
|
|
1763
|
+
body.parts = [{ text: argv.text || "", media }];
|
|
1734
1764
|
} else {
|
|
1735
1765
|
body.text = argv.text;
|
|
1736
1766
|
}
|
|
@@ -1773,21 +1803,21 @@ async function postsPublish(argv) {
|
|
|
1773
1803
|
note("Use exactly one of --text (single post), --part (thread), or --parts-json.");
|
|
1774
1804
|
process.exit(1);
|
|
1775
1805
|
}
|
|
1776
|
-
if (sourceCount === 0) {
|
|
1806
|
+
if (sourceCount === 0 && argv.media === void 0) {
|
|
1777
1807
|
note("Provide --text for a single post, --part flags for a thread, or --parts-json.");
|
|
1778
1808
|
process.exit(1);
|
|
1779
1809
|
}
|
|
1780
|
-
if (argv.media !== void 0 &&
|
|
1810
|
+
if (argv.media !== void 0 && (parts.length > 0 || argv["parts-json"] !== void 0)) {
|
|
1781
1811
|
note("--media applies to the --text single-post form. For threads, put media in --parts-json.");
|
|
1782
1812
|
process.exit(1);
|
|
1783
1813
|
}
|
|
1784
1814
|
const body = { scheduled_for: "now" };
|
|
1785
1815
|
if (argv["parts-json"] !== void 0) {
|
|
1786
1816
|
body.parts = parsePartsJson(argv["parts-json"]);
|
|
1787
|
-
} else if (argv.text) {
|
|
1817
|
+
} else if (argv.text || argv.media !== void 0) {
|
|
1788
1818
|
const media = mediaFromFlags(argv.media, argv["alt-text"]);
|
|
1789
1819
|
if (media) {
|
|
1790
|
-
body.parts = [{ text: argv.text, media }];
|
|
1820
|
+
body.parts = [{ text: argv.text || "", media }];
|
|
1791
1821
|
} else {
|
|
1792
1822
|
body.text = argv.text;
|
|
1793
1823
|
}
|
|
@@ -2656,7 +2686,10 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
2656
2686
|
}).option("instructions", {
|
|
2657
2687
|
describe: "Extra direction for this remix (max 500 chars)",
|
|
2658
2688
|
type: "string"
|
|
2659
|
-
}).
|
|
2689
|
+
}).option("author-handle", {
|
|
2690
|
+
describe: "The @handle that originally posted the text. Pass this account's own handle when remixing one of its own posts so the subject stays",
|
|
2691
|
+
type: "string"
|
|
2692
|
+
}).example('$0 posts:remix --text "$(cat post.txt)" --closeness 70', "A close rewrite in your voice").example('$0 posts:remix --text "..." --closeness 20 --instructions "make it a question"', "A loose reinterpretation").example('$0 posts:remix --text "$(cat old-post.txt)" --closeness 60 --author-handle @you', "Say one of your own posts again, same subject").epilogue(
|
|
2660
2693
|
"Returns TEXT ONLY. Nothing is posted or scheduled: save the result with posts:draft or scheduled:create once you are happy with it."
|
|
2661
2694
|
),
|
|
2662
2695
|
run(postsRemix)
|
|
@@ -2728,6 +2761,19 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
2728
2761
|
"The score compares this draft with the account's OWN recent posts. It is not a reach prediction and it knows nothing about follower count. Rewrite and score again until the score stops rising, and read any warnings as a stop sign: asking for replies or sending readers off the platform can never raise it."
|
|
2729
2762
|
),
|
|
2730
2763
|
run(postsViralScore)
|
|
2764
|
+
).command(
|
|
2765
|
+
"posts:triage <query>",
|
|
2766
|
+
"Sort recent public posts on a topic into Read, Pass or Not sure (costs AI credits)",
|
|
2767
|
+
(y) => accountOption(y).positional("query", {
|
|
2768
|
+
describe: "Topic, phrase or search expression (2-80 characters)",
|
|
2769
|
+
type: "string"
|
|
2770
|
+
}).option("days", {
|
|
2771
|
+
describe: "How far back to search, 1 to 7 days (default 3)",
|
|
2772
|
+
type: "number"
|
|
2773
|
+
}).example('$0 posts:triage "coding agents"', "What is worth reading on a topic today").example('$0 posts:triage "indie SaaS" --days 7', "The past week instead of the past three days").epilogue(
|
|
2774
|
+
"Judges the TEXT of each post and never sees who wrote it, so read the result as a call on the writing rather than a rating of a person. Up to 40 posts a run, 2 credits, and each plan has a daily cap. Nothing is posted, saved or sent."
|
|
2775
|
+
),
|
|
2776
|
+
run(postsTriage)
|
|
2731
2777
|
).command(
|
|
2732
2778
|
"replies:list",
|
|
2733
2779
|
"List replies the account has sent (newest first)",
|
|
@@ -2749,7 +2795,10 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
2749
2795
|
describe: "Sort order",
|
|
2750
2796
|
type: "string",
|
|
2751
2797
|
choices: ["relevant", "recent", "likes", "reposts", "impressions", "outlier"]
|
|
2752
|
-
}).option("min-likes", { describe: "Only posts with at least this many likes", type: "number" }).option("min-reposts", { describe: "Only posts with at least this many reposts", type: "number" }).option("min-replies", { describe: "Only posts with at least this many replies", type: "number" }).option("min-bookmarks", { describe: "Only posts with at least this many bookmarks", type: "number" }).option("min-impressions", { describe: "Only posts with at least this many impressions", type: "number" }).option("min-followers", { describe: "Only posts from authors with at least this many followers", type: "number" }).option("max-followers", { describe: "Only posts from authors with at most this many followers", type: "number" }).option("
|
|
2798
|
+
}).option("min-likes", { describe: "Only posts with at least this many likes", type: "number" }).option("min-reposts", { describe: "Only posts with at least this many reposts", type: "number" }).option("min-replies", { describe: "Only posts with at least this many replies", type: "number" }).option("min-bookmarks", { describe: "Only posts with at least this many bookmarks", type: "number" }).option("min-impressions", { describe: "Only posts with at least this many impressions", type: "number" }).option("min-followers", { describe: "Only posts from authors with at least this many followers", type: "number" }).option("max-followers", { describe: "Only posts from authors with at most this many followers", type: "number" }).option("min-outlier-score", {
|
|
2799
|
+
describe: "Only posts with at least this outlier score (1.0 = expected engagement for the author's size, max 1000)",
|
|
2800
|
+
type: "number"
|
|
2801
|
+
}).option("min-length", { describe: "Only posts with at least this many characters", type: "number" }).option("since", { describe: "Only posts after this time (UTC ISO-8601)", type: "string" }).option("until", { describe: "Only posts before this time (UTC ISO-8601)", type: "string" }).option("lang", { describe: "Language code (default en)", type: "string" }).option("exclude-topics", { describe: "Comma-separated topics to exclude", type: "string" }).example('$0 inspiration:search "build in public" --limit 10', "Ten posts about building in public").example('$0 inspiration:search "indie hackers" --sort outlier --min-likes 500', "Overperformers with 500+ likes"),
|
|
2753
2802
|
run(inspirationSearch)
|
|
2754
2803
|
).command(
|
|
2755
2804
|
"inspiration:media [query]",
|
|
@@ -3365,7 +3414,7 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3365
3414
|
type: "string",
|
|
3366
3415
|
array: true
|
|
3367
3416
|
}).option("media", {
|
|
3368
|
-
describe: "Comma list of image object_keys (from media:upload) to attach; single-post form only (max 4 images or 1 GIF)",
|
|
3417
|
+
describe: "Comma list of image object_keys (from media:upload) to attach; single-post form only (max 4 images or 1 GIF). Without --text it makes a media-only post",
|
|
3369
3418
|
type: "string"
|
|
3370
3419
|
}).option("alt-text", {
|
|
3371
3420
|
describe: "Accessibility description for the attached image (single --media key only, max 1000 chars)",
|
|
@@ -3383,7 +3432,7 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3383
3432
|
}).option("idempotency-key", {
|
|
3384
3433
|
describe: "Idempotency-Key header (max 64 chars); retries with the same key return the original result",
|
|
3385
3434
|
type: "string"
|
|
3386
|
-
}).example('$0 scheduled:create --text "Hello"', "Create a draft").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z"', "Schedule a post").example('$0 scheduled:create --part "1/ Hook" --part "2/ Detail" --part "3/ CTA"', "Draft a 3-part thread").example('$0 scheduled:create --text "Hello" --title "Launch teaser" --tag abc123', "Draft with a title and a tag").example('$0 scheduled:create --text "Chart of the week" --media "<object_key>" --alt-text "Revenue chart"', "Draft with an image").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z" --auto-retweet 6 --auto-retweet-remove 4', "Schedule with an auto retweet").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z" --no-auto-retweet --no-auto-plug', "Schedule with your defaults off for this post"),
|
|
3435
|
+
}).example('$0 scheduled:create --text "Hello"', "Create a draft").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z"', "Schedule a post").example('$0 scheduled:create --part "1/ Hook" --part "2/ Detail" --part "3/ CTA"', "Draft a 3-part thread").example('$0 scheduled:create --text "Hello" --title "Launch teaser" --tag abc123', "Draft with a title and a tag").example('$0 scheduled:create --text "Chart of the week" --media "<object_key>" --alt-text "Revenue chart"', "Draft with an image").example('$0 scheduled:create --media "<object_key>"', "Draft an image-only post (no text)").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z" --auto-retweet 6 --auto-retweet-remove 4', "Schedule with an auto retweet").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z" --no-auto-retweet --no-auto-plug', "Schedule with your defaults off for this post"),
|
|
3387
3436
|
run(scheduledCreate)
|
|
3388
3437
|
).command(
|
|
3389
3438
|
"scheduled:update <id>",
|
|
@@ -3393,7 +3442,7 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3393
3442
|
type: "string",
|
|
3394
3443
|
array: true
|
|
3395
3444
|
}).option("media", {
|
|
3396
|
-
describe:
|
|
3445
|
+
describe: `Comma list of image object_keys to attach with --text (full replace: re-list existing keys to keep them; --text without --media removes the post's media; --text "" with --media makes the post media-only)`,
|
|
3397
3446
|
type: "string"
|
|
3398
3447
|
}).option("alt-text", {
|
|
3399
3448
|
describe: "Accessibility description for the attached image (single --media key only, max 1000 chars)",
|
|
@@ -3405,18 +3454,18 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3405
3454
|
describe: "New schedule time (UTC ISO-8601 with explicit Z or offset). On its own it never schedules a draft; add --status scheduled.",
|
|
3406
3455
|
type: "string"
|
|
3407
3456
|
}).option("status", {
|
|
3408
|
-
describe: "Explicit transition; scheduled needs a future time (via --at or already set)",
|
|
3457
|
+
describe: "Explicit transition; scheduled needs a future time (via --at or already set). Moving a scheduled post to draft re-points scheduled posts that quote it to the post it quoted when that is one of your own posts (still scheduled earlier, or already published); otherwise they lose the quote",
|
|
3409
3458
|
type: "string",
|
|
3410
3459
|
choices: ["draft", "scheduled"]
|
|
3411
3460
|
}).option("title", { describe: "New draft title (max 300 chars)", type: "string" }).option("clear-title", { describe: "Remove the title", type: "boolean" }).option("scratchpad", { describe: "New private notes (max 30000 chars)", type: "string" }).option("clear-scratchpad", { describe: "Remove the notes", type: "boolean" }).option("tag", {
|
|
3412
3461
|
describe: "Replacement tag id set (repeat the flag, max 20; replaces ALL current tags)",
|
|
3413
3462
|
type: "string",
|
|
3414
3463
|
array: true
|
|
3415
|
-
}).option("clear-tags", { describe: "Remove all tags", type: "boolean" }).example('$0 scheduled:update abc123 --title "Better hook"', "Retitle a draft, everything else untouched").example('$0 scheduled:update abc123 --at "2026-08-01T15:00:00Z" --status scheduled', "Promote a draft to the queue").example("$0 scheduled:update abc123 --status draft", "Pull a post back to drafts (quota refunds)").example("$0 scheduled:update abc123 --auto-delete 8 --auto-delete-threshold 500", "Add an auto delete to the post").example("$0 scheduled:update abc123 --no-auto-retweet", "Remove the post's auto retweet"),
|
|
3464
|
+
}).option("clear-tags", { describe: "Remove all tags", type: "boolean" }).example('$0 scheduled:update abc123 --title "Better hook"', "Retitle a draft, everything else untouched").example('$0 scheduled:update abc123 --at "2026-08-01T15:00:00Z" --status scheduled', "Promote a draft to the queue").example("$0 scheduled:update abc123 --status draft", "Pull a post back to drafts (quota refunds)").example("$0 scheduled:update abc123 --auto-delete 8 --auto-delete-threshold 500", "Add an auto delete to the post").example("$0 scheduled:update abc123 --no-auto-retweet", "Remove the post's auto retweet").example('$0 scheduled:update abc123 --text "" --media "<object_key>"', "Make the post image-only (removes its text)"),
|
|
3416
3465
|
run(scheduledUpdate)
|
|
3417
3466
|
).command(
|
|
3418
3467
|
"scheduled:delete <id>",
|
|
3419
|
-
"Delete a draft or scheduled post by id",
|
|
3468
|
+
"Delete a draft or scheduled post by id. Scheduled posts that quote it are re-pointed to the post it quoted when that is one of your own posts (still scheduled earlier, or already published); otherwise they lose the quote",
|
|
3420
3469
|
(y) => y.positional("id", { describe: "Post id (from scheduled:list or scheduled:create)", type: "string" }),
|
|
3421
3470
|
run(scheduledDelete)
|
|
3422
3471
|
).command(
|
|
@@ -3427,7 +3476,7 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3427
3476
|
type: "string",
|
|
3428
3477
|
array: true
|
|
3429
3478
|
}).option("media", {
|
|
3430
|
-
describe: "Comma list of image object_keys (from media:upload) to attach; single-post form only (max 4 images or 1 GIF)",
|
|
3479
|
+
describe: "Comma list of image object_keys (from media:upload) to attach; single-post form only (max 4 images or 1 GIF). Without --text it makes a media-only post",
|
|
3431
3480
|
type: "string"
|
|
3432
3481
|
}).option("alt-text", {
|
|
3433
3482
|
describe: "Accessibility description for the attached image (single --media key only, max 1000 chars)",
|
|
@@ -3442,7 +3491,7 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3442
3491
|
}).option("idempotency-key", {
|
|
3443
3492
|
describe: "REQUIRED (max 64 chars). Reuse the SAME key when retrying so a timed-out call cannot post twice; use a new key only for new content",
|
|
3444
3493
|
type: "string"
|
|
3445
|
-
}).example('$0 posts:publish --text "Shipping now." --idempotency-key launch-2026-09-07', "Publish a single post").example('$0 posts:publish --part "1/ Hook" --part "2/ Detail" --idempotency-key thread-42', "Publish a thread").example('$0 posts:publish --text "Shipping now." --auto-retweet 6 --idempotency-key launch-2026-09-07', "Publish with an auto retweet"),
|
|
3494
|
+
}).example('$0 posts:publish --text "Shipping now." --idempotency-key launch-2026-09-07', "Publish a single post").example('$0 posts:publish --part "1/ Hook" --part "2/ Detail" --idempotency-key thread-42', "Publish a thread").example('$0 posts:publish --media "<object_key>" --idempotency-key gif-2026-10-03', "Publish an image-only post").example('$0 posts:publish --text "Shipping now." --auto-retweet 6 --idempotency-key launch-2026-09-07', "Publish with an auto retweet"),
|
|
3446
3495
|
run(postsPublish)
|
|
3447
3496
|
).command(
|
|
3448
3497
|
"scheduled:bulk-retime",
|
|
@@ -3459,7 +3508,7 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3459
3508
|
run(scheduledBulkAutoRetweet)
|
|
3460
3509
|
).command(
|
|
3461
3510
|
"scheduled:bulk-delete",
|
|
3462
|
-
"Delete up to 100 QUEUED posts and refund their post quota (sent posts and drafts are left alone)",
|
|
3511
|
+
"Delete up to 100 QUEUED posts and refund their post quota (sent posts and drafts are left alone). Scheduled posts that quote them are re-pointed to the post each one quoted when that is one of your own posts (still scheduled earlier, or already published); otherwise they lose the quote",
|
|
3463
3512
|
(y) => accountOption(y).option("ids", { describe: "Comma list of post ids (max 100, from scheduled:list)", type: "string" }).example("$0 scheduled:bulk-delete --ids abc,def", "Delete two queued posts"),
|
|
3464
3513
|
run(scheduledBulkDelete)
|
|
3465
3514
|
).command(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superx-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "SuperX CLI - command line interface to the SuperX API for Twitter/X growth: read posts and analytics, find engaged contacts, and schedule posts and threads",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
package/skills/superx/SKILL.md
CHANGED
|
@@ -32,7 +32,7 @@ official website: https://superx.so
|
|
|
32
32
|
|
|
33
33
|
**Rule 2: Read `references/growth-strategy.md` before creating any content.** This skill ships a growth strategy guide (`references/growth-strategy.md`, also inside the installed npm package). It tells you WHAT to post, WHEN, and WHY: the action hierarchy, out-of-network discovery, the engagement loop, and the failure modes that kill reach. The CLI gives you data and actions; the strategy guide gives you judgment. Do not schedule content without it. For a goal-shaped task ("give me my weekly recap", "DM the people who replied to this post"), open the matching skill under `references/skills/`: the named SuperX skills, each with its exact command chain, MCP tool names and stopping point.
|
|
34
34
|
|
|
35
|
-
**Rule 3: Know the write constraints.** `scheduled:create` without `--at` creates a DRAFT (nothing publishes). With `--at` it schedules for that time. `scheduled:update` changes only the flags you pass, and a new `--at` alone never schedules a draft; add `--status scheduled` to promote. Writes work on your main account or any linked account (pass the same `--account` you used to read it); accounts shared with you by other people are read-only, and tags are workspace-wide. Images attach via `media:upload` then `--media` (JPG/PNG/WEBP up to 5MB, GIF up to 15MB; max 4 images or 1 GIF per post); video is not supported. Timestamps MUST be UTC ISO-8601 with an explicit `Z` or offset; naive timestamps are rejected with 400. `posts:publish` and `articles:publish` post to X IMMEDIATELY and irreversibly; treat them like hitting Publish in public and get human confirmation of the exact text unless the user already gave it. `posts:publish` also requires `--idempotency-key`, which you reuse verbatim on any retry. `posts:draft` writes post text in the user's voice and saves NOTHING: show the drafts, let the user pick and edit one, then pass the final text to `scheduled:create` yourself; it costs AI credits per draft, so ask for the count the user actually wants. `--voice mine` is the voice of the `--account` you pass (its own posts and style guide); shared accounts are refused.
|
|
35
|
+
**Rule 3: Know the write constraints.** `scheduled:create` without `--at` creates a DRAFT (nothing publishes). With `--at` it schedules for that time. `scheduled:update` changes only the flags you pass, and a new `--at` alone never schedules a draft; add `--status scheduled` to promote. Writes work on your main account or any linked account (pass the same `--account` you used to read it); accounts shared with you by other people are read-only, and tags are workspace-wide. Images attach via `media:upload` then `--media` (JPG/PNG/WEBP up to 5MB, GIF up to 15MB; max 4 images or 1 GIF per post), and a post can be image-only (`--media` with no `--text`; on `scheduled:update` use `--text "" --media KEY`, since an update replaces the whole post); video is not supported. Timestamps MUST be UTC ISO-8601 with an explicit `Z` or offset; naive timestamps are rejected with 400. `posts:publish` and `articles:publish` post to X IMMEDIATELY and irreversibly; treat them like hitting Publish in public and get human confirmation of the exact text unless the user already gave it. `posts:publish` also requires `--idempotency-key`, which you reuse verbatim on any retry. `posts:draft` writes post text in the user's voice and saves NOTHING: show the drafts, let the user pick and edit one, then pass the final text to `scheduled:create` yourself; it costs AI credits per draft, so ask for the count the user actually wants. `--voice mine` is the voice of the `--account` you pass (its own posts and style guide); shared accounts are refused.
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
@@ -189,6 +189,7 @@ scoping are in [references/skills/README.md](./references/skills/README.md).
|
|
|
189
189
|
|
|
190
190
|
### Replies & Engagement
|
|
191
191
|
- **Reply Sprint**: The replies your posts got, each with a ready-to-send draft. -> references/skills/reply-sprint/recipe.md
|
|
192
|
+
- **Worth a Reply**: Let Jev read your niche and hand you the posts worth replying to today. -> references/skills/worth-a-reply/recipe.md
|
|
192
193
|
- **Reply to Any Post**: Paste any post, get the context and a strong reply. -> references/skills/reply-to-any-post/recipe.md
|
|
193
194
|
- **My Replies Report**: Which of your replies actually earn attention. -> references/skills/my-replies-report/recipe.md
|
|
194
195
|
- **Who Is This Person?**: A fast read on any public account, plus your history with them. -> references/skills/who-is-this-person/recipe.md
|
|
@@ -81,11 +81,12 @@ superx replies:received --limit 20 # Replies the audience has se
|
|
|
81
81
|
superx inspiration:search "build in public" --limit 10 # Topic search
|
|
82
82
|
superx inspiration:search "indie hackers" --sort outlier # Biggest overperformers
|
|
83
83
|
superx inspiration:search "AI tools" --min-likes 500 --min-followers 1000 --max-followers 50000
|
|
84
|
+
superx inspiration:search "SaaS pricing" --min-outlier-score 3 --min-length 100 # 3x+ expected engagement, 100+ chars
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
- Searches a library of 50M+ real high-performing posts. Use results for structures, hooks, and angles to remix. Never copy them.
|
|
87
|
-
- Flags: `--sort relevant|recent|likes|reposts|impressions|outlier`, `--min-likes/--min-reposts/--min-replies/--min-bookmarks/--min-impressions`, `--min-followers/--max-followers` (author size), `--since/--until`, `--lang` (default en), `--exclude-topics "crypto,politics"`, `--limit` (max 50), `--page` (1-7).
|
|
88
|
-
- `outlier_score` on each result = how far the post outperformed the norm for its author's follower tier. Sorting by `outlier` surfaces content that won on substance, not audience size.
|
|
88
|
+
- Flags: `--sort relevant|recent|likes|reposts|impressions|outlier`, `--min-likes/--min-reposts/--min-replies/--min-bookmarks/--min-impressions`, `--min-length` (characters), `--min-followers/--max-followers` (author size), `--min-outlier-score` (0-1000), `--since/--until`, `--lang` (default en), `--exclude-topics "crypto,politics"`, `--limit` (max 50), `--page` (1-7).
|
|
89
|
+
- `outlier_score` on each result = how far the post outperformed the norm for its author's follower tier (1.0 = expected, 3 = three times expected). Sorting by `outlier` surfaces content that won on substance, not audience size; `--min-outlier-score` drops everything below a floor.
|
|
89
90
|
- Results are relevance-ranked, strongest matches first. Weak and promotional matches are filtered out, so a page may return fewer than `--limit` posts.
|
|
90
91
|
|
|
91
92
|
### Live X lookups
|
|
@@ -358,6 +359,7 @@ superx engage:reply-draft --text "hot take about pricing" --handle levelsio --au
|
|
|
358
359
|
# Rewrite a post, near or far from the original
|
|
359
360
|
superx posts:remix --text "$(cat post.txt)" --closeness 70
|
|
360
361
|
superx posts:remix --text "..." --closeness 20 --instructions "make it a question"
|
|
362
|
+
superx posts:remix --text "$(cat old-post.txt)" --closeness 60 --author-handle @you
|
|
361
363
|
|
|
362
364
|
# Change one selected piece, keeping the surrounding style
|
|
363
365
|
superx tools:inline-edit --text "the hook line" --full "$(cat post.txt)" --type hook
|
|
@@ -372,16 +374,21 @@ superx tools:factcheck --text "X has 600M daily active users"
|
|
|
372
374
|
# Score a draft against this account's own normal post, then improve it
|
|
373
375
|
superx posts:viral-score --text "$(cat draft.txt)"
|
|
374
376
|
superx posts:viral-score --text "$(cat draft-v2.txt)" --image
|
|
377
|
+
|
|
378
|
+
# Sort what people are posting on a topic into Read, Pass or Not sure
|
|
379
|
+
superx posts:triage "coding agents"
|
|
380
|
+
superx posts:triage "indie SaaS" --days 7
|
|
375
381
|
```
|
|
376
382
|
|
|
377
383
|
- **Every one of these returns TEXT and posts NOTHING.** `engage:reply-draft` writes a reply for a person to review and post; there is still no reply-sending command anywhere in the CLI. Show the draft, let the user edit it, and never say a reply went out.
|
|
378
384
|
- `engage:reply-draft` takes exactly one of `--post <id>` (the API reads the post live, so the draft sees the real text, author and any quoted post) or `--text` with optional `--author` and `--handle`. Add `--thoughts` with what the USER wants to say - ask them, never invent an opinion for them - and `--tone engaging|humorous|creative|sarcastic|inspirational|concise`. `--post` also spends one live X lookup on top of the credit.
|
|
379
|
-
- `posts:remix` needs `--closeness` 0-100: 0 keeps only the idea, 100 stays very close to the original wording. Use it on a proven post the user wants to say again in their own words, then save the result with `posts:draft` or `scheduled:create`.
|
|
385
|
+
- `posts:remix` needs `--closeness` 0-100: 0 keeps only the idea, 100 stays very close to the original wording. Use it on a proven post the user wants to say again in their own words, then save the result with `posts:draft` or `scheduled:create`. When the post is one of the account's OWN (from `posts:list`, analytics or an old post the user pastes), add `--author-handle` with that account's handle so the remix keeps its subject instead of re-grounding it in the profile.
|
|
380
386
|
- `tools:inline-edit` needs `--instruction`, `--type`, or both, and works best with `--full` so the edit blends into the post around it. `--type` presets: grammar, translate, hook, details, concise, engaging, humorous, creative, sarcastic, inspirational.
|
|
381
387
|
- `tools:rephrase` presets: improve, grammar, translate, hook, details, clarity, engaging, humorous, positive, creative, sarcastic, inspirational, concise. The style ones write in the user's voice; grammar, translate, clarity, details and concise stay mechanical.
|
|
382
388
|
- `tools:factcheck` reports `result` (true, false or unknown), a one-sentence `comment` and the `sources` it read. It is a model's reading of a couple of search results, NOT a guarantee: show the sources and never present the verdict as settled.
|
|
383
389
|
- `posts:viral-score` scores ONE draft from 0 to 100 against the account's OWN recent posts, with `helped` / `hurt` in plain English and an expected multiple per counter (`reposts_and_quotes` and `views` come back `confidence: "low"`, so say so). It is not a reach prediction and it knows nothing about follower count. Rewrite what `hurt` names, score again, and stop when the score stops rising: three or four rounds is the useful range. NEVER chase the score with reply bait - anything in `warnings` (asking for replies, inviting people to connect, a borrowed template, sending readers off the platform) can only push a score DOWN, so a warning means change the post, not work around it. The baseline is the account's originals from the past 90 days, minus the last 3 days whose numbers are still settling; `baseline.kind: "population"` means fewer than 10 of those were usable and the draft was scored against the average training post instead.
|
|
384
|
-
-
|
|
390
|
+
- `posts:triage "<query>"` is the only one here that reads other people's posts: it searches recent public posts on a topic (`--days` 1-7, default 3) and sorts up to 40 of them into `lane` `read`, `unsure` or `pass`, each with `pct`, a `kind` (insight, story, data, progress, news, question, intro, launch, joke, opinion) and the `answers` behind the call. Quote a post's own answers when you say why it landed where it did, and never invent a reason. **`pct` is how CLEAR the call was, not how good the post is:** on `read` and `pass` it runs 50 to 99, so a `pass` at 99 means confidently NOT worth reading, and only on `unsure` is it the raw worth-reading score. Always say the lane with the number, never rank posts by `pct` across lanes, and never call a 90 a great post without checking the lane first. What is judged is the TEXT of a post and the model never sees who wrote it, so report it as a call on the writing, NOT as a rating of a person or an account. The search asks for original posts, English only, with a floor of 30 likes, and never returns replies or reposts: a small or brand-new topic can come back empty, which is an honest answer rather than a failure. The floor is what the search asks for, not a promise about `counts.likes`, so do not tell the user every post has 30+ likes. `posts_searched` says how many distinct posts the searches found before the 40-post cap. Nothing is posted, saved or sent.
|
|
391
|
+
- Costs are measured AI credits: typically 1 each, 2 for a remix or a reply draft, and a flat 2 for a triage run. None of them spends a live X request except `engage:reply-draft --post` and `posts:triage`, which spends the searches it runs; both also carry a per-plan daily cap.
|
|
385
392
|
|
|
386
393
|
### Workers (posts written for you on a schedule)
|
|
387
394
|
|
|
@@ -447,6 +454,7 @@ superx scheduled:delete <post-id>
|
|
|
447
454
|
- Replays add `"replayed": true` to the JSON output and print a stderr note.
|
|
448
455
|
- `scheduled:list` filters: `--status draft,scheduled,sent,error` (comma list), `--tags id,id` (any-of), `--from/--to` bounds on the scheduled time.
|
|
449
456
|
- `media:upload` accepts JPG/PNG/WEBP (5MB) and GIF (15MB); a post part carries up to 4 images OR exactly 1 GIF. Uploads are capped at 100/day and expire after 24h if never attached.
|
|
457
|
+
- Image-only posts work: `--media <key>` with no `--text` (on `scheduled:update` pass `--text "" --media <key>`, since it replaces the whole post), or a `--parts-json` part with `media` and no `text`.
|
|
450
458
|
|
|
451
459
|
### Publishing now (irreversible)
|
|
452
460
|
|
|
@@ -10,6 +10,6 @@ superx scheduled:create --text "<approved text>" --idempotency-key "remix-<slug>
|
|
|
10
10
|
|
|
11
11
|
`--mirror` copies the SHAPE of a proven post, not its words. Pick a reference with room for the account's own facts.
|
|
12
12
|
|
|
13
|
-
MCP: `find_inspiration` -> `draft_post` -> `schedule_post`
|
|
13
|
+
MCP: `find_inspiration` (`sort: "outlier"`, optionally `min_outlier_score: 3` for posts with 3x the expected engagement for the author's size) -> `draft_post` -> `schedule_post`
|
|
14
14
|
|
|
15
15
|
Stops at: reference posts plus drafts, for the person to pick from. 3 AI credits per draft written.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Worth a Reply",
|
|
3
|
+
"category": "replies-engagement",
|
|
4
|
+
"order": 15,
|
|
5
|
+
"subtitle": "Let Jev read your niche and hand you the posts worth replying to today.",
|
|
6
|
+
"summary": "Searches recent public posts on a topic and reads every one of them, then sorts them into three lanes: Read, the ones worth your time, Skim, the maybes, and Pass, which stays greyed out and is never listed unless you ask for it. The Read posts come back ordered by how much room there is to add something in a reply, each with its link, one line on why it is worth a reply and an angle you could take. It reads the words of a post and never who wrote it, so it is a call on the writing and not a rating of a person. Nothing is drafted or sent unless you ask.",
|
|
7
|
+
"howToAsk": [
|
|
8
|
+
"What should I reply to in AI agents today?",
|
|
9
|
+
"Find me 5 posts worth replying to about indie SaaS",
|
|
10
|
+
"Anything worth replying to on coding agents this week?"
|
|
11
|
+
],
|
|
12
|
+
"provide": "a topic, and optionally how many days back (1 to 7).",
|
|
13
|
+
"get": "the posts worth a reply, each with a link, one line on why and an angle, then the Skim list.",
|
|
14
|
+
"chips": [
|
|
15
|
+
"2 credits on the API and CLI",
|
|
16
|
+
"Up to 40 posts",
|
|
17
|
+
"Judges the text, never the author",
|
|
18
|
+
"Nothing is sent"
|
|
19
|
+
],
|
|
20
|
+
"launchPrompt": "Find me the posts worth replying to on a topic today. Search recent posts on it, sort them into Read, Skim and Pass, and show the Read posts first, ordered by how much room there is to add something in a reply: one post per line with its link, its kind, one line on why it is worth a reply and the angle a reply could take, all taken from that post's own answers and text. Then list Skim as short one-liners. Leave the Pass pile out unless I ask for it, and do not draft any replies until I pick one. Here is the topic: ",
|
|
21
|
+
"sampleKind": "recap"
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Worth a Reply
|
|
2
|
+
|
|
3
|
+
Read a topic the person cares about and hand back the posts worth replying to today, each with the angle a reply could take.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
superx posts:triage "<the topic>" --days 3 # --days 1-7, default 3; one run per topic per day is plenty
|
|
7
|
+
superx engage:reply-draft --post <id> --thoughts "<what the person wants to say>" # only after they pick one
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Read `lane`, `pct`, `kind` and `answers` from every post. Present the `read` lane first, ordered by `answers.r_reply_room` (how much room a knowledgeable reader has to add something), and order ONLY inside that lane: one line per post with its `url`, its `kind`, one line on why it is worth a reply built from that post's own answers (`r_specific` says it names something concrete, `r_new` says it is not the usual take, `r_reply_room` says there is something left to add), and one line on the angle a reply could take, drawn from the post's text. Then the `unsure` lane as short one-liners under a Skim heading. Leave the `pass` lane out entirely unless the person asks what was dropped, and then say how many and why in one sentence. Quote a post's own answers rather than inventing a reason, and never claim a post has a given like count from the search floor.
|
|
11
|
+
|
|
12
|
+
`pct` is how CLEAR the call was, not how good the post is: on `read` and `pass` it runs 50 to 99, so a `pass` at 99 means confidently not worth the time, and only on `unsure` is it the raw worth-reading score. Say the lane with the number, never rank posts by `pct` across lanes. What is judged is the TEXT of a post and the model never sees who wrote it, so report it as a call on the writing and never as a rating of a person or an account. The search asks for original posts, English only, with a floor of 30 likes, and returns no replies or reposts, so a small or brand-new topic can come back empty, which is an honest answer rather than a failure.
|
|
13
|
+
|
|
14
|
+
MCP: `triage_posts` -> `draft_reply` only when the person picks a post and says what they want to say
|
|
15
|
+
|
|
16
|
+
Stops at: a reply list, in chat, for the person to pick from. It drafts nothing until they choose a post, and nothing is ever sent. A run costs a flat 2 credits and spends 1 live X lookup for a single-word topic or 2 for a multi-word one, out of the account's daily allowance.
|