superx-cli 0.5.2 → 0.6.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 +12 -0
- package/README.md +9 -5
- package/dist/index.js +249 -16
- package/package.json +5 -5
- package/skills/superx/SKILL.md +448 -0
- package/{SKILL.md → skills/superx/references/commands.md} +44 -428
- 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/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,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 (2026-09-18)
|
|
4
|
+
|
|
5
|
+
- 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
|
|
6
|
+
- 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`
|
|
7
|
+
- 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
|
|
8
|
+
- New skill Viral Score Loop (viral-score-iterate): score a draft, rewrite keyed on what hurt, rescore, stop on warnings. Skills are now 31.
|
|
9
|
+
- 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
|
|
10
|
+
- 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
|
|
11
|
+
- `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`
|
|
12
|
+
- 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
|
|
13
|
+
- 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
|
|
14
|
+
|
|
3
15
|
## 0.5.2 (2026-09-16)
|
|
4
16
|
|
|
5
17
|
- 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,9 @@ 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;
|
|
202
203
|
}
|
|
203
204
|
/** Draft ONE reply to a post. Text only: a person posts it. */
|
|
204
205
|
async draftReply(body) {
|
|
@@ -654,7 +655,7 @@ function getConfig() {
|
|
|
654
655
|
|
|
655
656
|
// src/commands/auth.ts
|
|
656
657
|
function promptForKey() {
|
|
657
|
-
return new Promise((
|
|
658
|
+
return new Promise((resolve2) => {
|
|
658
659
|
const rl = readline.createInterface({
|
|
659
660
|
input: process.stdin,
|
|
660
661
|
output: process.stderr
|
|
@@ -662,7 +663,7 @@ function promptForKey() {
|
|
|
662
663
|
});
|
|
663
664
|
rl.question("Paste your API key (sxk_...): ", (answer) => {
|
|
664
665
|
rl.close();
|
|
665
|
-
|
|
666
|
+
resolve2(answer.trim());
|
|
666
667
|
});
|
|
667
668
|
});
|
|
668
669
|
}
|
|
@@ -799,6 +800,17 @@ async function postsRemix(argv) {
|
|
|
799
800
|
const api = new SuperXAPI(getConfig());
|
|
800
801
|
printJson(await api.remixPost(body));
|
|
801
802
|
}
|
|
803
|
+
async function postsViralScore(argv) {
|
|
804
|
+
const body = { text: argv.text };
|
|
805
|
+
if (argv.image) body.has_image = true;
|
|
806
|
+
if (argv.video) body.has_video = true;
|
|
807
|
+
if (argv.quote) body.is_quote = true;
|
|
808
|
+
if (argv.postAt) body.post_at = argv.postAt;
|
|
809
|
+
if (argv.population) body.baseline = "population";
|
|
810
|
+
if (argv.account) body.account_id = argv.account;
|
|
811
|
+
const api = new SuperXAPI(getConfig());
|
|
812
|
+
printJson(await api.viralScore(body));
|
|
813
|
+
}
|
|
802
814
|
|
|
803
815
|
// src/commands/tools.ts
|
|
804
816
|
async function toolsInlineEdit(argv) {
|
|
@@ -826,12 +838,6 @@ async function toolsFactcheck(argv) {
|
|
|
826
838
|
const api = new SuperXAPI(getConfig());
|
|
827
839
|
printJson(await api.factCheck(body));
|
|
828
840
|
}
|
|
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
841
|
|
|
836
842
|
// src/commands/inspiration.ts
|
|
837
843
|
async function inspirationSearch(argv) {
|
|
@@ -1093,7 +1099,7 @@ async function waitForDataset(api, started) {
|
|
|
1093
1099
|
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
1094
1100
|
let latest = started;
|
|
1095
1101
|
while (Date.now() < deadline) {
|
|
1096
|
-
await new Promise((
|
|
1102
|
+
await new Promise((resolve2) => setTimeout(resolve2, WAIT_POLL_MS));
|
|
1097
1103
|
latest = await api.getDataset(datasetId);
|
|
1098
1104
|
if (latest?.data?.status !== "collecting") {
|
|
1099
1105
|
printJson(latest);
|
|
@@ -2316,6 +2322,206 @@ async function queueSet(argv) {
|
|
|
2316
2322
|
printJson(await api.updateQueueSettings(body));
|
|
2317
2323
|
}
|
|
2318
2324
|
|
|
2325
|
+
// src/commands/skills.ts
|
|
2326
|
+
var fs4 = __toESM(require("fs"));
|
|
2327
|
+
var os2 = __toESM(require("os"));
|
|
2328
|
+
var path4 = __toESM(require("path"));
|
|
2329
|
+
var SKILL_NAME = "superx";
|
|
2330
|
+
function packageSkillDir() {
|
|
2331
|
+
return path4.join(__dirname, "..", "skills", SKILL_NAME);
|
|
2332
|
+
}
|
|
2333
|
+
function skillsRefDir() {
|
|
2334
|
+
return path4.join(packageSkillDir(), "references", "skills");
|
|
2335
|
+
}
|
|
2336
|
+
function readJson(file) {
|
|
2337
|
+
return JSON.parse(fs4.readFileSync(file, "utf8"));
|
|
2338
|
+
}
|
|
2339
|
+
async function skillsList() {
|
|
2340
|
+
const dir = skillsRefDir();
|
|
2341
|
+
const meta = readJson(path4.join(dir, "categories.json"));
|
|
2342
|
+
const categoryOrder = meta.categories.map((c) => c.id);
|
|
2343
|
+
const rows = fs4.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
|
|
2344
|
+
const card = readJson(path4.join(dir, e.name, "card.json"));
|
|
2345
|
+
return {
|
|
2346
|
+
id: e.name,
|
|
2347
|
+
name: card.name,
|
|
2348
|
+
category: card.category,
|
|
2349
|
+
order: card.order
|
|
2350
|
+
};
|
|
2351
|
+
}).sort((a, b) => {
|
|
2352
|
+
const ca = categoryOrder.indexOf(a.category);
|
|
2353
|
+
const cb = categoryOrder.indexOf(b.category);
|
|
2354
|
+
if (ca !== cb) return ca - cb;
|
|
2355
|
+
return a.order - b.order;
|
|
2356
|
+
}).map(({ id, name, category }) => ({ id, name, category }));
|
|
2357
|
+
printJson({
|
|
2358
|
+
data: rows,
|
|
2359
|
+
meta: { count: rows.length, skills_dir: dir, categories: meta.categories }
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
function homeTargets(target) {
|
|
2363
|
+
const home = os2.homedir();
|
|
2364
|
+
const claude = {
|
|
2365
|
+
label: "claude",
|
|
2366
|
+
dest: path4.join(home, ".claude", "skills", SKILL_NAME)
|
|
2367
|
+
};
|
|
2368
|
+
const agents = {
|
|
2369
|
+
label: "agents",
|
|
2370
|
+
dest: path4.join(home, ".agents", "skills", SKILL_NAME)
|
|
2371
|
+
};
|
|
2372
|
+
const openclaw = {
|
|
2373
|
+
label: "openclaw",
|
|
2374
|
+
dest: path4.join(home, ".openclaw", "skills", SKILL_NAME)
|
|
2375
|
+
};
|
|
2376
|
+
if (target === "claude") return [claude];
|
|
2377
|
+
if (target === "agents") return [agents];
|
|
2378
|
+
if (target === "openclaw") return [openclaw];
|
|
2379
|
+
if (target === "all") return [claude, agents, openclaw];
|
|
2380
|
+
const targets = [claude, agents];
|
|
2381
|
+
if (fs4.existsSync(path4.join(home, ".openclaw"))) targets.push(openclaw);
|
|
2382
|
+
return targets;
|
|
2383
|
+
}
|
|
2384
|
+
function projectTargets(target) {
|
|
2385
|
+
const cwd = process.cwd();
|
|
2386
|
+
const claude = {
|
|
2387
|
+
label: "claude",
|
|
2388
|
+
dest: path4.join(cwd, ".claude", "skills", SKILL_NAME)
|
|
2389
|
+
};
|
|
2390
|
+
const agents = {
|
|
2391
|
+
label: "agents",
|
|
2392
|
+
dest: path4.join(cwd, ".agents", "skills", SKILL_NAME)
|
|
2393
|
+
};
|
|
2394
|
+
const workspace = {
|
|
2395
|
+
label: "workspace",
|
|
2396
|
+
dest: path4.join(cwd, "skills", SKILL_NAME)
|
|
2397
|
+
};
|
|
2398
|
+
if (target === "claude") return [claude];
|
|
2399
|
+
if (target === "agents") return [agents];
|
|
2400
|
+
if (target === "openclaw") return [workspace];
|
|
2401
|
+
return [claude, agents, workspace];
|
|
2402
|
+
}
|
|
2403
|
+
function copyTree(src, dest) {
|
|
2404
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
2405
|
+
for (const entry of fs4.readdirSync(src, { withFileTypes: true })) {
|
|
2406
|
+
const from = path4.join(src, entry.name);
|
|
2407
|
+
const to = path4.join(dest, entry.name);
|
|
2408
|
+
if (entry.isDirectory()) copyTree(from, to);
|
|
2409
|
+
else if (entry.isFile()) fs4.copyFileSync(from, to);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
function copySkillTree(src, dest) {
|
|
2413
|
+
if (!fs4.existsSync(path4.join(src, "SKILL.md"))) {
|
|
2414
|
+
throw new Error(`refusing to copy: ${src} has no SKILL.md`);
|
|
2415
|
+
}
|
|
2416
|
+
try {
|
|
2417
|
+
copyTree(src, dest);
|
|
2418
|
+
} catch (err) {
|
|
2419
|
+
fs4.rmSync(dest, { recursive: true, force: true });
|
|
2420
|
+
throw err;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
function isSameDirectory(dest, source) {
|
|
2424
|
+
try {
|
|
2425
|
+
if (fs4.lstatSync(dest).isSymbolicLink()) return false;
|
|
2426
|
+
} catch {
|
|
2427
|
+
return false;
|
|
2428
|
+
}
|
|
2429
|
+
if (path4.resolve(dest) === path4.resolve(source)) return true;
|
|
2430
|
+
try {
|
|
2431
|
+
return fs4.realpathSync(dest) === fs4.realpathSync(source);
|
|
2432
|
+
} catch {
|
|
2433
|
+
return false;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
function isManagedInstall(dest) {
|
|
2437
|
+
let stat;
|
|
2438
|
+
try {
|
|
2439
|
+
stat = fs4.lstatSync(dest);
|
|
2440
|
+
} catch {
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
if (stat.isSymbolicLink()) return true;
|
|
2444
|
+
if (!stat.isDirectory()) return false;
|
|
2445
|
+
if (!fs4.existsSync(path4.join(dest, "references", "skills", "categories.json"))) {
|
|
2446
|
+
return false;
|
|
2447
|
+
}
|
|
2448
|
+
let head;
|
|
2449
|
+
try {
|
|
2450
|
+
head = fs4.readFileSync(path4.join(dest, "SKILL.md"), "utf8").slice(0, 4096);
|
|
2451
|
+
} catch {
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2454
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(head);
|
|
2455
|
+
if (!frontmatter) return false;
|
|
2456
|
+
return new RegExp(`^name:\\s*${SKILL_NAME}\\s*$`, "m").test(frontmatter[1]);
|
|
2457
|
+
}
|
|
2458
|
+
function install(source, t, copy) {
|
|
2459
|
+
let exists = true;
|
|
2460
|
+
try {
|
|
2461
|
+
fs4.lstatSync(t.dest);
|
|
2462
|
+
} catch {
|
|
2463
|
+
exists = false;
|
|
2464
|
+
}
|
|
2465
|
+
if (exists && isSameDirectory(t.dest, source)) return { action: "unchanged" };
|
|
2466
|
+
if (exists) {
|
|
2467
|
+
if (!isManagedInstall(t.dest)) {
|
|
2468
|
+
return {
|
|
2469
|
+
action: "skipped",
|
|
2470
|
+
error: `${t.dest} already exists and is not a copy of this skill; move or remove it first, then run skills:install again`
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
if (!copy) {
|
|
2474
|
+
try {
|
|
2475
|
+
if (fs4.realpathSync(t.dest) === fs4.realpathSync(source)) return { action: "unchanged" };
|
|
2476
|
+
} catch {
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
fs4.rmSync(t.dest, { recursive: true, force: true });
|
|
2480
|
+
}
|
|
2481
|
+
fs4.mkdirSync(path4.dirname(t.dest), { recursive: true });
|
|
2482
|
+
if (!copy) {
|
|
2483
|
+
try {
|
|
2484
|
+
fs4.symlinkSync(source, t.dest, "dir");
|
|
2485
|
+
return { action: exists ? "relinked" : "linked" };
|
|
2486
|
+
} catch (err) {
|
|
2487
|
+
note(`Symlink failed for ${t.dest} (${err?.code || err?.message}); copying instead.`);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
copySkillTree(source, t.dest);
|
|
2491
|
+
return { action: "copied" };
|
|
2492
|
+
}
|
|
2493
|
+
async function skillsInstall(argv) {
|
|
2494
|
+
const source = packageSkillDir();
|
|
2495
|
+
if (!fs4.existsSync(path4.join(source, "SKILL.md"))) {
|
|
2496
|
+
note(`Error: no skill found at ${source}. Reinstall superx-cli.`);
|
|
2497
|
+
process.exit(1);
|
|
2498
|
+
}
|
|
2499
|
+
const targets = argv.project ? projectTargets(argv.target) : homeTargets(argv.target);
|
|
2500
|
+
const results = targets.map((t) => {
|
|
2501
|
+
let outcome;
|
|
2502
|
+
try {
|
|
2503
|
+
outcome = install(source, t, Boolean(argv.copy));
|
|
2504
|
+
} catch (err) {
|
|
2505
|
+
outcome = { action: "failed", error: err?.message || String(err) };
|
|
2506
|
+
}
|
|
2507
|
+
if (outcome.error) note(`${t.label}: ${outcome.error}`);
|
|
2508
|
+
else note(`${t.label}: ${outcome.action} ${t.dest}`);
|
|
2509
|
+
return { target: t.label, path: t.dest, ...outcome };
|
|
2510
|
+
});
|
|
2511
|
+
const failed = results.filter((r) => r.error).length;
|
|
2512
|
+
printJson({
|
|
2513
|
+
data: {
|
|
2514
|
+
skill: SKILL_NAME,
|
|
2515
|
+
source,
|
|
2516
|
+
mode: argv.copy ? "copy" : "symlink",
|
|
2517
|
+
scope: argv.project ? "project" : "home",
|
|
2518
|
+
targets: results
|
|
2519
|
+
},
|
|
2520
|
+
meta: { installed: results.length - failed, failed }
|
|
2521
|
+
});
|
|
2522
|
+
if (failed) process.exit(1);
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2319
2525
|
// src/commands/docs.ts
|
|
2320
2526
|
async function docs() {
|
|
2321
2527
|
const apiUrl = resolveApiUrl(loadCredentials()?.apiUrl || DEFAULT_API_URL);
|
|
@@ -2513,12 +2719,15 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
2513
2719
|
),
|
|
2514
2720
|
run(toolsFactcheck)
|
|
2515
2721
|
).command(
|
|
2516
|
-
"
|
|
2517
|
-
"Score
|
|
2518
|
-
(y) => accountOption(y).option("
|
|
2519
|
-
"
|
|
2722
|
+
"posts:viral-score",
|
|
2723
|
+
"Score one draft against this account's own normal post, with what helped and what hurt",
|
|
2724
|
+
(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", {
|
|
2725
|
+
describe: "Score against the average training post instead of this account's own posts",
|
|
2726
|
+
type: "boolean"
|
|
2727
|
+
}).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(
|
|
2728
|
+
"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
2729
|
),
|
|
2521
|
-
run(
|
|
2730
|
+
run(postsViralScore)
|
|
2522
2731
|
).command(
|
|
2523
2732
|
"replies:list",
|
|
2524
2733
|
"List replies the account has sent (newest first)",
|
|
@@ -3495,6 +3704,30 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
|
3495
3704
|
"Show the account's DM allowances and how much of each is used (free)",
|
|
3496
3705
|
(y) => accountOption(y),
|
|
3497
3706
|
run(dmLimits)
|
|
3707
|
+
).command(
|
|
3708
|
+
"skills:list",
|
|
3709
|
+
"List the SuperX skills bundled with this package (no auth needed)",
|
|
3710
|
+
(y) => y.example("$0 skills:list", "Every skill's id, name and category as JSON").epilogue(
|
|
3711
|
+
"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."
|
|
3712
|
+
),
|
|
3713
|
+
run(skillsList)
|
|
3714
|
+
).command(
|
|
3715
|
+
"skills:install",
|
|
3716
|
+
"Install the bundled SuperX skill where your agent looks for skills",
|
|
3717
|
+
(y) => y.option("target", {
|
|
3718
|
+
describe: "Where to install: claude (~/.claude/skills), agents (~/.agents/skills), openclaw (~/.openclaw/skills), all. Default: claude + agents, plus openclaw when ~/.openclaw exists",
|
|
3719
|
+
type: "string",
|
|
3720
|
+
choices: ["claude", "agents", "openclaw", "all"]
|
|
3721
|
+
}).option("project", {
|
|
3722
|
+
describe: "Install into the current directory (.claude/skills, .agents/skills, skills) instead of your home directory",
|
|
3723
|
+
type: "boolean"
|
|
3724
|
+
}).option("copy", {
|
|
3725
|
+
describe: "Copy the files instead of symlinking them (also the automatic fallback when a symlink is refused)",
|
|
3726
|
+
type: "boolean"
|
|
3727
|
+
}).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(
|
|
3728
|
+
"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."
|
|
3729
|
+
),
|
|
3730
|
+
run(skillsInstall)
|
|
3498
3731
|
).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
3732
|
if (err) throw err;
|
|
3500
3733
|
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.6.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": {
|
|
@@ -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
|
],
|