superx-cli 0.5.1 → 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.
Files changed (72) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +9 -5
  3. package/dist/index.js +255 -18
  4. package/package.json +5 -5
  5. package/skills/superx/SKILL.md +448 -0
  6. package/{SKILL.md → skills/superx/references/commands.md} +47 -429
  7. package/{PLAYBOOK.md → skills/superx/references/growth-strategy.md} +3 -3
  8. package/skills/superx/references/skills/README.md +31 -0
  9. package/skills/superx/references/skills/audience-export/card.json +20 -0
  10. package/skills/superx/references/skills/audience-export/recipe.md +13 -0
  11. package/skills/superx/references/skills/cadence-and-queue-audit/card.json +22 -0
  12. package/skills/superx/references/skills/cadence-and-queue-audit/recipe.md +17 -0
  13. package/skills/superx/references/skills/categories.json +41 -0
  14. package/skills/superx/references/skills/daily-post-ideas/card.json +22 -0
  15. package/skills/superx/references/skills/daily-post-ideas/recipe.md +17 -0
  16. package/skills/superx/references/skills/dm-ready-audience-builder/card.json +21 -0
  17. package/skills/superx/references/skills/dm-ready-audience-builder/recipe.md +16 -0
  18. package/skills/superx/references/skills/find-your-story/card.json +23 -0
  19. package/skills/superx/references/skills/find-your-story/recipe.md +17 -0
  20. package/skills/superx/references/skills/growth-plan-builder/card.json +21 -0
  21. package/skills/superx/references/skills/growth-plan-builder/recipe.md +17 -0
  22. package/skills/superx/references/skills/instant-lead-hunt/card.json +21 -0
  23. package/skills/superx/references/skills/instant-lead-hunt/recipe.md +14 -0
  24. package/skills/superx/references/skills/lead-review/card.json +20 -0
  25. package/skills/superx/references/skills/lead-review/recipe.md +16 -0
  26. package/skills/superx/references/skills/my-content-export/card.json +21 -0
  27. package/skills/superx/references/skills/my-content-export/recipe.md +15 -0
  28. package/skills/superx/references/skills/my-replies-report/card.json +20 -0
  29. package/skills/superx/references/skills/my-replies-report/recipe.md +13 -0
  30. package/skills/superx/references/skills/post-post-mortem/card.json +20 -0
  31. package/skills/superx/references/skills/post-post-mortem/recipe.md +16 -0
  32. package/skills/superx/references/skills/profile-research-briefs/card.json +21 -0
  33. package/skills/superx/references/skills/profile-research-briefs/recipe.md +15 -0
  34. package/skills/superx/references/skills/queue-reshuffle/card.json +21 -0
  35. package/skills/superx/references/skills/queue-reshuffle/recipe.md +15 -0
  36. package/skills/superx/references/skills/reply-sprint/card.json +20 -0
  37. package/skills/superx/references/skills/reply-sprint/recipe.md +12 -0
  38. package/skills/superx/references/skills/reply-to-any-post/card.json +20 -0
  39. package/skills/superx/references/skills/reply-to-any-post/recipe.md +15 -0
  40. package/skills/superx/references/skills/reply-to-dm-campaign/card.json +21 -0
  41. package/skills/superx/references/skills/reply-to-dm-campaign/recipe.md +18 -0
  42. package/skills/superx/references/skills/repurpose-a-winner/card.json +21 -0
  43. package/skills/superx/references/skills/repurpose-a-winner/recipe.md +16 -0
  44. package/skills/superx/references/skills/sentiment-slice/card.json +20 -0
  45. package/skills/superx/references/skills/sentiment-slice/recipe.md +15 -0
  46. package/skills/superx/references/skills/standing-lead-agent/card.json +21 -0
  47. package/skills/superx/references/skills/standing-lead-agent/recipe.md +17 -0
  48. package/skills/superx/references/skills/teach-superx-your-rules/card.json +21 -0
  49. package/skills/superx/references/skills/teach-superx-your-rules/recipe.md +15 -0
  50. package/skills/superx/references/skills/thread-builder/card.json +20 -0
  51. package/skills/superx/references/skills/thread-builder/recipe.md +14 -0
  52. package/skills/superx/references/skills/top-performers-breakdown/card.json +21 -0
  53. package/skills/superx/references/skills/top-performers-breakdown/recipe.md +13 -0
  54. package/skills/superx/references/skills/trending-now-scan/card.json +21 -0
  55. package/skills/superx/references/skills/trending-now-scan/recipe.md +14 -0
  56. package/skills/superx/references/skills/viral-format-remix/card.json +20 -0
  57. package/skills/superx/references/skills/viral-format-remix/recipe.md +15 -0
  58. package/skills/superx/references/skills/viral-score-iterate/card.json +22 -0
  59. package/skills/superx/references/skills/viral-score-iterate/recipe.md +15 -0
  60. package/skills/superx/references/skills/warm-outreach-pipeline/card.json +21 -0
  61. package/skills/superx/references/skills/warm-outreach-pipeline/recipe.md +19 -0
  62. package/skills/superx/references/skills/week-of-posts/card.json +19 -0
  63. package/skills/superx/references/skills/week-of-posts/recipe.md +18 -0
  64. package/skills/superx/references/skills/weekly-growth-recap/card.json +22 -0
  65. package/skills/superx/references/skills/weekly-growth-recap/recipe.md +17 -0
  66. package/skills/superx/references/skills/who-is-this-person/card.json +20 -0
  67. package/skills/superx/references/skills/who-is-this-person/recipe.md +16 -0
  68. package/skills/superx/references/skills/worker-output-review/card.json +20 -0
  69. package/skills/superx/references/skills/worker-output-review/recipe.md +18 -0
  70. package/skills/superx/references/skills/your-warmest-leads/card.json +21 -0
  71. package/skills/superx/references/skills/your-warmest-leads/recipe.md +14 -0
  72. package/PLAYBOOKS.md +0 -542
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
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
+
15
+ ## 0.5.2 (2026-09-16)
16
+
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
18
+
3
19
  ## 0.5.1 (2026-09-14)
