superx-cli 0.5.2 → 0.7.1
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 +21 -0
- package/README.md +9 -5
- package/dist/index.js +283 -16
- package/package.json +5 -5
- package/skills/superx/SKILL.md +449 -0
- package/{SKILL.md → skills/superx/references/commands.md} +50 -429
- package/{PLAYBOOK.md → skills/superx/references/growth-strategy.md} +3 -3
- package/skills/superx/references/skills/README.md +31 -0
- package/skills/superx/references/skills/audience-export/card.json +20 -0
- package/skills/superx/references/skills/audience-export/recipe.md +13 -0
- package/skills/superx/references/skills/cadence-and-queue-audit/card.json +22 -0
- package/skills/superx/references/skills/cadence-and-queue-audit/recipe.md +17 -0
- package/skills/superx/references/skills/categories.json +41 -0
- package/skills/superx/references/skills/daily-post-ideas/card.json +22 -0
- package/skills/superx/references/skills/daily-post-ideas/recipe.md +17 -0
- package/skills/superx/references/skills/dm-ready-audience-builder/card.json +21 -0
- package/skills/superx/references/skills/dm-ready-audience-builder/recipe.md +16 -0
- package/skills/superx/references/skills/find-your-story/card.json +23 -0
- package/skills/superx/references/skills/find-your-story/recipe.md +17 -0
- package/skills/superx/references/skills/growth-plan-builder/card.json +21 -0
- package/skills/superx/references/skills/growth-plan-builder/recipe.md +17 -0
- package/skills/superx/references/skills/instant-lead-hunt/card.json +21 -0
- package/skills/superx/references/skills/instant-lead-hunt/recipe.md +14 -0
- package/skills/superx/references/skills/lead-review/card.json +20 -0
- package/skills/superx/references/skills/lead-review/recipe.md +16 -0
- package/skills/superx/references/skills/my-content-export/card.json +21 -0
- package/skills/superx/references/skills/my-content-export/recipe.md +15 -0
- package/skills/superx/references/skills/my-replies-report/card.json +20 -0
- package/skills/superx/references/skills/my-replies-report/recipe.md +13 -0
- package/skills/superx/references/skills/post-post-mortem/card.json +20 -0
- package/skills/superx/references/skills/post-post-mortem/recipe.md +16 -0
- package/skills/superx/references/skills/profile-research-briefs/card.json +21 -0
- package/skills/superx/references/skills/profile-research-briefs/recipe.md +15 -0
- package/skills/superx/references/skills/queue-reshuffle/card.json +21 -0
- package/skills/superx/references/skills/queue-reshuffle/recipe.md +15 -0
- package/skills/superx/references/skills/reply-sprint/card.json +20 -0
- package/skills/superx/references/skills/reply-sprint/recipe.md +12 -0
- package/skills/superx/references/skills/reply-to-any-post/card.json +20 -0
- package/skills/superx/references/skills/reply-to-any-post/recipe.md +15 -0
- package/skills/superx/references/skills/reply-to-dm-campaign/card.json +21 -0
- package/skills/superx/references/skills/reply-to-dm-campaign/recipe.md +18 -0
- package/skills/superx/references/skills/repurpose-a-winner/card.json +21 -0
- package/skills/superx/references/skills/repurpose-a-winner/recipe.md +16 -0
- package/skills/superx/references/skills/sentiment-slice/card.json +20 -0
- package/skills/superx/references/skills/sentiment-slice/recipe.md +15 -0
- package/skills/superx/references/skills/standing-lead-agent/card.json +21 -0
- package/skills/superx/references/skills/standing-lead-agent/recipe.md +17 -0
- package/skills/superx/references/skills/teach-superx-your-rules/card.json +21 -0
- package/skills/superx/references/skills/teach-superx-your-rules/recipe.md +15 -0
- package/skills/superx/references/skills/thread-builder/card.json +20 -0
- package/skills/superx/references/skills/thread-builder/recipe.md +14 -0
- package/skills/superx/references/skills/top-performers-breakdown/card.json +21 -0
- package/skills/superx/references/skills/top-performers-breakdown/recipe.md +13 -0
- package/skills/superx/references/skills/trending-now-scan/card.json +21 -0
- package/skills/superx/references/skills/trending-now-scan/recipe.md +14 -0
- package/skills/superx/references/skills/viral-format-remix/card.json +20 -0
- package/skills/superx/references/skills/viral-format-remix/recipe.md +15 -0
- package/skills/superx/references/skills/viral-score-iterate/card.json +22 -0
- package/skills/superx/references/skills/viral-score-iterate/recipe.md +15 -0
- package/skills/superx/references/skills/warm-outreach-pipeline/card.json +21 -0
- package/skills/superx/references/skills/warm-outreach-pipeline/recipe.md +19 -0
- package/skills/superx/references/skills/week-of-posts/card.json +19 -0
- package/skills/superx/references/skills/week-of-posts/recipe.md +18 -0
- package/skills/superx/references/skills/weekly-growth-recap/card.json +22 -0
- package/skills/superx/references/skills/weekly-growth-recap/recipe.md +17 -0
- package/skills/superx/references/skills/who-is-this-person/card.json +20 -0
- package/skills/superx/references/skills/who-is-this-person/recipe.md +16 -0
- package/skills/superx/references/skills/worker-output-review/card.json +20 -0
- package/skills/superx/references/skills/worker-output-review/recipe.md +18 -0
- 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/skills/superx/references/skills/your-warmest-leads/card.json +21 -0
- package/skills/superx/references/skills/your-warmest-leads/recipe.md +14 -0
- package/PLAYBOOKS.md +0 -542
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1 (2026-09-18)
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
|
|
7
|
+
## 0.7.0 (2026-09-18)
|
|
8
|
+
|
|
9
|
+
- `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
|
|
10
|
+
- MCP `triage_posts` does the same thing with the same fields. Reconnect the server to pick it up
|
|
11
|
+
|
|
12
|
+
## 0.6.0 (2026-09-18)
|
|
13
|
+
|
|
14
|
+
- 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
|
|
15
|
+
- Skills install: `superx skills:list` prints every bundled skill (id, name, category) as JSON, and `superx skills:install` puts the skill where your agent looks for it (`~/.claude/skills` and `~/.agents/skills` by default, `--target claude|agents|openclaw|all`, `--project` for the current directory, `--copy` instead of a symlink). Re-running is safe and nothing ever runs on `npm install`
|
|
16
|
+
- Skills set: 30 skills, up from 29 recipes and 29 app cards that were not the same 29. Worker Output Review gained its app card and Find Your Story gained its recipe, so every skill now ships both
|
|
17
|
+
- New skill Viral Score Loop (viral-score-iterate): score a draft, rewrite keyed on what hurt, rescore, stop on warnings. Skills are now 31.
|
|
18
|
+
- Renamed skill ids: `reply-like-a-pro` is now `reply-to-any-post` and `launch-announcement-campaign` is now `reply-to-dm-campaign`, matching the names they have had for a while
|
|
19
|
+
- Skills content fixes: Instant Lead Hunt carries `signals:search --offer` and `--max-age-days` and prices the hunt honestly (it was "No extra cost"), Viral Format Remix says 3 AI credits per draft instead of "No extra cost", and Lead Review says the fit or not-fit verdicts are recorded
|
|
20
|
+
- `npm run skills:build` regenerates the `SKILL.md` index, the server catalog and the docs page from the folders; `npm run skills:check` fails when any of the three is stale and runs on `prepublishOnly`
|
|
21
|
+
- Viral Score: `posts:viral-score --text "..."` scores ONE draft from 0 to 100 against the account's OWN recent posts, with what helped and what hurt in plain English and an expected multiple on likes, replies, reposts+quotes and views versus that account's own median post. `--image`, `--video`, `--quote` and `--post-at` describe how the post would go out, and `--population` scores against the average training post instead (an account with fewer than 10 recent originals falls back to that on its own). Reposts+quotes and views carry `confidence: "low"`. It compares a draft with the account's normal post: it is not a reach prediction and it knows nothing about follower count. `warnings` name what the score never rewards (asking for replies, inviting people to connect, a borrowed template, sending readers off the platform), so a warning means rewrite rather than work around it. Cheap on purpose so drafts can be iterated: most calls charge no credits, one in ten charges 1, and the first call for an account charges 1 more while its baseline is built. `SKILL.md` carries the iterate-until-it-stops-rising recipe
|
|
22
|
+
- Removed: `tools:predict`. It compared two drafts on a model's opinion and was never measured against real posts; `posts:viral-score` replaces it with a score fit on real same-author outcomes
|
|
23
|
+
|
|
3
24
|
## 0.5.2 (2026-09-16)
|
|
4
25
|
|
|
5
26
|
- Lead search offer: `signals:search --offer "<one sentence on what you sell>"` (3-300 chars) tells the search what is being sold, and it plans up to 10 buyer-side query angles from that (workflows, pain, competitors, brand, adjacent) instead of running your keywords verbatim. `--keywords` are now 2-5 short seed angles of how the BUYER talks (a symptom, a tool they already pay for, their jargon), not the product's name and no search operators. Adding `--offer` is the single biggest lever on lead quality; `SKILL.md` carries the updated recipe
|
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
npx skills add superx-so/superx-agent
|
|
5
5
|
```
|
|
6
6
|
|
|
7
|
+
Already have the CLI? `superx skills:install` puts the same skill in
|
|
8
|
+
`~/.claude/skills/superx` and `~/.agents/skills/superx` (`--target openclaw`,
|
|
9
|
+
`--project`, `--copy`).
|
|
10
|
+
|
|
7
11
|
# SuperX CLI
|
|
8
12
|
|
|
9
13
|
**Twitter/X growth CLI for developers and AI agents.** Read your posts and their metrics, pull account analytics, find the people who engage with you most, create draft or scheduled posts and threads (with image attachments), write, schedule, and publish long-form X Articles (with AI cover generation), and read or update the Context settings that steer SuperX's AI writing through the [SuperX API](https://docs.superx.so).
|
|
@@ -11,7 +15,7 @@ npx skills add superx-so/superx-agent
|
|
|
11
15
|
Two things ship in this repo:
|
|
12
16
|
|
|
13
17
|
- `superx-cli`, an npm package installing the `superx` binary (a thin client for `api.superx.so/v1`)
|
|
14
|
-
- An agent skill (`SKILL.md`) plus a growth strategy guide (`
|
|
18
|
+
- An agent skill (`skills/superx/SKILL.md`) plus a growth strategy guide (`references/growth-strategy.md`) and a goal-shaped skill for every SuperX workflow (`references/skills/`) so agents do not just schedule posts, they follow a strategy that works
|
|
15
19
|
|
|
16
20
|
---
|
|
17
21
|
|
|
@@ -342,9 +346,9 @@ superx docs # Prints the API quickstart as markdown; works without auth
|
|
|
342
346
|
|
|
343
347
|
## Features for AI agents
|
|
344
348
|
|
|
345
|
-
- **Skill included**: `npx skills add superx-so/superx-agent` installs [
|
|
346
|
-
- **Strategy included**: [
|
|
347
|
-
- **
|
|
349
|
+
- **Skill included**: `npx skills add superx-so/superx-agent`, or `superx skills:install` once the CLI is on the machine, installs [skills/superx](./skills/superx/SKILL.md), a route-first agent reference with the hard rules, the workflow, the gotchas and a [full command reference](./skills/superx/references/commands.md).
|
|
350
|
+
- **Strategy included**: [references/growth-strategy.md](./skills/superx/references/growth-strategy.md) distills the SuperX growth methodology (action hierarchy, out-of-network discovery, the 3-3-3 engagement loop, weekly operating system) into directives an agent can execute with this CLI. The skill instructs agents to read it before creating content.
|
|
351
|
+
- **Skills included**: [references/skills/](./skills/superx/references/skills) holds one folder per SuperX skill, each with its CLI chain, the matching MCP tool chain, and the point where the agent hands the result back to a person.
|
|
348
352
|
- **Clean JSON stdout**: no decoration to strip; every data command is `jq`-safe.
|
|
349
353
|
- **Idempotent writes**: agents can retry `scheduled:create` safely with `--idempotency-key`.
|
|
350
354
|
- **Self-describing**: `superx docs` fetches the current API quickstart at runtime.
|
|
@@ -357,7 +361,7 @@ superx accounts
|
|
|
357
361
|
superx posts:list --sort likes --limit 10 # study what works
|
|
358
362
|
superx posts:analytics # check the trend
|
|
359
363
|
superx contacts:list --sort engagement --limit 10 # who to engage today
|
|
360
|
-
# ... read
|
|
364
|
+
# ... read references/growth-strategy.md, draft content ...
|
|
361
365
|
superx scheduled:create --text "Draft for review" # draft first
|
|
362
366
|
superx scheduled:list --status draft
|
|
363
367
|
```
|
package/dist/index.js
CHANGED
|
@@ -197,8 +197,13 @@ var SuperXAPI = class {
|
|
|
197
197
|
async factCheck(body) {
|
|
198
198
|
return (await this.request("/tools/factcheck", { method: "POST", body })).json;
|
|
199
199
|
}
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
/** Score ONE draft against the account's own normal post. */
|
|
201
|
+
async viralScore(body) {
|
|
202
|
+
return (await this.request("/posts/viral-score", { method: "POST", body })).json;
|
|
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;
|
|
202
207
|
}
|
|
203
208
|
/** Draft ONE reply to a post. Text only: a person posts it. */
|
|
204
209
|
async draftReply(body) {
|
|
@@ -654,7 +659,7 @@ function getConfig() {
|
|
|
654
659
|
|
|
655
660
|
// src/commands/auth.ts
|
|
656
661
|
function promptForKey() {
|
|
657
|
-
return new Promise((
|
|
662
|
+
return new Promise((resolve2) => {
|
|
658
663
|
const rl = readline.createInterface({
|
|
659
664
|
input: process.stdin,
|
|
660
665
|
output: process.stderr
|
|
@@ -662,7 +667,7 @@ function promptForKey() {
|
|
|
662
667
|
});
|
|
663
668
|
rl.question("Paste your API key (sxk_...): ", (answer) => {
|
|
664
669
|
rl.close();
|
|
665
|
-
|
|
670
|
+
resolve2(answer.trim());
|
|
666
671
|
});
|
|
667
672
|
});
|
|
668
673
|
}
|
|
@@ -799,6 +804,34 @@ async function postsRemix(argv) {
|
|
|
799
804
|
const api = new SuperXAPI(getConfig());
|
|
800
805
|
printJson(await api.remixPost(body));
|
|
801
806
|
}
|
|
807
|
+
async function postsViralScore(argv) {
|
|
808
|
+
const body = { text: argv.text };
|
|
809
|
+
if (argv.image) body.has_image = true;
|
|
810
|
+
if (argv.video) body.has_video = true;
|
|
811
|
+
if (argv.quote) body.is_quote = true;
|
|
812
|
+
if (argv.postAt) body.post_at = argv.postAt;
|
|
813
|
+
if (argv.population) body.baseline = "population";
|
|
814
|
+
if (argv.account) body.account_id = argv.account;
|
|
815
|
+
const api = new SuperXAPI(getConfig());
|
|
816
|
+
printJson(await api.viralScore(body));
|
|
817
|
+
}
|
|
818
|
+
async function postsTriage(argv) {
|
|
819
|
+
if (!argv.query || !argv.query.trim()) {
|
|
820
|
+
note('Provide a query: superx posts:triage "coding agents".');
|
|
821
|
+
process.exit(1);
|
|
822
|
+
}
|
|
823
|
+
if (argv.days !== void 0) {
|
|
824
|
+
if (!Number.isInteger(argv.days) || argv.days < 1 || argv.days > 7) {
|
|
825
|
+
note("--days must be a whole number of days between 1 and 7.");
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const body = { query: argv.query };
|
|
830
|
+
if (argv.days !== void 0) body.max_age_days = argv.days;
|
|
831
|
+
if (argv.account) body.account_id = argv.account;
|
|
832
|
+
const api = new SuperXAPI(getConfig());
|
|
833
|
+
printJson(await api.triage(body));
|
|
834
|
+
}
|
|
802
835
|
|
|
803
836
|
// src/commands/tools.ts
|
|
804
837
|
async function toolsInlineEdit(argv) {
|
|
@@ -826,12 +859,6 @@ async function toolsFactcheck(argv) {
|
|
|
826
859
|
const api = new SuperXAPI(getConfig());
|
|
827
860
|
printJson(await api.factCheck(body));
|
|
828
861
|
}
|
|
829
|
-
async function toolsPredict(argv) {
|
|
830
|
-
const body = { version_a: argv.a, version_b: argv.b };
|
|
831
|
-
if (argv.account) body.account_id = argv.account;
|
|
832
|
-
const api = new SuperXAPI(getConfig());
|
|
833
|
-
printJson(await api.predictAlgorithm(body));
|
|
834
|
-
}
|
|
835
862
|
|
|
836
863
|
// src/commands/inspiration.ts
|
|
837
864
|
async function inspirationSearch(argv) {
|
|
@@ -1093,7 +1120,7 @@ async function waitForDataset(api, started) {
|
|
|
1093
1120
|
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
1094
1121
|
let latest = started;
|
|
1095
1122
|
while (Date.now() < deadline) {
|
|
1096
|
-
await new Promise((
|
|
1123
|
+
await new Promise((resolve2) => setTimeout(resolve2, WAIT_POLL_MS));
|
|
1097
1124
|
latest = await api.getDataset(datasetId);
|
|
1098
1125
|
if (latest?.data?.status !== "collecting") {
|
|
1099
1126
|
printJson(latest);
|
|
@@ -2316,6 +2343,206 @@ async function queueSet(argv) {
|
|
|
2316
2343
|
printJson(await api.updateQueueSettings(body));
|
|
2317
2344
|
}
|
|
2318
2345
|
|
|
2346
|
+
// src/commands/skills.ts
|
|
2347
|
+
var fs4 = __toESM(require("fs"));
|
|
2348
|
+
var os2 = __toESM(require("os"));
|
|
2349
|
+
var path4 = __toESM(require("path"));
|
|
2350
|
+
var SKILL_NAME = "superx";
|
|
2351
|
+
function packageSkillDir() {
|
|
2352
|
+
return path4.join(__dirname, "..", "skills", SKILL_NAME);
|
|
2353
|
+
}
|
|
2354
|
+
function skillsRefDir() {
|
|
2355
|
+
return path4.join(packageSkillDir(), "references", "skills");
|
|
2356
|
+
}
|
|
2357
|
+
function readJson(file) {
|
|
2358
|
+
return JSON.parse(fs4.readFileSync(file, "utf8"));
|
|
2359
|
+
}
|
|
2360
|
+
async function skillsList() {
|
|
2361
|
+
const dir = skillsRefDir();
|
|
2362
|
+
const meta = readJson(path4.join(dir, "categories.json"));
|
|
2363
|
+
const categoryOrder = meta.categories.map((c) => c.id);
|
|
2364
|
+
const rows = fs4.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
2365
|
+
const card = readJson(path4.join(dir, e.name, "card.json"));
|
|
2366
|
+
return {
|
|
2367
|
+
id: e.name,
|
|
2368
|
+
name: card.name,
|
|
2369
|
+
category: card.category,
|
|
2370
|
+
order: card.order
|
|
2371
|
+
};
|
|
2372
|
+
}).sort((a, b) => {
|
|
2373
|
+
const ca = categoryOrder.indexOf(a.category);
|
|
2374
|
+
const cb = categoryOrder.indexOf(b.category);
|
|
2375
|
+
if (ca !== cb) return ca - cb;
|
|
2376
|
+
return a.order - b.order;
|
|
2377
|
+
}).map(({ id, name, category }) => ({ id, name, category }));
|
|
2378
|
+
printJson({
|
|
2379
|
+
data: rows,
|
|
2380
|
+
meta: { count: rows.length, skills_dir: dir, categories: meta.categories }
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
function homeTargets(target) {
|
|
2384
|
+
const home = os2.homedir();
|
|
2385
|
+
const claude = {
|
|
2386
|
+
label: "claude",
|
|
2387
|
+
dest: path4.join(home, ".claude", "skills", SKILL_NAME)
|
|
2388
|
+
};
|
|
2389
|
+
const agents = {
|
|
2390
|
+
label: "agents",
|
|
2391
|
+
dest: path4.join(home, ".agents", "skills", SKILL_NAME)
|
|
2392
|
+
};
|
|
2393
|
+
const openclaw = {
|
|
2394
|
+
label: "openclaw",
|
|
2395
|
+
dest: path4.join(home, ".openclaw", "skills", SKILL_NAME)
|
|
2396
|
+
};
|
|
2397
|
+
if (target === "claude") return [claude];
|
|
2398
|
+
if (target === "agents") return [agents];
|
|
2399
|
+
if (target === "openclaw") return [openclaw];
|
|
2400
|
+
if (target === "all") return [claude, agents, openclaw];
|
|
2401
|
+
const targets = [claude, agents];
|
|
2402
|
+
if (fs4.existsSync(path4.join(home, ".openclaw"))) targets.push(openclaw);
|
|
2403
|
+
return targets;
|
|
2404
|
+
}
|
|
2405
|
+
function projectTargets(target) {
|
|
2406
|
+
const cwd = process.cwd();
|
|
2407
|
+
const claude = {
|
|
2408
|
+
label: "claude",
|
|
2409
|
+
dest: path4.join(cwd, ".claude", "skills", SKILL_NAME)
|
|
2410
|
+
};
|
|
2411
|
+
const agents = {
|
|
2412
|
+
label: "agents",
|
|
2413
|
+
dest: path4.join(cwd, ".agents", "skills", SKILL_NAME)
|
|
2414
|
+
};
|
|
2415
|
+
const workspace = {
|
|
2416
|
+
label: "workspace",
|
|
2417
|
+
dest: path4.join(cwd, "skills", SKILL_NAME)
|
|
2418
|
+
};
|
|
2419
|
+
if (target === "claude") return [claude];
|
|
2420
|
+
if (target === "agents") return [agents];
|
|
2421
|
+
if (target === "openclaw") return [workspace];
|
|
2422
|
+
return [claude, agents, workspace];
|
|
2423
|
+
}
|
|
2424
|
+
function copyTree(src, dest) {
|
|
2425
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
2426
|
+
for (const entry of fs4.readdirSync(src, { withFileTypes: true })) {
|
|
2427
|
+
const from = path4.join(src, entry.name);
|
|
2428
|
+
const to = path4.join(dest, entry.name);
|
|
2429
|
+
if (entry.isDirectory()) copyTree(from, to);
|
|
2430
|
+
else if (entry.isFile()) fs4.copyFileSync(from, to);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
function copySkillTree(src, dest) {
|
|
2434
|
+
if (!fs4.existsSync(path4.join(src, "SKILL.md"))) {
|
|
2435
|
+
throw new Error(`refusing to copy: ${src} has no SKILL.md`);
|
|
2436
|
+
}
|
|
2437
|
+
try {
|
|
2438
|
+
copyTree(src, dest);
|
|
2439
|
+
} catch (err) {
|
|
2440
|
+
fs4.rmSync(dest, { recursive: true, force: true });
|
|
2441
|
+
throw err;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
function isSameDirectory(dest, source) {
|
|
2445
|
+
try {
|
|
2446
|
+
if (fs4.lstatSync(dest).isSymbolicLink()) return false;
|
|
2447
|
+
} catch {
|
|
2448
|
+
return false;
|
|
2449
|
+
}
|
|
2450
|
+
if (path4.resolve(dest) === path4.resolve(source)) return true;
|
|
2451
|
+
try {
|
|
2452
|
+
return fs4.realpathSync(dest) === fs4.realpathSync(source);
|
|
2453
|
+
} catch {
|
|
2454
|
+
return false;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
function isManagedInstall(dest) {
|
|
2458
|
+
let stat;
|
|
2459
|
+
try {
|
|
2460
|
+
stat = fs4.lstatSync(dest);
|
|
2461
|
+
} catch {
|
|
2462
|
+
return false;
|
|
2463
|
+
}
|
|
2464
|
+
if (stat.isSymbolicLink()) return true;
|
|
2465
|
+
if (!stat.isDirectory()) return false;
|
|
2466
|
+
if (!fs4.existsSync(path4.join(dest, "references", "skills", "categories.json"))) {
|
|
2467
|
+
return false;
|
|
2468
|
+
}
|
|
2469
|
+
let head;
|
|
2470
|
+
try {
|
|
2471
|
+
head = fs4.readFileSync(path4.join(dest, "SKILL.md"), "utf8").slice(0, 4096);
|
|
2472
|
+
} catch {
|
|
2473
|
+
return false;
|
|
2474
|
+
}
|
|
2475
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(head);
|
|
2476
|
+
if (!frontmatter) return false;
|
|
2477
|
+
return new RegExp(`^name:\\s*${SKILL_NAME}\\s*$`, "m").test(frontmatter[1]);
|
|
2478
|
+
}
|
|
2479
|
+
function install(source, t, copy) {
|
|
2480
|
+
let exists = true;
|
|
2481
|
+
try {
|
|
2482
|
+
fs4.lstatSync(t.dest);
|
|
2483
|
+
} catch {
|
|
2484
|
+
exists = false;
|
|
2485
|
+
}
|
|
2486
|
+
if (exists && isSameDirectory(t.dest, source)) return { action: "unchanged" };
|
|
2487
|
+
if (exists) {
|
|
2488
|
+
if (!isManagedInstall(t.dest)) {
|
|
2489
|
+
return {
|
|
2490
|
+
action: "skipped",
|
|
2491
|
+
error: `${t.dest} already exists and is not a copy of this skill; move or remove it first, then run skills:install again`
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
if (!copy) {
|
|
2495
|
+
try {
|
|
2496
|
+
if (fs4.realpathSync(t.dest) === fs4.realpathSync(source)) return { action: "unchanged" };
|
|
2497
|
+
} catch {
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
fs4.rmSync(t.dest, { recursive: true, force: true });
|
|
2501
|
+
}
|
|
2502
|
+
fs4.mkdirSync(path4.dirname(t.dest), { recursive: true });
|
|
2503
|
+
if (!copy) {
|
|
2504
|
+
try {
|
|
2505
|
+
fs4.symlinkSync(source, t.dest, "dir");
|
|
2506
|
+
return { action: exists ? "relinked" : "linked" };
|
|
2507
|
+
} catch (err) {
|
|
2508
|
+
note(`Symlink failed for ${t.dest} (${err?.code || err?.message}); copying instead.`);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
copySkillTree(source, t.dest);
|
|
2512
|
+
return { action: "copied" };
|
|
2513
|
+
}
|
|
2514
|
+
async function skillsInstall(argv) {
|
|
2515
|
+
const source = packageSkillDir();
|
|
2516
|
+
if (!fs4.existsSync(path4.join(source, "SKILL.md"))) {
|
|
2517
|
+
note(`Error: no skill found at ${source}. Reinstall superx-cli.`);
|
|
2518
|
+
process.exit(1);
|
|
2519
|
+
}
|
|
2520
|
+
const targets = argv.project ? projectTargets(argv.target) : homeTargets(argv.target);
|
|
2521
|
+
const results = targets.map((t) => {
|
|
2522
|
+
let outcome;
|
|
2523
|
+
try {
|
|
2524
|
+
outcome = install(source, t, Boolean(argv.copy));
|
|
2525
|
+
} catch (err) {
|
|
2526
|
+
outcome = { action: "failed", error: err?.message || String(err) };
|
|
2527
|
+
}
|
|
2528
|
+
if (outcome.error) note(`${t.label}: ${outcome.error}`);
|
|
2529
|
+
else note(`${t.label}: ${outcome.action} ${t.dest}`);
|
|
2530
|
+
return { target: t.label, path: t.dest, ...outcome };
|
|
2531
|
+
});
|
|
2532
|
+
const failed = results.filter((r) => r.error).length;
|
|
2533
|
+
printJson({
|
|
2534
|
+
data: {
|
|
2535
|
+
skill: SKILL_NAME,
|
|
2536
|
+
source,
|
|
2537
|
+
mode: argv.copy ? "copy" : "symlink",
|
|
2538
|
+
scope: argv.project ? "project" : "home",
|
|
2539
|
+
targets: results
|
|
2540
|
+
},
|
|
2541
|
+
meta: { installed: results.length - failed, failed }
|
|
2542
|
+
});
|
|
2543
|
+
if (failed) process.exit(1);
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2319
2546
|
// src/commands/docs.ts
|
|
2320
2547
|
async function docs() {
|
|
2321
2548
|
const apiUrl = resolveApiUrl(loadCredentials()?.apiUrl || DEFAULT_API_URL);
|
|
@@ -2513,12 +2740,28 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
2513
2740
|
),
|
|
2514
2741
|
run(toolsFactcheck)
|
|
2515
2742
|
).command(
|
|
2516
|
-
"
|
|
2517
|
-
"Score
|
|
2518
|
-
(y) => accountOption(y).option("
|
|
2519
|
-
"
|
|
2743
|
+
"posts:viral-score",
|
|
2744
|
+
"Score one draft against this account's own normal post, with what helped and what hurt",
|
|
2745
|
+
(y) => accountOption(y).option("text", { describe: "The draft to score (required, max 4000 chars)", type: "string", demandOption: true }).option("image", { describe: "An image would be attached", type: "boolean" }).option("video", { describe: "A video would be attached", type: "boolean" }).option("quote", { describe: "The post quotes another post", type: "boolean" }).option("post-at", { describe: "When it would go out (UTC ISO-8601)", type: "string" }).option("population", {
|
|
2746
|
+
describe: "Score against the average training post instead of this account's own posts",
|
|
2747
|
+
type: "boolean"
|
|
2748
|
+
}).example('$0 posts:viral-score --text "$(cat draft.txt)"', "Score a draft before you send it").example('$0 posts:viral-score --text "..." --image', "Score it as a post with an image").epilogue(
|
|
2749
|
+
"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."
|
|
2520
2750
|
),
|
|
2521
|
-
run(
|
|
2751
|
+
run(postsViralScore)
|
|
2752
|
+
).command(
|
|
2753
|
+
"posts:triage <query>",
|
|
2754
|
+
"Sort recent public posts on a topic into Read, Pass or Not sure (costs AI credits)",
|
|
2755
|
+
(y) => accountOption(y).positional("query", {
|
|
2756
|
+
describe: "Topic, phrase or search expression (2-80 characters)",
|
|
2757
|
+
type: "string"
|
|
2758
|
+
}).option("days", {
|
|
2759
|
+
describe: "How far back to search, 1 to 7 days (default 3)",
|
|
2760
|
+
type: "number"
|
|
2761
|
+
}).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(
|
|
2762
|
+
"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."
|
|
2763
|
+
),
|
|
2764
|
+
run(postsTriage)
|
|
2522
2765
|
).command(
|
|
2523
2766
|
"replies:list",
|
|
2524
2767
|
"List replies the account has sent (newest first)",
|
|
@@ -3495,6 +3738,30 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3495
3738
|
"Show the account's DM allowances and how much of each is used (free)",
|
|
3496
3739
|
(y) => accountOption(y),
|
|
3497
3740
|
run(dmLimits)
|
|
3741
|
+
).command(
|
|
3742
|
+
"skills:list",
|
|
3743
|
+
"List the SuperX skills bundled with this package (no auth needed)",
|
|
3744
|
+
(y) => y.example("$0 skills:list", "Every skill's id, name and category as JSON").epilogue(
|
|
3745
|
+
"Each skill's recipe is at <skills_dir>/<id>/recipe.md and its app card at card.json. Install the skill for your agent with skills:install."
|
|
3746
|
+
),
|
|
3747
|
+
run(skillsList)
|
|
3748
|
+
).command(
|
|
3749
|
+
"skills:install",
|
|
3750
|
+
"Install the bundled SuperX skill where your agent looks for skills",
|
|
3751
|
+
(y) => y.option("target", {
|
|
3752
|
+
describe: "Where to install: claude (~/.claude/skills), agents (~/.agents/skills), openclaw (~/.openclaw/skills), all. Default: claude + agents, plus openclaw when ~/.openclaw exists",
|
|
3753
|
+
type: "string",
|
|
3754
|
+
choices: ["claude", "agents", "openclaw", "all"]
|
|
3755
|
+
}).option("project", {
|
|
3756
|
+
describe: "Install into the current directory (.claude/skills, .agents/skills, skills) instead of your home directory",
|
|
3757
|
+
type: "boolean"
|
|
3758
|
+
}).option("copy", {
|
|
3759
|
+
describe: "Copy the files instead of symlinking them (also the automatic fallback when a symlink is refused)",
|
|
3760
|
+
type: "boolean"
|
|
3761
|
+
}).example("$0 skills:install", "Symlink the skill into ~/.claude/skills and ~/.agents/skills").example("$0 skills:install --project --copy", "Copy it into this repo for everyone working in it").epilogue(
|
|
3762
|
+
"Re-running is safe: an install that already points at this package is left alone. Nothing runs on npm install; this command is the only thing that writes outside the package."
|
|
3763
|
+
),
|
|
3764
|
+
run(skillsInstall)
|
|
3498
3765
|
).command("docs", "Print the SuperX API quickstart (markdown, no auth needed)", {}, run(docs)).demandCommand(1, "Specify a command. Run: superx --help").strict().help().alias("h", "help").version().wrap(Math.min(100, process.stdout.columns || 100)).fail((msg, err) => {
|
|
3499
3766
|
if (err) throw err;
|
|
3500
3767
|
note(msg || "Invalid usage. Run: superx --help");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superx-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
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": {
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
"dev": "tsup --watch",
|
|
11
11
|
"build": "tsup",
|
|
12
12
|
"start": "node ./dist/index.js",
|
|
13
|
-
"
|
|
13
|
+
"skills:build": "node scripts/skills.mjs build --server ../superx-server --docs ../superx-docs",
|
|
14
|
+
"skills:check": "node scripts/skills.mjs check --server ../superx-server --docs ../superx-docs",
|
|
15
|
+
"prepublishOnly": "npm run skills:check && npm run build"
|
|
14
16
|
},
|
|
15
17
|
"files": [
|
|
16
18
|
"dist",
|
|
19
|
+
"skills",
|
|
17
20
|
"README.md",
|
|
18
|
-
"SKILL.md",
|
|
19
|
-
"PLAYBOOK.md",
|
|
20
|
-
"PLAYBOOKS.md",
|
|
21
21
|
"CHANGELOG.md",
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|