4
20
 
5
21
  - Lead search recency: `signals:search --max-age-days <n>` only counts posts from the last N days (1-90, default 30), so every lead comes from something written recently rather than a lifetime match. Older matches are skipped and counted in `freshness.stale_skipped`, each lead's `provenance.posted_at` / `provenance.post_age_days` says when the matched post was written, and leads come back freshest first within each score. Use `--max-age-days 7` to catch a pain point while it is fresh; an empty result with `stale_skipped` above 0 means people do post about this, just not lately. `SKILL.md` and `PLAYBOOKS.md` (lead hunt recipe) carry the flag
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 (`PLAYBOOK.md`) and 29 goal-shaped recipes (`PLAYBOOKS.md`) so agents do not just schedule posts, they follow a strategy that works
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 [SKILL.md](./SKILL.md), a complete agent reference with hard rules, workflows, and gotchas.
346
- - **Strategy included**: [PLAYBOOK.md](./PLAYBOOK.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.
347
- - **Playbooks included**: [PLAYBOOKS.md](./PLAYBOOKS.md) holds 29 goal-shaped recipes, one 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.
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 PLAYBOOK.md, draft content ...
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
- async predictAlgorithm(body) {
201
- return (await this.request("/tools/algorithm-predict", { method: "POST", body })).json;
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((resolve) => {
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
- resolve(answer.trim());
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((resolve) => setTimeout(resolve, WAIT_POLL_MS));
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);
@@ -1207,6 +1213,7 @@ async function signalsSearch(argv) {
1207
1213
  keywords: argv.keywords,
1208
1214
  icp_description: argv.icp
1209
1215
  };
1216
+ if (argv.offer) body.offer = argv.offer;
1210
1217
  if (argv.precision) body.precision = argv.precision;
1211
1218
  if (argv.max !== void 0) body.max_leads = argv.max;
1212
1219
  if (argv["max-age-days"] !== void 0) {
@@ -2315,6 +2322,206 @@ async function queueSet(argv) {
2315
2322
  printJson(await api.updateQueueSettings(body));
2316
2323
  }
2317
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
+
2318
2525
  // src/commands/docs.ts
2319
2526
  async function docs() {
2320
2527
  const apiUrl = resolveApiUrl(loadCredentials()?.apiUrl || DEFAULT_API_URL);
@@ -2512,12 +2719,15 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
2512
2719
  ),
2513
2720
  run(toolsFactcheck)
2514
2721
  ).command(
2515
- "tools:predict",
2516
- "Score two versions of a post against what the timeline rewards (costs AI credits)",
2517
- (y) => accountOption(y).option("a", { describe: "The first version (required)", type: "string", demandOption: true }).option("b", { describe: "The second version (required)", type: "string", demandOption: true }).example('$0 tools:predict --a "$(cat v1.txt)" --b "$(cat v2.txt)"', "Compare two drafts").epilogue(
2518
- "The scores are a model's opinion, useful for comparing two drafts against each other, not a prediction of real reach."
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."
2519
2729
  ),
2520
- run(toolsPredict)
2730
+ run(postsViralScore)
2521
2731
  ).command(
2522
2732
  "replies:list",
2523
2733
  "List replies the account has sent (newest first)",
@@ -2837,13 +3047,16 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
2837
3047
  "signals:search",
2838
3048
  "Search X now for people matching an audience description (saves nothing)",
2839
3049
  (y) => accountOption(y).option("keywords", {
2840
- describe: "Plain-language phrases these people would post, comma-separated for alternatives. No search operators",
3050
+ describe: "2-5 seed angles of how the BUYER talks, 2-3 words each, comma-separated: workflows, tools they already pay for, jargon, a symptom. Not the product's name, no search operators",
2841
3051
  type: "string",
2842
3052
  demandOption: true
2843
3053
  }).option("icp", {
2844
3054
  describe: "Who counts as a good lead, in 1-2 sentences: role, domain, and the intent that qualifies them",
2845
3055
  type: "string",
2846
3056
  demandOption: true
3057
+ }).option("offer", {
3058
+ describe: "What you are selling, one sentence (3-300 chars). The search plans its queries from this",
3059
+ type: "string"
2847
3060
  }).option("precision", {
2848
3061
  describe: "high = only confident matches; discovery (default) = broader adjacent matches",
2849
3062
  type: "string",
@@ -2852,7 +3065,7 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
2852
3065
  describe: "Only posts from the last N days count (1-90, default 30). Use 7 to catch a fresh pain point",
2853
3066
  type: "number"
2854
3067
  }).example(
2855
- "$0 signals:search --keywords 'losing customers to churn' --icp 'B2B SaaS founders worried about retention'",
3068
+ "$0 signals:search --offer 'ChurnRadar, retention analytics for B2B SaaS' --keywords 'cancelled today, mrr dropped, renewal call' --icp 'B2B SaaS founders worried about retention'",
2856
3069
  "Find people posting about churn right now"
2857
3070
  ).epilogue(
2858
3071
  "This CREATES NOTHING: no signal agent, no saved leads. Use signals:create-agent for an audience that keeps filling up. Reading X live costs AI credits (at least 1) plus one of the plan's daily lead searches, and draws on a platform-wide fair-use ceiling shared by every account. Takes up to a minute."
@@ -3491,6 +3704,30 @@ var advancedSettingsOptions = (y) => y.option("auto-retweet", {
3491
3704
  "Show the account's DM allowances and how much of each is used (free)",
3492
3705
  (y) => accountOption(y),
3493
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)
3494
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) => {
3495
3732
  if (err) throw err;
3496
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.5.1",
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
- "prepublishOnly": "npm run build"
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
  ],