superx-cli 0.1.0 → 0.3.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 +11 -1
- package/PLAYBOOK.md +4 -3
- package/README.md +111 -11
- package/SKILL.md +130 -12
- package/dist/index.js +391 -6
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.3.0 (2026-09-05)
|
|
4
4
|
|
|
5
|
+
- Engage feeds: `engage:feeds` lists the keyword and list feeds saved in the app (with `type`, `active`, and `fetch_units`), and `engage:posts <feedId>` fetches one feed's candidate posts for review (`--account`, `--limit` 1-50, `--mode top|latest`, `--fresh`, `--include-replied`, `--exclude` comma list of post ids to page with). Read-only by design: replies are written and sent by a person in the SuperX app, so there is no reply command. `--limit` applies to keyword feeds; list feeds return one page per fetch and are paged with `--exclude`. Feed fetches have their own per-plan daily allowance and a list feed that rotates its members counts as 3
|
|
6
|
+
- Inspiration search relevance: `inspiration:search` results are now relevance-ranked, strongest matches first and more loosely related posts after. BEHAVIOR CHANGE: results are no longer a fresh varied mix on every run, and weak and promotional matches are filtered out, so a page can return fewer posts than `--limit`; `has_more` no longer requires a full page, so keep paging with `--page` while it is true (pages run 1 to 7). `--sort relevant` is still the default and is the relevance-ranked order
|
|
7
|
+
- Docs: write commands now describe `--account` for linked accounts (server change shipped Sep 4 2026)
|
|
8
|
+
|
|
9
|
+
## 0.2.0 (2026-08-01)
|
|
10
|
+
|
|
11
|
+
- Advanced post settings: `scheduled:create`/`scheduled:update` gain `--auto-retweet <h>` / `--auto-retweet-remove <h>` (1-12), `--auto-delete <h>` / `--auto-delete-threshold <views>`, `--auto-plug <templateId>` / `--auto-plug-threshold <likes>`, `--super-followers`, plus `--no-*` disable forms; new `plug-templates:list` command. BEHAVIOR CHANGE: posts created via the API now inherit the account's Default Post Settings (auto retweet, auto delete, auto plug, auto DM, Super Followers only) when the matching flags are omitted, exactly like posts composed in the app; previously API posts got no advanced settings at all. Auto DM stays inherit-only (no flag) and surfaces `auto_dm_skipped: true` when a plan limit strips it
|
|
5
12
|
- Media: `media:upload <file>` uploads a local image (JPG/PNG/WEBP 5MB, GIF 15MB) and prints its `object_key`; `scheduled:create`/`scheduled:update` gain `--media` (comma list of keys), `--alt-text` (single key), and `--parts-json` for threads with per-part media. Text replacement on update is a full replace, media included
|
|
6
13
|
- Signal agent writes: `signals:create-agent` (--name, --icp, repeatable --keyword, --precision, --list-id, --idempotency-key with replay detection), `signals:pause-agent <id>`, `signals:resume-agent <id>`, `signals:delete-agent <id>`
|
|
14
|
+
- Context settings: `context:get` (the account's AI writing background: profile description, interests, SuperX rules, reply settings, favorite creators, style guide, products), `context:set` (present flags only change; `""` clears a string; `--interests`/`--favorite-creators` comma lists fully replace; boolean flags support `--no-*`), `context:products`, `context:products:set` (upsert by `--url` or edit by `--id`), `context:products:delete <id>`
|
|
15
|
+
- Queue settings: `queue:get` (the account's posting schedule: predefined time slots and their timezone) and `queue:set` (`--slots-json` full replace, max 50, 0 = Sunday, `'[]'` clears; `--timezone` IANA name). Changing the slots also re-flows queued posts onto them the way the app does, reported as `reflow: { moved, skipped, bailed }`; a timezone-only change never moves posts
|
|
16
|
+
- Shared accounts: `accounts` now lists accounts shared with you (manual and team shares) alongside your own, each with `shared` and `permission`. `--account` accepts them everywhere, and `context:*` / `queue:set` can write to them. A share with Editor permission can change queue settings but gets 403 `editor_restricted` on context writes
|
|
7
17
|
|
|
8
18
|
## 0.1.0 (2026-07-06)
|
|
9
19
|
|
package/PLAYBOOK.md
CHANGED
|
@@ -34,7 +34,7 @@ A good post can go from a handful of follower likes to thousands of stranger vie
|
|
|
34
34
|
- Reply early under larger accounts in the niche with something additive (a fact, a sharp take, a smart follow-up). Never "great post" filler.
|
|
35
35
|
- Time the first window: schedule the best post of the day for when the audience is active, then protect the first 30 to 60 minutes by replying to every response fast. Early engagement compounds.
|
|
36
36
|
|
|
37
|
-
Actions: `superx scheduled:create --at` for peak-time scheduling; `superx replies:list` to confirm the account is actually participating in conversations, not just broadcasting.
|
|
37
|
+
Actions: `superx scheduled:create --at` for peak-time scheduling; `superx queue:get` to see the account's standing posting slots and `superx queue:set --slots-json ...` to move that cadence onto the hours the audience is actually awake (queued posts follow the slots automatically); `superx replies:list` to confirm the account is actually participating in conversations, not just broadcasting.
|
|
38
38
|
|
|
39
39
|
## 4. The engagement loop (3-3-3)
|
|
40
40
|
|
|
@@ -45,7 +45,7 @@ Consistent, targeted interaction grows accounts faster than posting alone:
|
|
|
45
45
|
- After a genuine back-and-forth, a soft pointer to related content on the profile is fine. Self-promo inside someone else's thread is not.
|
|
46
46
|
- Budget: about 20 minutes outbound and 20 minutes inbound daily.
|
|
47
47
|
|
|
48
|
-
Data: `superx contacts:list --sort engagement` identifies who already engages most (reply to them first); `superx contacts:replies <id>` shows the history with one person so replies can be specific. For the inbound block, `superx replies:received --sort recent` lists every reply the audience has sent across all posts, so nothing goes unanswered. Keep the 3-3-3 circle in a contact list (`superx lists:list`, `lists:add-member`) so the daily targets survive between sessions. If the account runs signal agents, `superx signals:leads` surfaces fresh people matching the ideal customer profile, with the post that revealed them; engage while the discovery is recent. No agent yet? `superx signals:create-agent --name ... --icp ...` sets one up from a plain-language customer description (keywords auto-suggested when omitted); leads accumulate over the following days, so create it early in the week and harvest with `signals:leads` later.
|
|
48
|
+
Data: `superx contacts:list --sort engagement` identifies who already engages most (reply to them first); `superx contacts:replies <id>` shows the history with one person so replies can be specific. For the inbound block, `superx replies:received --sort recent` lists every reply the audience has sent across all posts, so nothing goes unanswered. The Engage feeds saved in the app are another source: `superx engage:feeds` lists them and `superx engage:posts <feedId>` returns the posts one surfaces, ready to score before a person writes and sends the reply in the app. Keep the 3-3-3 circle in a contact list (`superx lists:list`, `lists:add-member`) so the daily targets survive between sessions. If the account runs signal agents, `superx signals:leads` surfaces fresh people matching the ideal customer profile, with the post that revealed them; engage while the discovery is recent. No agent yet? `superx signals:create-agent --name ... --icp ...` sets one up from a plain-language customer description (keywords auto-suggested when omitted); leads accumulate over the following days, so create it early in the week and harvest with `signals:leads` later.
|
|
49
49
|
|
|
50
50
|
## 5. The research system
|
|
51
51
|
|
|
@@ -61,10 +61,11 @@ Data: `superx posts:list --sort likes --since <60d ago>` and `--sort impressions
|
|
|
61
61
|
|
|
62
62
|
A sustainable weekly loop an agent can run:
|
|
63
63
|
|
|
64
|
-
1. Confirm the 1-2 topic clusters for the week; every post should fit one.
|
|
64
|
+
1. Confirm the 1-2 topic clusters for the week; every post should fit one. `superx context:get` shows the account's stated interests, hard rules, profile description, and products: drafts must respect the rules and fit the interests. If the user's focus has shifted, update it (`superx context:set --interests ...`) so every AI surface in the app follows, and get confirmation first: these settings shape ALL future AI output for the account.
|
|
65
65
|
2. Pull last week's winners (`posts:list --sort likes --since ...`) and note why each worked.
|
|
66
66
|
3. Plan roughly 7 posts for the week; draft them (`scheduled:create` without `--at`, with a `--title` naming the angle and a `--tag` for the week's cluster so the human can scan the batch), review, then promote the best 3 to peak times (`scheduled:update <id> --at ... --status scheduled`).
|
|
67
67
|
4. Include one format experiment per week (a thread via `--part`, a longer post, or a long-form X Article via `articles:create` when a topic deserves depth: draft it, add a cover with `articles:cover`, and let the human review before `articles:publish`) so format reach is never left untested.
|
|
68
|
+
For the week's strongest post, consider the advanced settings: `--auto-retweet 6` gives it a second push into a different timezone window, and `--auto-plug <template-id> --auto-plug-threshold 50` (ids from `plug-templates:list`) turns a winner into a lead-in for the account's offer. Posts inherit the account's Default Post Settings automatically when the flags are omitted; use `--no-auto-retweet`/`--no-auto-plug` on posts where the defaults do not fit (see SKILL.md for the full flag set).
|
|
68
69
|
5. Refresh the 3-3-3 circle (`contacts:list`) and do the daily reply blocks.
|
|
69
70
|
6. End of week: `posts:analytics` for the trend, top 3 posts by meaningful actions, one failure mode to fix with a rule (for example "no link-drop posts", "never ghost early replies").
|
|
70
71
|
7. Repurpose one winner into two new assets for next week (tighter version, thread expansion, follow-up take).
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ npx skills add superx-so/superx-agent
|
|
|
6
6
|
|
|
7
7
|
# SuperX CLI
|
|
8
8
|
|
|
9
|
-
**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),
|
|
9
|
+
**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).
|
|
10
10
|
|
|
11
11
|
Two things ship in this repo:
|
|
12
12
|
|
|
@@ -27,7 +27,7 @@ Requires Node.js 18 or newer.
|
|
|
27
27
|
|
|
28
28
|
## Authentication
|
|
29
29
|
|
|
30
|
-
Create an API key in the SuperX app: [app.superx.so/account?tab=
|
|
30
|
+
Create an API key in the SuperX app: [app.superx.so/account?tab=api](https://app.superx.so/account?tab=api)
|
|
31
31
|
|
|
32
32
|
### Option 1: Guided login (local use)
|
|
33
33
|
|
|
@@ -80,7 +80,7 @@ superx me # Key owner, plan tier, key name and scopes
|
|
|
80
80
|
superx accounts # X accounts this key can read (main account first)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
Read commands accept `--account <id>` (an id from `superx accounts`) to select a linked account. Omitting it means the main account.
|
|
83
|
+
Read commands accept `--account <id>` (an id from `superx accounts`) to select a linked or shared account. Write commands accept it too for your main or linked accounts; accounts shared with you by other people are read-only (403 `writes_main_account_only`). Omitting it means the main account.
|
|
84
84
|
|
|
85
85
|
### Posts
|
|
86
86
|
|
|
@@ -124,7 +124,7 @@ superx inspiration:search "indie hackers" --sort outlier --min-likes 500
|
|
|
124
124
|
superx inspiration:search "AI tools" --min-followers 1000 --max-followers 50000
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
Searches a library of 50M+ real high-performing posts by topic. Options: `--sort relevant|recent|likes|reposts|impressions|outlier`, `--min-likes/--min-reposts/--min-replies/--min-bookmarks/--min-impressions`, `--min-followers/--max-followers` (author size), `--since/--until` (UTC ISO-8601), `--lang` (default en), `--exclude-topics "crypto,politics"`, `--limit` (max 50), `--page` (1-7). Each result carries an `outlier_score`: how far the post outperformed the norm for its author's follower tier. Results are
|
|
127
|
+
Searches a library of 50M+ real high-performing posts by topic. Options: `--sort relevant|recent|likes|reposts|impressions|outlier`, `--min-likes/--min-reposts/--min-replies/--min-bookmarks/--min-impressions`, `--min-followers/--max-followers` (author size), `--since/--until` (UTC ISO-8601), `--lang` (default en), `--exclude-topics "crypto,politics"`, `--limit` (max 50), `--page` (1-7). Each result carries an `outlier_score`: how far the post outperformed the norm for its author's follower tier. Results are relevance-ranked, strongest matches first, with weak and promotional matches filtered out, so a page may return fewer than `--limit` posts. Use them for structures and hooks to remix, never to copy.
|
|
128
128
|
|
|
129
129
|
### Contacts (who engages with you)
|
|
130
130
|
|
|
@@ -142,7 +142,24 @@ superx lists:add-member <list-id> --handle levelsio # or --x-user-id 44196397
|
|
|
142
142
|
superx lists:remove-member <list-id> <member-id>
|
|
143
143
|
```
|
|
144
144
|
|
|
145
|
-
Lists are the saved people-collections from the SuperX app. System lists (Followers, Following, Repliers, Reposters) appear in `lists:list` but are read-only and their members are not available through the API (400 `system_list_not_supported`). Adding someone already in a list is harmless: the API returns the existing member with `"duplicate": true` and writes nothing. Member writes
|
|
145
|
+
Lists are the saved people-collections from the SuperX app. System lists (Followers, Following, Repliers, Reposters) appear in `lists:list` but are read-only and their members are not available through the API (400 `system_list_not_supported`). Adding someone already in a list is harmless: the API returns the existing member with `"duplicate": true` and writes nothing. Member writes work on your main or linked accounts with `--account`; shared accounts are read-only.
|
|
146
|
+
|
|
147
|
+
### Engage (feed posts to reply to)
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
superx engage:feeds # the feeds saved in the app's Engage tab
|
|
151
|
+
superx engage:posts <feed-id> --limit 50 # one big page of candidate posts
|
|
152
|
+
superx engage:posts <feed-id> --mode latest --fresh true
|
|
153
|
+
superx engage:posts <feed-id> --exclude 1234567890,1234567891 # next batch, minus what you have
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Engage feeds are the keyword and list feeds set up in the SuperX app. `engage:feeds` returns each feed's `id`, `name`, `type` (`keywords`, `list`, or `x_list`), `active` flag, and `fetch_units` (what one fetch of it costs: 1 for a keyword feed, at most 3 for list feeds, and an imported X list charges 1 at fetch time). Feeds are created and edited in the app, not through the API.
|
|
157
|
+
|
|
158
|
+
`engage:posts <feed-id>` returns candidate posts with text, author (handle, bio, follower counts), engagement metrics, and post time, plus `has_more` and the `feed` it came from. Results are for review: replies are written and sent by a person in SuperX, so there is no reply command. Sending replies that read as inauthentic can get an X account suspended under X's inauthentic-behavior rules and a SuperX account terminated; AI output must be reviewed and meaningfully edited by a person before it is posted, and reply activity is logged and may be audited.
|
|
159
|
+
|
|
160
|
+
`--limit` (1-50, default 20) applies to keyword feeds. List feeds ignore it upstream and return one page per fetch, about 10 posts for a member list and 20 to 25 for an imported X list, with `--limit` only trimming that page; page those with `--exclude` instead. On a keyword feed a 50-post page costs the same as a 20-post page, so ask for 50 a few times a day and filter locally rather than polling. Feeds refresh over hours, so fetching more often than hourly returns the same posts. `--mode top|latest` defaults to `top`; `--fresh true` skips the cache; `--include-replied true` keeps posts already replied to, skipped, or blocked and flags them with `replied`. `--exclude` takes up to 100 post ids and is how you page.
|
|
161
|
+
|
|
162
|
+
Feed fetches have their own per-plan daily allowance (Trial 20, Pro 60, Advanced 120, Ultra 300 per day), separate from the read budget; per minute: trial 2, pro 5, advanced 10, ultra 15. A list feed that rotates its members counts as up to 3 fetches, every other feed as 1; `engage:feeds` never touches the allowance. Over the cap you get 429 `rate_limited`: the API returns `remaining_day`; the CLI prints the message and the retry delay. Fetches also run a few at a time across all API users, so a 429 with a retry delay can mean busy rather than out of allowance; wait and retry. Posts a fetch returns count as seen and are demoted in later fetches, in the app as well as the API. An unknown feed id returns 404 `feed_not_found`.
|
|
146
163
|
|
|
147
164
|
### Signals (automated lead finding)
|
|
148
165
|
|
|
@@ -152,7 +169,7 @@ superx signals:leads --limit 20 # newest leads across all a
|
|
|
152
169
|
superx signals:leads --agent 3 --deposited false # new leads from one agent
|
|
153
170
|
superx signals:leads --since "2026-07-01T00:00:00Z" # leads discovered since July
|
|
154
171
|
|
|
155
|
-
# Create an agent (main account
|
|
172
|
+
# Create an agent (write scope; main or linked account via --account)
|
|
156
173
|
superx signals:create-agent \
|
|
157
174
|
--name "Build in public founders" \
|
|
158
175
|
--icp "Indie founders building SaaS in public, sharing MRR and launches" \
|
|
@@ -218,7 +235,23 @@ superx scheduled:update <post-id> --clear-tags --clear-title --clear-scratchpad
|
|
|
218
235
|
|
|
219
236
|
A new `--at` time alone never schedules a draft; pass `--status scheduled` explicitly. CAUTION: replacement text is a full replace, media included. `--text` without `--media` removes any images the post carried; re-list the current `object_key`s (visible in `scheduled:list`) to keep them. Title, scratchpad, tag, and time edits never touch media.
|
|
220
237
|
|
|
221
|
-
Write constraints in the current API version: main account only; images via `media:upload` (no video); one of `--text`, `--part`, or `--parts-json`. Idempotent replays add `"replayed": true` to the output. Note that drafts have no scheduled time, so `--from/--to` filters exclude them.
|
|
238
|
+
Write constraints in the current API version: main or linked accounts via `--account` (shared accounts are read-only; tags are workspace-wide and main account only); images via `media:upload` (no video); one of `--text`, `--part`, or `--parts-json`. Idempotent replays add `"replayed": true` to the output. Note that drafts have no scheduled time, so `--from/--to` filters exclude them.
|
|
239
|
+
|
|
240
|
+
### Advanced settings
|
|
241
|
+
|
|
242
|
+
Auto retweet, auto delete, auto plug, and Super Followers only are available as flags on `scheduled:create` and `scheduled:update`.
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" --auto-retweet 6 --auto-retweet-remove 4
|
|
246
|
+
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" --auto-delete 8 --auto-delete-threshold 500
|
|
247
|
+
superx plug-templates:list # template ids for --auto-plug
|
|
248
|
+
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" --auto-plug <template-id> --auto-plug-threshold 50
|
|
249
|
+
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" --no-auto-retweet --no-auto-plug
|
|
250
|
+
superx scheduled:update <post-id> --auto-retweet 2 # override on an existing post
|
|
251
|
+
superx scheduled:update <post-id> --no-auto-delete # remove from an existing post
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
On create, flags you omit inherit your Default Post Settings from the SuperX app (exactly five settings inherit: auto retweet, auto delete, auto plug, auto DM, Super Followers only; other composer defaults like Bluesky cross-posting never apply to API posts); the `--no-*` forms turn a setting off for that post. On update there is no inheritance: passed flags override, omitted flags keep the post's current settings, `--no-*` removes them. Hours are 1-12; `--auto-plug` needs `--auto-plug-threshold` (likes). Auto DM has no flag and always follows your app defaults; if a plan limit strips it at create time the response carries `"auto_dm_skipped": true`. `scheduled:list` shows the applied settings per post.
|
|
222
255
|
|
|
223
256
|
### Tags
|
|
224
257
|
|
|
@@ -231,6 +264,46 @@ superx tags:delete <tag-id> # also removes it from every po
|
|
|
231
264
|
|
|
232
265
|
Tag names are unique (409 `duplicate_name` on collision) and capped at 40 characters.
|
|
233
266
|
|
|
267
|
+
### Context settings (AI writing background)
|
|
268
|
+
|
|
269
|
+
The Context settings are the background SuperX's AI uses when writing for the account: profile description, interests, hard rules, reply settings, favorite creators, style-guide overrides, and products. Editing them changes every AI writing surface in the app.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
superx context:get # the whole context document
|
|
273
|
+
|
|
274
|
+
# Only the flags you pass change; "" clears a string; lists fully replace
|
|
275
|
+
superx context:set --rules "Never use hashtags. Keep posts under 200 chars."
|
|
276
|
+
superx context:set --profile-description "Indie hacker building SuperX" --profile-description-enabled
|
|
277
|
+
superx context:set --interests "indie hacking,SaaS,AI agents" # replaces the list
|
|
278
|
+
superx context:set --favorite-creators "levelsio,marc_louvion" # max 3, replaces the list
|
|
279
|
+
superx context:set --reply-rules "Be helpful, never salesy" --no-reply-author-name
|
|
280
|
+
superx context:set --style-audience "Bootstrapped SaaS founders" # outranks the generated guide
|
|
281
|
+
superx context:set --style-audience "" # revert to the generated guide
|
|
282
|
+
|
|
283
|
+
# Products (max 5): mentioned naturally in generated content
|
|
284
|
+
superx context:products
|
|
285
|
+
superx context:products:set --url "https://superx.so" --name "SuperX" --description "X growth platform"
|
|
286
|
+
superx context:products:set --id 3 --updates "Shipped the public API"
|
|
287
|
+
superx context:products:delete <product-id>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Caps: profile description, rules, and reply rules 500 characters; style audience 600; style vocabulary 1000; 30 interests of 50 characters each; 3 favorite creators; 5 products. `context:get` also returns the read-only generated style guide (`style_guide.generated`). `context:products:set --url` creates the product when it does not exist; removing a product is reversible by re-adding the same url. Writes need a key with the write scope, work on any linked or shared account via `--account`, and return 403 `editor_restricted` on a share with Editor permission.
|
|
291
|
+
|
|
292
|
+
### Queue settings (posting schedule)
|
|
293
|
+
|
|
294
|
+
The posting schedule is the set of predefined time slots the queue fills, plus the timezone they run in: the Edit Queue modal in the app.
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
superx queue:get # slots, timezone, and the is_default flags
|
|
298
|
+
|
|
299
|
+
# Slots are JSON so weekday sets stay unambiguous; 0 = Sunday
|
|
300
|
+
superx queue:set --slots-json '[{"time":"09:00","days":[1,2,3,4,5]},{"time":"17:30","days":[1,3,5]}]'
|
|
301
|
+
superx queue:set --timezone "Europe/London" # never moves queued posts
|
|
302
|
+
superx queue:set --slots-json '[]' # clear every predefined slot
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Slots are a full replace, max 50, one entry per unique time. Changing them also re-flows the queue the way the app does: a queued post sitting exactly on an old slot moves to the matching new slot, so gaps are preserved and hand-picked custom times stay put. The response carries `reflow: { moved, skipped, bailed }`; `bailed: true` means the settings were saved but the queue was left untouched on purpose, and rerunning the same command is safe. Changing the timezone and the slots in one call usually moves nothing, because the existing posts were placed under the old timezone; to re-flow them, change the timezone first, then send the slots in a second call. Writes need a key with the write scope and work on any linked or shared account via `--account`, Editor-permission shares included.
|
|
306
|
+
|
|
234
307
|
### Articles (long-form X posts)
|
|
235
308
|
|
|
236
309
|
Article bodies are **markdown in both directions**: headings (h1-h3), bullet and numbered lists (one nesting level), blockquotes, bold/italic/strikethrough, links, images by URL, and bare X post URLs as embeds. Code blocks and horizontal rules are not supported by the X Articles format and degrade to plain text (the response lists any degradations in `warnings`).
|
|
@@ -324,6 +397,8 @@ The CLI talks to these SuperX API endpoints (base `https://api.superx.so/v1`):
|
|
|
324
397
|
| `/signals/agents/:id` | PATCH | `signals:pause-agent <id>` / `signals:resume-agent <id>` |
|
|
325
398
|
| `/signals/agents/:id` | DELETE | `signals:delete-agent <id>` |
|
|
326
399
|
| `/signals/leads` | GET | `signals:leads` |
|
|
400
|
+
| `/engage/feeds` | GET | `engage:feeds` |
|
|
401
|
+
| `/engage/feeds/:id/posts` | GET | `engage:posts <feedId>` |
|
|
327
402
|
| `/media` | POST | `media:upload <file>` |
|
|
328
403
|
| `/scheduled-posts` | GET | `scheduled:list` |
|
|
329
404
|
| `/scheduled-posts` | POST | `scheduled:create` |
|
|
@@ -342,6 +417,12 @@ The CLI talks to these SuperX API endpoints (base `https://api.superx.so/v1`):
|
|
|
342
417
|
| `/articles/:id/schedule` | POST | `articles:schedule <id>` |
|
|
343
418
|
| `/articles/:id/unschedule` | POST | `articles:unschedule <id>` |
|
|
344
419
|
| `/articles/:id/cover` | POST | `articles:cover <id>` |
|
|
420
|
+
| `/context` | GET | `context:get`, `context:products` |
|
|
421
|
+
| `/context` | PATCH | `context:set` |
|
|
422
|
+
| `/context/products/:id` | PATCH | `context:products:set` |
|
|
423
|
+
| `/context/products/:id` | DELETE | `context:products:delete <id>` |
|
|
424
|
+
| `/queue-settings` | GET | `queue:get` |
|
|
425
|
+
| `/queue-settings` | PATCH | `queue:set` |
|
|
345
426
|
| `/docs` | GET | `docs` (no auth) |
|
|
346
427
|
|
|
347
428
|
Full API reference: [docs.superx.so](https://docs.superx.so)
|
|
@@ -367,7 +448,8 @@ Exit code `0` = success, `1` = error. Error codes come straight from the API:
|
|
|
367
448
|
|------|---------|
|
|
368
449
|
| `invalid_api_key` (401) | Bad or revoked key; run `superx login` again |
|
|
369
450
|
| `insufficient_scope` (403) | Read-only key used for a write |
|
|
370
|
-
| `writes_main_account_only` (403) | `scheduled:create` with a linked account |
|
|
451
|
+
| `writes_main_account_only` (403) | `scheduled:create` with a linked or shared account |
|
|
452
|
+
| `editor_restricted` (403) | `context:set` or `context:products:*` on a share with Editor permission |
|
|
371
453
|
| `subscription_required` (403) | SuperX subscription lapsed |
|
|
372
454
|
| `account_not_found` (404) | `--account` id is not one of your accounts |
|
|
373
455
|
| `list_not_found` (404) | List id is not one of your contact lists |
|
|
@@ -413,9 +495,12 @@ src/
|
|
|
413
495
|
├── contacts.ts # contacts:list / contacts:replies
|
|
414
496
|
├── lists.ts # lists:list / lists:members / lists:add-member / lists:remove-member
|
|
415
497
|
├── signals.ts # signals:agents / signals:leads / signals:create-agent / signals:pause-agent / signals:resume-agent / signals:delete-agent
|
|
498
|
+
├── engage.ts # engage:feeds / engage:posts
|
|
416
499
|
├── media.ts # media:upload
|
|
417
500
|
├── scheduled.ts # scheduled:list / scheduled:create / scheduled:update / scheduled:delete
|
|
418
501
|
├── tags.ts # tags:list / tags:create / tags:update / tags:delete
|
|
502
|
+
├── context.ts # context:get / context:set / context:products / context:products:set / context:products:delete
|
|
503
|
+
├── queue.ts # queue:get / queue:set
|
|
419
504
|
├── articles.ts # articles:list/get/create/update/delete/publish/schedule/unschedule/cover
|
|
420
505
|
└── docs.ts # docs
|
|
421
506
|
```
|
|
@@ -448,18 +533,20 @@ superx lists:list
|
|
|
448
533
|
superx lists:members <list-id> --q "founder"
|
|
449
534
|
superx signals:agents
|
|
450
535
|
superx signals:leads --agent 3 --deposited false
|
|
536
|
+
superx engage:feeds
|
|
537
|
+
superx engage:posts <feed-id> --limit 50
|
|
451
538
|
|
|
452
|
-
# Contact list writes (main account)
|
|
539
|
+
# Contact list writes (main or linked account)
|
|
453
540
|
superx lists:add-member <list-id> --handle levelsio
|
|
454
541
|
superx lists:remove-member <list-id> <member-id>
|
|
455
542
|
|
|
456
|
-
# Signal agent writes (main account)
|
|
543
|
+
# Signal agent writes (main or linked account)
|
|
457
544
|
superx signals:create-agent --name "..." --icp "..." --keyword "..."
|
|
458
545
|
superx signals:pause-agent <id>
|
|
459
546
|
superx signals:resume-agent <id>
|
|
460
547
|
superx signals:delete-agent <id>
|
|
461
548
|
|
|
462
|
-
# Writes (main account)
|
|
549
|
+
# Writes (main or linked account via --account)
|
|
463
550
|
superx scheduled:create --text "Post" # Draft
|
|
464
551
|
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" # Scheduled
|
|
465
552
|
superx scheduled:create --part "1/" --part "2/" --at "..." # Thread
|
|
@@ -478,6 +565,19 @@ superx tags:create "Launch week" --color amber
|
|
|
478
565
|
superx tags:update <id> --name "Launch"
|
|
479
566
|
superx tags:delete <id>
|
|
480
567
|
|
|
568
|
+
# Context settings (AI writing background)
|
|
569
|
+
superx context:get
|
|
570
|
+
superx context:set --rules "Never use hashtags."
|
|
571
|
+
superx context:set --interests "indie hacking,SaaS" # replaces the list
|
|
572
|
+
superx context:products
|
|
573
|
+
superx context:products:set --url "https://superx.so" --name "SuperX"
|
|
574
|
+
superx context:products:delete <id>
|
|
575
|
+
|
|
576
|
+
# Queue settings (posting schedule; 0 = Sunday)
|
|
577
|
+
superx queue:get
|
|
578
|
+
superx queue:set --slots-json '[{"time":"09:00","days":[1,3,5]}]' # replaces the slots
|
|
579
|
+
superx queue:set --timezone "Europe/London" # never moves posts
|
|
580
|
+
|
|
481
581
|
# Articles (markdown bodies; publish needs X Premium)
|
|
482
582
|
superx articles:create --title "My article" --file draft.md
|
|
483
583
|
superx articles:list --status draft
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: superx
|
|
3
|
-
description: SuperX is a Twitter/X growth tool. Use it to read an account's published posts with engagement metrics, pull account analytics (impressions, likes, replies, follower change), find the people who engage with the account most, review reply history in both directions (sent and received), manage contact lists, create and manage signal agents (automated lead finders) and review the leads they discover, search a library of 50M+ high-performing posts for inspiration, create, edit, tag, or schedule draft posts and threads (with image attachments),
|
|
3
|
+
description: SuperX is a Twitter/X growth tool. Use it to read an account's published posts with engagement metrics, pull account analytics (impressions, likes, replies, follower change), find the people who engage with the account most, review reply history in both directions (sent and received), manage contact lists, create and manage signal agents (automated lead finders) and review the leads they discover, search a library of 50M+ high-performing posts for inspiration, read the Engage feed posts a saved feed surfaces for review, create, edit, tag, or schedule draft posts and threads (with image attachments), write, schedule, publish, and generate AI covers for long-form X Articles, and read or update the account's Context settings (profile description, interests, SuperX rules, reply settings, favorite creators, style guide, products) that steer SuperX's AI writing, all through the SuperX API.
|
|
4
4
|
homepage: https://docs.superx.so
|
|
5
5
|
metadata: {"openclaw":{"emoji":"🚀","requires":{"bins":["superx"],"env":[]}}}
|
|
6
6
|
---
|
|
@@ -21,18 +21,18 @@ official website: https://superx.so
|
|
|
21
21
|
| Property | Value |
|
|
22
22
|
|----------|-------|
|
|
23
23
|
| **name** | superx |
|
|
24
|
-
| **description** | Twitter/X growth CLI: posts, analytics, contacts, contact lists, audience replies, signal agents and their leads, inspiration, tags, scheduling (with images),
|
|
24
|
+
| **description** | Twitter/X growth CLI: posts, analytics, contacts, contact lists, audience replies, signal agents and their leads, inspiration, tags, scheduling (with images), long-form Articles, and Context settings (AI writing background) via the SuperX API |
|
|
25
25
|
| **allowed-tools** | Bash(superx:*) |
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
29
|
## Three Hard Rules (Read First)
|
|
30
30
|
|
|
31
|
-
**Rule 1: Run `superx status` before anything else.** Every other command fails without valid credentials. If the `superx` binary is missing, install it with `npm install -g superx-cli`. If not authenticated, either run `superx login` (interactive) or set `export SUPERX_API_KEY=sxk_...` (CI and non-interactive sessions). Keys are created at https://app.superx.so/account?tab=
|
|
31
|
+
**Rule 1: Run `superx status` before anything else.** Every other command fails without valid credentials. If the `superx` binary is missing, install it with `npm install -g superx-cli`. If not authenticated, either run `superx login` (interactive) or set `export SUPERX_API_KEY=sxk_...` (CI and non-interactive sessions). Keys are created at https://app.superx.so/account?tab=api.
|
|
32
32
|
|
|
33
33
|
**Rule 2: Read PLAYBOOK.md before creating any content.** This repo ships a growth strategy guide (`PLAYBOOK.md`, also inside the installed npm package). It tells you WHAT to post, WHEN, and WHY: the action hierarchy, out-of-network discovery, the engagement loop, and the failure modes that kill reach. The CLI gives you data and actions; the playbook gives you judgment. Do not schedule content without it.
|
|
34
34
|
|
|
35
|
-
**Rule 3: Know the write constraints.** `scheduled:create` without `--at` creates a DRAFT (nothing publishes). With `--at` it schedules for that time. `scheduled:update` changes only the flags you pass, and a new `--at` alone never schedules a draft; add `--status scheduled` to promote. Writes work on
|
|
35
|
+
**Rule 3: Know the write constraints.** `scheduled:create` without `--at` creates a DRAFT (nothing publishes). With `--at` it schedules for that time. `scheduled:update` changes only the flags you pass, and a new `--at` alone never schedules a draft; add `--status scheduled` to promote. Writes work on your main account or any linked account (pass the same `--account` you used to read it); accounts shared with you by other people are read-only, and tags are workspace-wide. Images attach via `media:upload` then `--media` (JPG/PNG/WEBP up to 5MB, GIF up to 15MB; max 4 images or 1 GIF per post); video is not supported. Timestamps MUST be UTC ISO-8601 with an explicit `Z` or offset; naive timestamps are rejected with 400. `articles:publish` posts a long-form article to X IMMEDIATELY and irreversibly; treat it like hitting Publish in public and get human confirmation unless the user already gave it.
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
@@ -103,7 +103,7 @@ superx me # Key owner, plan tier, key name and scopes
|
|
|
103
103
|
superx accounts # X accounts this key can read; use ids with --account
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
Reads accept `--account <id>` to select a linked account. Omitting it means the main account.
|
|
106
|
+
Reads accept `--account <id>` to select a linked or shared account. Omitting it means the main account.
|
|
107
107
|
|
|
108
108
|
### Posts and analytics
|
|
109
109
|
|
|
@@ -133,7 +133,7 @@ superx inspiration:search "AI tools" --min-likes 500 --min-followers 1000 --max-
|
|
|
133
133
|
- Searches a library of 50M+ real high-performing posts. Use results for structures, hooks, and angles to remix. Never copy them.
|
|
134
134
|
- Flags: `--sort relevant|recent|likes|reposts|impressions|outlier`, `--min-likes/--min-reposts/--min-replies/--min-bookmarks/--min-impressions`, `--min-followers/--max-followers` (author size), `--since/--until`, `--lang` (default en), `--exclude-topics "crypto,politics"`, `--limit` (max 50), `--page` (1-7).
|
|
135
135
|
- `outlier_score` on each result = how far the post outperformed the norm for its author's follower tier. Sorting by `outlier` surfaces content that won on substance, not audience size.
|
|
136
|
-
- Results are
|
|
136
|
+
- Results are relevance-ranked, strongest matches first. Weak and promotional matches are filtered out, so a page may return fewer than `--limit` posts.
|
|
137
137
|
|
|
138
138
|
### Contacts (who engages with you)
|
|
139
139
|
|
|
@@ -157,7 +157,25 @@ superx lists:remove-member <list-id> <member-id> # member-id from lists:memb
|
|
|
157
157
|
- Lists are the saved people-collections from the SuperX app. Use them to track prospects, customers, or people worth engaging.
|
|
158
158
|
- System lists (Followers, Following, Repliers, Reposters) appear in `lists:list` with `is_system: true` but are read-only and their members are NOT available through the API.
|
|
159
159
|
- Adding someone already in a list is harmless: the existing member returns with `"duplicate": true` and nothing changes.
|
|
160
|
-
- Member writes
|
|
160
|
+
- Member writes work on your main or linked accounts (`--account`) and need a key with the write scope; shared accounts are read-only.
|
|
161
|
+
|
|
162
|
+
### Engage (feed posts to reply to)
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
superx engage:feeds # feeds saved in the app, with type and fetch cost
|
|
166
|
+
superx engage:posts <feed-id> --limit 50 # one big page of candidate posts
|
|
167
|
+
superx engage:posts <feed-id> --mode latest --fresh true # newest, skipping the cache
|
|
168
|
+
superx engage:posts <feed-id> --exclude 1234567890,1234567891 # next batch, minus what you have
|
|
169
|
+
superx engage:posts <feed-id> --include-replied true # keep posts already replied to
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- Engage feeds are the keyword and list feeds the user set up in the app's Engage tab. `engage:feeds` gives each feed's `id`, `name`, `type` (`keywords`, `list`, `x_list`), `active`, and `fetch_units`. Feeds are created and edited in the app; the API only reads them.
|
|
173
|
+
- `engage:posts` returns candidate posts (text, author handle/bio/follower counts, engagement metrics, post time) plus `has_more` and the `feed`. Score and shortlist them for the user.
|
|
174
|
+
- READ-ONLY BY DESIGN: there is no reply command. Replies are written and sent by a person in SuperX. Sending replies that read as inauthentic can get an X account suspended and a SuperX account terminated; AI output must be reviewed and meaningfully edited by a person before it is posted. Surface candidates and draft suggestions for the user; never claim a reply was sent.
|
|
175
|
+
- `--limit` (1-50, default 20) applies to KEYWORD feeds. List feeds return one page per fetch (about 10 posts for a member list, 20 to 25 for an imported X list) and `--limit` only trims it. Page a list feed with `--exclude` (at most 100 ids per call; window the list to the most recent ids), not a bigger `--limit`.
|
|
176
|
+
- A 50-post page on a keyword feed costs the same as a 20-post page: ask for `--limit 50` a few times a day and filter locally. Feeds refresh over hours, so polling more often than hourly returns the same posts.
|
|
177
|
+
- Each plan has a daily feed-fetch allowance (Trial 20, Pro 60, Advanced 120, Ultra 300), separate from the read budget; per minute: trial 2, pro 5, advanced 10, ultra 15. A list feed that rotates its members counts as 3 fetches. `engage:feeds` never spends it. Over the cap you get 429 `rate_limited`: the API returns `remaining_day`; the CLI prints the message and the retry delay.
|
|
178
|
+
- Posts a fetch returns count as seen and get demoted in later fetches, in the app as well as here. An unknown feed id returns 404 `feed_not_found`.
|
|
161
179
|
|
|
162
180
|
### Signals (automated lead finding)
|
|
163
181
|
|
|
@@ -167,7 +185,7 @@ superx signals:leads --limit 20 # newest leads across all a
|
|
|
167
185
|
superx signals:leads --agent 3 --deposited false # one agent's leads not yet in its list
|
|
168
186
|
superx signals:leads --since "2026-07-01T00:00:00Z" # leads discovered since July
|
|
169
187
|
|
|
170
|
-
# Create an agent (main account
|
|
188
|
+
# Create an agent (write scope; main or linked account via --account)
|
|
171
189
|
superx signals:create-agent \
|
|
172
190
|
--name "Build in public founders" \
|
|
173
191
|
--icp "Indie founders building SaaS in public, sharing MRR and launches" \
|
|
@@ -244,6 +262,37 @@ superx scheduled:update <post-id> --clear-tags --clear-title --clear-scratchpad
|
|
|
244
262
|
- A new `--at` alone never schedules a draft. Promotion is always explicit via `--status scheduled` (which needs a future time, provided or already set).
|
|
245
263
|
- CAUTION: replacement text is a FULL replace, media included. `--text` without `--media` REMOVES any images the post carried; re-list the current `object_key`s (visible in `scheduled:list`) to keep them. Title, scratchpad, tag, and time edits never touch media.
|
|
246
264
|
|
|
265
|
+
### Advanced settings (auto retweet, auto delete, auto plug, super followers)
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Explicit values on create
|
|
269
|
+
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" \
|
|
270
|
+
--auto-retweet 6 --auto-retweet-remove 4
|
|
271
|
+
|
|
272
|
+
# Auto plug: reply with a template once the post hits a likes threshold
|
|
273
|
+
superx plug-templates:list # id, text, has_media
|
|
274
|
+
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" \
|
|
275
|
+
--auto-plug <template-id> --auto-plug-threshold 50
|
|
276
|
+
|
|
277
|
+
# Auto delete underperformers (delete after 8h if under 500 views)
|
|
278
|
+
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" \
|
|
279
|
+
--auto-delete 8 --auto-delete-threshold 500
|
|
280
|
+
|
|
281
|
+
# Turn the user's defaults OFF for one post
|
|
282
|
+
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" \
|
|
283
|
+
--no-auto-retweet --no-auto-plug
|
|
284
|
+
|
|
285
|
+
# Edit or remove on an existing post (no inheritance on update)
|
|
286
|
+
superx scheduled:update <post-id> --auto-retweet 2
|
|
287
|
+
superx scheduled:update <post-id> --no-auto-delete
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- On `scheduled:create`, flags you OMIT inherit the user's Default Post Settings from the SuperX app; that is the expected behavior, not a bug. Exactly five settings inherit (auto retweet, auto delete, auto plug, auto DM, Super Followers only); other composer defaults like Bluesky cross-posting never apply to API posts. Use the `--no-*` forms to turn a default off for one post.
|
|
291
|
+
- On `scheduled:update` there is no inheritance: passed flags override, omitted flags keep the post's current settings, `--no-*` removes them.
|
|
292
|
+
- Hours are 1-12. `--auto-plug` needs `--auto-plug-threshold` (likes); template ids come from `plug-templates:list`, unknown ids fail with `unknown_plug_template`. `--super-followers` / `--no-super-followers` toggle Super Followers only.
|
|
293
|
+
- Auto DM has no flag: it always follows the user's app defaults. If a plan limit strips it at create time, the response carries `"auto_dm_skipped": true`; relay that to the user instead of ignoring it.
|
|
294
|
+
- `scheduled:list` shows the applied settings per post (`auto_retweet`, `auto_delete`, `auto_plug`, `auto_dm`, `super_followers_only`), so you can verify what a post will actually do.
|
|
295
|
+
|
|
247
296
|
### Tags
|
|
248
297
|
|
|
249
298
|
```bash
|
|
@@ -255,6 +304,56 @@ superx tags:delete <tag-id> # also removes it from every po
|
|
|
255
304
|
|
|
256
305
|
Tag names are unique per workspace (409 `duplicate_name`) and capped at 40 characters. Assign tags with `scheduled:create --tag` or `scheduled:update --tag`.
|
|
257
306
|
|
|
307
|
+
### Context settings (AI writing background)
|
|
308
|
+
|
|
309
|
+
The Context settings are the background SuperX's AI uses when writing for the account: who the user is, what they post about, hard rules, whose style they admire, and what products they sell. Editing them changes every AI writing surface in the app.
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
superx context:get # The whole context document
|
|
313
|
+
superx context:get | jq '.data.rules' # One section
|
|
314
|
+
|
|
315
|
+
# Only the flags you pass change; "" clears a string; lists fully replace
|
|
316
|
+
superx context:set --rules "Never use hashtags. Keep posts under 200 chars."
|
|
317
|
+
superx context:set --profile-description "Indie hacker building SuperX" --profile-description-enabled
|
|
318
|
+
superx context:set --interests "indie hacking,SaaS,AI agents" # replaces the list
|
|
319
|
+
superx context:set --favorite-creators "levelsio,marc_louvion" # max 3, replaces the list
|
|
320
|
+
superx context:set --reply-rules "Be helpful, never salesy" --no-reply-author-name
|
|
321
|
+
superx context:set --style-audience "Bootstrapped SaaS founders" # outranks the generated guide
|
|
322
|
+
superx context:set --style-audience "" # revert to the generated guide
|
|
323
|
+
|
|
324
|
+
# Products (max 5): mentioned naturally in generated content
|
|
325
|
+
superx context:products
|
|
326
|
+
superx context:products:set --url "https://superx.so" --name "SuperX" --description "X growth platform"
|
|
327
|
+
superx context:products:set --id 3 --updates "Shipped the public API"
|
|
328
|
+
superx context:products:delete <product-id>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- What each setting affects: `--profile-description` grounds the AI's voice and personalizes the daily content mix and search; `--rules` are mandatory instructions on EVERY AI surface; `--reply-rules` and `--reply-author-name` steer generated replies; `--favorite-creators` (X usernames, max 3) inspire the writing style; `--interests` are the highest-priority topics for content suggestions; `--style-audience`/`--style-vocabulary` outrank the app's generated style guide until cleared.
|
|
332
|
+
- `context:get` also returns the read-only generated style guide (`style_guide.generated`) so you can see what a cleared override falls back to.
|
|
333
|
+
- Caps: profile description 500, rules 500, reply rules 500, style audience 600, style vocabulary 1000 characters; 30 interests of 50 characters each; 3 favorite creators; 5 products.
|
|
334
|
+
- `context:products:set --url` creates the product when it does not exist. Removing a product is reversible: re-adding the same url restores its scraped details.
|
|
335
|
+
- Writes need a key with the write scope. Unlike scheduling, context writes work on ANY linked or shared account via `--account` (they are per-account settings). On a share with Editor permission they return 403 `editor_restricted`: only the account owner can change these. These settings shape ALL future AI output for the account; confirm with the user before changing rules or the profile description.
|
|
336
|
+
|
|
337
|
+
### Queue settings (posting schedule)
|
|
338
|
+
|
|
339
|
+
The posting schedule is the set of predefined time slots the queue fills, plus the timezone they run in.
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
superx queue:get # slots, timezone, is_default flags
|
|
343
|
+
|
|
344
|
+
# Slots are JSON so weekday sets stay unambiguous; 0 = Sunday
|
|
345
|
+
superx queue:set --slots-json '[{"time":"09:00","days":[1,2,3,4,5]},{"time":"17:30","days":[1,3,5]}]'
|
|
346
|
+
superx queue:set --timezone "Europe/London" # never moves queued posts
|
|
347
|
+
superx queue:set --slots-json '[]' # clear every predefined slot
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
- `--slots-json` is a FULL REPLACE: max 50 entries, one per unique time, each with at least one weekday. Send the complete set the user should end up with.
|
|
351
|
+
- Changing the slots also re-flows the queue the way the app does: a queued post sitting exactly on an old slot moves to the matching new slot (Nth old occurrence to Nth new occurrence), so gaps are preserved and hand-picked custom times stay put. Read `reflow.moved` in the response to see how many posts moved.
|
|
352
|
+
- `reflow.bailed: true` means the settings were saved but the queue was deliberately left alone (a post had nowhere to land, or the move set was too large). Rerunning the same command is safe.
|
|
353
|
+
- A timezone-only change never moves posts. Changing the timezone and the slots in one call usually moves nothing, because the existing posts were placed under the old timezone; to re-flow them, change the timezone first, then send the slots in a second call.
|
|
354
|
+
- `slots_are_default` / `timezone_is_default` mark values the account has never set; SuperX is using its own default.
|
|
355
|
+
- Writes need a key with the write scope and work on any linked or shared account via `--account`, Editor-permission shares included (running the queue is exactly what a delegate is there for).
|
|
356
|
+
|
|
258
357
|
### Articles (long-form X posts)
|
|
259
358
|
|
|
260
359
|
Article bodies are markdown in BOTH directions: headings (h1-h3), bullet and numbered lists (one nesting level), blockquotes, bold/italic/strikethrough, links, images by URL, and bare X post URLs alone on a line as embeds. Code blocks and `---` rules degrade to plain text; the response lists degradations in `warnings`.
|
|
@@ -365,7 +464,7 @@ superx scheduled:list --status scheduled
|
|
|
365
464
|
1. **Naive timestamps are rejected (400)**. Always include `Z` or an offset: `2026-08-01T15:00:00Z`, not `2026-08-01T15:00:00`.
|
|
366
465
|
2. **Schedule window**: `--at` must be at least 60 seconds in the future and within 18 months.
|
|
367
466
|
3. **Read-only keys cannot write**: `scheduled:create`/`scheduled:delete` with a read-only key returns 403 `insufficient_scope`. Check `superx me` for the key's scopes.
|
|
368
|
-
4. **Writes are main-account-only**: passing a linked account to `scheduled:create` returns 403 `writes_main_account_only`. Reads accept any
|
|
467
|
+
4. **Writes are main-account-only**: passing a linked or shared account to `scheduled:create` returns 403 `writes_main_account_only`. Reads accept any account `superx accounts` lists. The exceptions are `context:*` and `queue:set`, which are per-account settings.
|
|
369
468
|
5. **Images need an upload first**: `--media` takes `object_key`s from `media:upload`, never file paths or URLs. Unknown keys return 400 `invalid_media`; a presign whose bytes were never PUT returns 400 `media_not_uploaded`. Video is not supported.
|
|
370
469
|
6. **Size caps**: max 25 thread parts, 25,000 characters total.
|
|
371
470
|
7. **Rate limited (429)**: `rate_limited` on stderr with a retry delay. Back off; do not hammer.
|
|
@@ -387,6 +486,10 @@ superx scheduled:list --status scheduled
|
|
|
387
486
|
23. **Signal agents find leads asynchronously**: `signals:create-agent` returns the created agent, not leads. Leads land over the following minutes and days; read them with `signals:leads`.
|
|
388
487
|
24. **Plan caps on agents return 403 `cap_reached`**: the plan allows only so many agents (and keyword signals per agent). Pause/delete an existing agent or ask the account owner to upgrade.
|
|
389
488
|
25. **Agent creation is composite**: with an auto-created list, a mid-failure can leave an empty `Leads: ...` contact list behind (visible in `lists:list`, deletable in the app). The agent itself is never left without signals.
|
|
489
|
+
26. **`context:set` list flags REPLACE the stored list**: `--interests` and `--favorite-creators` overwrite what is there; include every value the user should keep. `""` on a string flag clears it (style-guide overrides then revert to the generated guide). These settings steer all future AI output; confirm with the user before changing them.
|
|
490
|
+
27. **`queue:set --slots-json` REPLACES the whole schedule** and re-flows queued posts onto the new slots. Read the current slots with `queue:get` first and send the full set. `'[]'` clears every slot and leaves the queue all-custom. `reflow.bailed: true` means the settings saved but no post moved.
|
|
491
|
+
28. **`editor_restricted` (403)**: the account is shared with the key owner with Editor permission. Editors can change queue settings but not context settings. Only the account owner can.
|
|
492
|
+
29. **`engage:posts --limit` is keyword-feeds only**: list feeds return one page of about 10 to 25 posts per fetch, so page them with `--exclude` (the ids you already have, at most 100 per call), not a bigger `--limit`. Each plan also has a daily feed-fetch allowance (separate from reads) and a list feed that rotates its members counts as 3 fetches, so fetch big pages a few times a day rather than polling. Posts a fetch returns count as seen and are demoted in later fetches, in the app as well as here.
|
|
390
493
|
|
|
391
494
|
---
|
|
392
495
|
|
|
@@ -417,18 +520,20 @@ superx lists:list
|
|
|
417
520
|
superx lists:members <list-id> --q "founder"
|
|
418
521
|
superx signals:agents
|
|
419
522
|
superx signals:leads --agent 3 --deposited false
|
|
523
|
+
superx engage:feeds
|
|
524
|
+
superx engage:posts <feed-id> --limit 50
|
|
420
525
|
|
|
421
|
-
# Contact list writes (main account
|
|
526
|
+
# Contact list writes (main or linked account)
|
|
422
527
|
superx lists:add-member <list-id> --handle levelsio
|
|
423
528
|
superx lists:remove-member <list-id> <member-id>
|
|
424
529
|
|
|
425
|
-
# Signal agent writes (main account
|
|
530
|
+
# Signal agent writes (main or linked account)
|
|
426
531
|
superx signals:create-agent --name "..." --icp "..." --keyword "..." # Lead finder
|
|
427
532
|
superx signals:pause-agent <id>
|
|
428
533
|
superx signals:resume-agent <id>
|
|
429
534
|
superx signals:delete-agent <id>
|
|
430
535
|
|
|
431
|
-
# Writes (main account
|
|
536
|
+
# Writes (main or linked account via --account)
|
|
432
537
|
superx scheduled:create --text "Post" # Draft
|
|
433
538
|
superx scheduled:create --text "Post" --at "2026-08-01T15:00:00Z" # Scheduled
|
|
434
539
|
superx scheduled:create --part "1/" --part "2/" --at "..." # Thread
|
|
@@ -459,6 +564,19 @@ superx articles:publish <id>
|
|
|
459
564
|
superx articles:cover <id> --style "minimal"
|
|
460
565
|
superx articles:delete <id>
|
|
461
566
|
|
|
567
|
+
# Context settings (AI writing background)
|
|
568
|
+
superx context:get
|
|
569
|
+
superx context:set --rules "Never use hashtags."
|
|
570
|
+
superx context:set --interests "indie hacking,SaaS" # replaces the list
|
|
571
|
+
superx context:products
|
|
572
|
+
superx context:products:set --url "https://superx.so" --name "SuperX"
|
|
573
|
+
superx context:products:delete <id>
|
|
574
|
+
|
|
575
|
+
# Queue settings (posting schedule; 0 = Sunday)
|
|
576
|
+
superx queue:get
|
|
577
|
+
superx queue:set --slots-json '[{"time":"09:00","days":[1,3,5]}]' # replaces the slots
|
|
578
|
+
superx queue:set --timezone "Europe/London" # never moves posts
|
|
579
|
+
|
|
462
580
|
# Docs and help
|
|
463
581
|
superx docs # API quickstart (markdown)
|
|
464
582
|
superx --help # All commands
|
package/dist/index.js
CHANGED
|
@@ -184,6 +184,13 @@ var SuperXAPI = class {
|
|
|
184
184
|
async deleteSignalAgent(id) {
|
|
185
185
|
return (await this.request(`/signals/agents/${id}`, { method: "DELETE" })).json;
|
|
186
186
|
}
|
|
187
|
+
// --- Engage ---
|
|
188
|
+
async listEngageFeeds(query = {}) {
|
|
189
|
+
return (await this.request("/engage/feeds", { query })).json;
|
|
190
|
+
}
|
|
191
|
+
async getEngageFeedPosts(feedId, query = {}) {
|
|
192
|
+
return (await this.request(`/engage/feeds/${encodeURIComponent(feedId)}/posts`, { query })).json;
|
|
193
|
+
}
|
|
187
194
|
// --- Scheduled posts ---
|
|
188
195
|
async listScheduled(query = {}) {
|
|
189
196
|
return (await this.request("/scheduled-posts", { query })).json;
|
|
@@ -200,6 +207,30 @@ var SuperXAPI = class {
|
|
|
200
207
|
async deleteScheduled(id) {
|
|
201
208
|
return (await this.request(`/scheduled-posts/${encodeURIComponent(id)}`, { method: "DELETE" })).json;
|
|
202
209
|
}
|
|
210
|
+
// --- Plug templates ---
|
|
211
|
+
async listPlugTemplates(query = {}) {
|
|
212
|
+
return (await this.request("/plug-templates", { query })).json;
|
|
213
|
+
}
|
|
214
|
+
// --- Context settings ---
|
|
215
|
+
async getContext(query = {}) {
|
|
216
|
+
return (await this.request("/context", { query })).json;
|
|
217
|
+
}
|
|
218
|
+
async updateContext(body) {
|
|
219
|
+
return (await this.request("/context", { method: "PATCH", body })).json;
|
|
220
|
+
}
|
|
221
|
+
async updateContextProduct(idOrUrl, body) {
|
|
222
|
+
return (await this.request(`/context/products/${encodeURIComponent(idOrUrl)}`, { method: "PATCH", body })).json;
|
|
223
|
+
}
|
|
224
|
+
async deleteContextProduct(id, query = {}) {
|
|
225
|
+
return (await this.request(`/context/products/${encodeURIComponent(id)}`, { method: "DELETE", query })).json;
|
|
226
|
+
}
|
|
227
|
+
// --- Queue settings ---
|
|
228
|
+
async getQueueSettings(query = {}) {
|
|
229
|
+
return (await this.request("/queue-settings", { query })).json;
|
|
230
|
+
}
|
|
231
|
+
async updateQueueSettings(body) {
|
|
232
|
+
return (await this.request("/queue-settings", { method: "PATCH", body })).json;
|
|
233
|
+
}
|
|
203
234
|
// --- Media ---
|
|
204
235
|
async createMediaUpload(body) {
|
|
205
236
|
return (await this.request("/media", { method: "POST", body })).json;
|
|
@@ -324,7 +355,7 @@ function getConfig() {
|
|
|
324
355
|
process.stderr.write("Not authenticated. Either:\n");
|
|
325
356
|
process.stderr.write(" 1. Run: superx login\n");
|
|
326
357
|
process.stderr.write(" 2. Or set: export SUPERX_API_KEY=sxk_...\n");
|
|
327
|
-
process.stderr.write("Create an API key at https://app.superx.so/account?tab=
|
|
358
|
+
process.stderr.write("Create an API key at https://app.superx.so/account?tab=api\n");
|
|
328
359
|
process.exit(1);
|
|
329
360
|
}
|
|
330
361
|
|
|
@@ -347,7 +378,7 @@ async function login(argv) {
|
|
|
347
378
|
let key = (argv.key || "").trim();
|
|
348
379
|
if (!key) {
|
|
349
380
|
note("Create or copy an API key at:");
|
|
350
|
-
note(" https://app.superx.so/account?tab=
|
|
381
|
+
note(" https://app.superx.so/account?tab=api");
|
|
351
382
|
note("");
|
|
352
383
|
key = await promptForKey();
|
|
353
384
|
}
|
|
@@ -576,6 +607,26 @@ async function signalsDeleteAgent(argv) {
|
|
|
576
607
|
printJson(await api.deleteSignalAgent(argv.id));
|
|
577
608
|
}
|
|
578
609
|
|
|
610
|
+
// src/commands/engage.ts
|
|
611
|
+
async function engageFeeds(argv) {
|
|
612
|
+
const api = new SuperXAPI(getConfig());
|
|
613
|
+
printJson(await api.listEngageFeeds({ account_id: argv.account }));
|
|
614
|
+
}
|
|
615
|
+
async function engagePosts(argv) {
|
|
616
|
+
const api = new SuperXAPI(getConfig());
|
|
617
|
+
printJson(
|
|
618
|
+
await api.getEngageFeedPosts(argv.feedId, {
|
|
619
|
+
account_id: argv.account,
|
|
620
|
+
limit: argv.limit,
|
|
621
|
+
mode: argv.mode,
|
|
622
|
+
// yargs boolean: pass through only when the flag was given.
|
|
623
|
+
fresh: argv.fresh === void 0 ? void 0 : String(argv.fresh),
|
|
624
|
+
include_replied: argv.includeReplied === void 0 ? void 0 : String(argv.includeReplied),
|
|
625
|
+
exclude_post_ids: argv.exclude
|
|
626
|
+
})
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
|
|
579
630
|
// src/commands/scheduled.ts
|
|
580
631
|
async function scheduledList(argv) {
|
|
581
632
|
const api = new SuperXAPI(getConfig());
|
|
@@ -628,6 +679,80 @@ function parsePartsJson(raw) {
|
|
|
628
679
|
}
|
|
629
680
|
return parsed;
|
|
630
681
|
}
|
|
682
|
+
function numericFlag(name, value) {
|
|
683
|
+
if (value === void 0 || value === false) return value;
|
|
684
|
+
if (value === true) {
|
|
685
|
+
note(`--${name} needs a numeric value.`);
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
689
|
+
if (!Number.isFinite(n)) {
|
|
690
|
+
note(`--${name} must be a number.`);
|
|
691
|
+
process.exit(1);
|
|
692
|
+
}
|
|
693
|
+
return n;
|
|
694
|
+
}
|
|
695
|
+
function applyAdvancedFlags(argv, body) {
|
|
696
|
+
const retweet = numericFlag("auto-retweet", argv["auto-retweet"]);
|
|
697
|
+
const retweetRemove = numericFlag("auto-retweet-remove", argv["auto-retweet-remove"]);
|
|
698
|
+
if (retweet === false) {
|
|
699
|
+
if (typeof retweetRemove === "number") {
|
|
700
|
+
note("--auto-retweet-remove cannot be combined with --no-auto-retweet.");
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
body.auto_retweet = null;
|
|
704
|
+
} else if (typeof retweet === "number") {
|
|
705
|
+
body.auto_retweet = {
|
|
706
|
+
after_hours: retweet,
|
|
707
|
+
...typeof retweetRemove === "number" ? { remove_after_hours: retweetRemove } : {}
|
|
708
|
+
};
|
|
709
|
+
} else if (typeof retweetRemove === "number") {
|
|
710
|
+
note("--auto-retweet-remove requires --auto-retweet <hours>.");
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
const del = numericFlag("auto-delete", argv["auto-delete"]);
|
|
714
|
+
const delThreshold = numericFlag("auto-delete-threshold", argv["auto-delete-threshold"]);
|
|
715
|
+
if (del === false) {
|
|
716
|
+
if (typeof delThreshold === "number") {
|
|
717
|
+
note("--auto-delete-threshold cannot be combined with --no-auto-delete.");
|
|
718
|
+
process.exit(1);
|
|
719
|
+
}
|
|
720
|
+
body.auto_delete = null;
|
|
721
|
+
} else if (typeof del === "number") {
|
|
722
|
+
body.auto_delete = {
|
|
723
|
+
after_hours: del,
|
|
724
|
+
...typeof delThreshold === "number" ? { threshold: delThreshold } : {}
|
|
725
|
+
};
|
|
726
|
+
} else if (typeof delThreshold === "number") {
|
|
727
|
+
note("--auto-delete-threshold requires --auto-delete <hours>.");
|
|
728
|
+
process.exit(1);
|
|
729
|
+
}
|
|
730
|
+
const plug = argv["auto-plug"];
|
|
731
|
+
const plugThreshold = numericFlag("auto-plug-threshold", argv["auto-plug-threshold"]);
|
|
732
|
+
if (plug === false) {
|
|
733
|
+
if (typeof plugThreshold === "number") {
|
|
734
|
+
note("--auto-plug-threshold cannot be combined with --no-auto-plug.");
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
body.auto_plug = null;
|
|
738
|
+
} else if (typeof plug === "string" && plug.length > 0) {
|
|
739
|
+
if (typeof plugThreshold !== "number") {
|
|
740
|
+
note("--auto-plug requires --auto-plug-threshold <likes>.");
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
body.auto_plug = { template_id: plug, threshold: plugThreshold };
|
|
744
|
+
} else if (typeof plugThreshold === "number") {
|
|
745
|
+
note("--auto-plug-threshold requires --auto-plug <templateId>.");
|
|
746
|
+
process.exit(1);
|
|
747
|
+
}
|
|
748
|
+
if (typeof argv["super-followers"] === "boolean") {
|
|
749
|
+
body.super_followers_only = argv["super-followers"];
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
async function plugTemplatesList(argv) {
|
|
753
|
+
const api = new SuperXAPI(getConfig());
|
|
754
|
+
printJson(await api.listPlugTemplates({ account_id: argv.account }));
|
|
755
|
+
}
|
|
631
756
|
async function scheduledCreate(argv) {
|
|
632
757
|
const parts = (argv.part || []).filter((p) => typeof p === "string");
|
|
633
758
|
const sourceCount = [argv.text, parts.length > 0 ? "p" : void 0, argv["parts-json"]].filter(
|
|
@@ -663,6 +788,7 @@ async function scheduledCreate(argv) {
|
|
|
663
788
|
if (argv.scratchpad !== void 0) body.scratchpad = argv.scratchpad;
|
|
664
789
|
const tags = (argv.tag || []).filter((t) => typeof t === "string" && t.length > 0);
|
|
665
790
|
if (tags.length > 0) body.tags = tags;
|
|
791
|
+
applyAdvancedFlags(argv, body);
|
|
666
792
|
if (argv.account) body.account_id = argv.account;
|
|
667
793
|
const api = new SuperXAPI(getConfig());
|
|
668
794
|
const { json, replayed } = await api.createScheduled(body, argv["idempotency-key"]);
|
|
@@ -720,6 +846,7 @@ async function scheduledUpdate(argv) {
|
|
|
720
846
|
if (argv["clear-scratchpad"]) body.scratchpad = null;
|
|
721
847
|
if (tags.length > 0) body.tags = tags;
|
|
722
848
|
if (argv["clear-tags"]) body.tags = [];
|
|
849
|
+
applyAdvancedFlags(argv, body);
|
|
723
850
|
if (argv.account) body.account_id = argv.account;
|
|
724
851
|
if (Object.keys(body).filter((k) => k !== "account_id").length === 0) {
|
|
725
852
|
note("Provide at least one field to update. Run: superx scheduled:update --help");
|
|
@@ -941,6 +1068,140 @@ async function articlesCover(argv) {
|
|
|
941
1068
|
printJson(await api.generateArticleCover(argv.id, body));
|
|
942
1069
|
}
|
|
943
1070
|
|
|
1071
|
+
// src/commands/context.ts
|
|
1072
|
+
async function contextGet(argv) {
|
|
1073
|
+
const api = new SuperXAPI(getConfig());
|
|
1074
|
+
printJson(await api.getContext({ account_id: argv.account }));
|
|
1075
|
+
}
|
|
1076
|
+
function commaList(value) {
|
|
1077
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1078
|
+
}
|
|
1079
|
+
function stringOrClear(value) {
|
|
1080
|
+
return value === "" ? null : value;
|
|
1081
|
+
}
|
|
1082
|
+
async function contextSet(argv) {
|
|
1083
|
+
const body = {};
|
|
1084
|
+
const profileDescription = {};
|
|
1085
|
+
if (argv["profile-description"] !== void 0) {
|
|
1086
|
+
profileDescription.text = stringOrClear(argv["profile-description"]);
|
|
1087
|
+
}
|
|
1088
|
+
if (typeof argv["profile-description-enabled"] === "boolean") {
|
|
1089
|
+
profileDescription.enabled = argv["profile-description-enabled"];
|
|
1090
|
+
}
|
|
1091
|
+
if (Object.keys(profileDescription).length > 0) {
|
|
1092
|
+
body.profile_description = profileDescription;
|
|
1093
|
+
}
|
|
1094
|
+
if (argv.interests !== void 0) {
|
|
1095
|
+
body.interests = commaList(argv.interests);
|
|
1096
|
+
}
|
|
1097
|
+
if (argv.rules !== void 0) {
|
|
1098
|
+
body.rules = stringOrClear(argv.rules);
|
|
1099
|
+
}
|
|
1100
|
+
const reply = {};
|
|
1101
|
+
if (argv["reply-rules"] !== void 0) {
|
|
1102
|
+
reply.custom_instructions = stringOrClear(argv["reply-rules"]);
|
|
1103
|
+
}
|
|
1104
|
+
if (typeof argv["reply-author-name"] === "boolean") {
|
|
1105
|
+
reply.include_author_name = argv["reply-author-name"];
|
|
1106
|
+
}
|
|
1107
|
+
if (Object.keys(reply).length > 0) {
|
|
1108
|
+
body.reply = reply;
|
|
1109
|
+
}
|
|
1110
|
+
const voice = {};
|
|
1111
|
+
if (argv["favorite-creators"] !== void 0) {
|
|
1112
|
+
voice.favorite_creators = commaList(argv["favorite-creators"]);
|
|
1113
|
+
}
|
|
1114
|
+
if (typeof argv["own-posts-as-examples"] === "boolean") {
|
|
1115
|
+
voice.use_own_posts_as_examples = argv["own-posts-as-examples"];
|
|
1116
|
+
}
|
|
1117
|
+
if (Object.keys(voice).length > 0) {
|
|
1118
|
+
body.voice = voice;
|
|
1119
|
+
}
|
|
1120
|
+
const styleGuide = {};
|
|
1121
|
+
if (argv["style-audience"] !== void 0) {
|
|
1122
|
+
styleGuide.audience_override = stringOrClear(argv["style-audience"]);
|
|
1123
|
+
}
|
|
1124
|
+
if (argv["style-vocabulary"] !== void 0) {
|
|
1125
|
+
styleGuide.vocabulary_override = stringOrClear(argv["style-vocabulary"]);
|
|
1126
|
+
}
|
|
1127
|
+
if (Object.keys(styleGuide).length > 0) {
|
|
1128
|
+
body.style_guide = styleGuide;
|
|
1129
|
+
}
|
|
1130
|
+
if (Object.keys(body).length === 0) {
|
|
1131
|
+
note("Provide at least one setting flag. Run: superx context:set --help");
|
|
1132
|
+
process.exit(1);
|
|
1133
|
+
}
|
|
1134
|
+
if (argv.account) body.account_id = argv.account;
|
|
1135
|
+
const api = new SuperXAPI(getConfig());
|
|
1136
|
+
printJson(await api.updateContext(body));
|
|
1137
|
+
}
|
|
1138
|
+
async function contextProducts(argv) {
|
|
1139
|
+
const api = new SuperXAPI(getConfig());
|
|
1140
|
+
const json = await api.getContext({ account_id: argv.account });
|
|
1141
|
+
printJson({ data: json?.data?.products ?? [] });
|
|
1142
|
+
}
|
|
1143
|
+
async function contextProductsSet(argv) {
|
|
1144
|
+
if (!argv.id && !argv.url) {
|
|
1145
|
+
note("Provide --id (from context:products) to edit, or --url to add or edit by url.");
|
|
1146
|
+
process.exit(1);
|
|
1147
|
+
}
|
|
1148
|
+
if (argv.id && argv.url) {
|
|
1149
|
+
note("Use either --id or --url, not both.");
|
|
1150
|
+
process.exit(1);
|
|
1151
|
+
}
|
|
1152
|
+
const body = {};
|
|
1153
|
+
if (argv.name !== void 0) body.name = stringOrClear(argv.name);
|
|
1154
|
+
if (argv.description !== void 0) body.description = stringOrClear(argv.description);
|
|
1155
|
+
if (argv.positioning !== void 0) body.positioning = stringOrClear(argv.positioning);
|
|
1156
|
+
if (argv.features !== void 0) body.features = stringOrClear(argv.features);
|
|
1157
|
+
if (argv.updates !== void 0) body.updates = stringOrClear(argv.updates);
|
|
1158
|
+
if (argv.account) body.account_id = argv.account;
|
|
1159
|
+
const api = new SuperXAPI(getConfig());
|
|
1160
|
+
printJson(await api.updateContextProduct(argv.id || argv.url, body));
|
|
1161
|
+
}
|
|
1162
|
+
async function contextProductsDelete(argv) {
|
|
1163
|
+
const id = String(argv.id || "").trim();
|
|
1164
|
+
if (!id) {
|
|
1165
|
+
note("Provide the product id. Run: superx context:products:delete --help");
|
|
1166
|
+
process.exit(1);
|
|
1167
|
+
}
|
|
1168
|
+
const api = new SuperXAPI(getConfig());
|
|
1169
|
+
printJson(await api.deleteContextProduct(id, { account_id: argv.account }));
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// src/commands/queue.ts
|
|
1173
|
+
async function queueGet(argv) {
|
|
1174
|
+
const api = new SuperXAPI(getConfig());
|
|
1175
|
+
printJson(await api.getQueueSettings({ account_id: argv.account }));
|
|
1176
|
+
}
|
|
1177
|
+
async function queueSet(argv) {
|
|
1178
|
+
const body = {};
|
|
1179
|
+
if (argv["slots-json"] !== void 0) {
|
|
1180
|
+
let parsed;
|
|
1181
|
+
try {
|
|
1182
|
+
parsed = JSON.parse(argv["slots-json"]);
|
|
1183
|
+
} catch {
|
|
1184
|
+
note(`--slots-json must be valid JSON, e.g. '[{"time":"09:00","days":[1,3,5]}]'`);
|
|
1185
|
+
process.exit(1);
|
|
1186
|
+
}
|
|
1187
|
+
if (!Array.isArray(parsed)) {
|
|
1188
|
+
note(`--slots-json must be a JSON array, e.g. '[{"time":"09:00","days":[1,3,5]}]' (use '[]' to clear).`);
|
|
1189
|
+
process.exit(1);
|
|
1190
|
+
}
|
|
1191
|
+
body.slots = parsed;
|
|
1192
|
+
}
|
|
1193
|
+
if (argv.timezone !== void 0) {
|
|
1194
|
+
body.timezone = argv.timezone;
|
|
1195
|
+
}
|
|
1196
|
+
if (Object.keys(body).length === 0) {
|
|
1197
|
+
note("Provide --slots-json and/or --timezone. Run: superx queue:set --help");
|
|
1198
|
+
process.exit(1);
|
|
1199
|
+
}
|
|
1200
|
+
if (argv.account) body.account_id = argv.account;
|
|
1201
|
+
const api = new SuperXAPI(getConfig());
|
|
1202
|
+
printJson(await api.updateQueueSettings(body));
|
|
1203
|
+
}
|
|
1204
|
+
|
|
944
1205
|
// src/commands/docs.ts
|
|
945
1206
|
async function docs() {
|
|
946
1207
|
const apiUrl = resolveApiUrl(loadCredentials()?.apiUrl || DEFAULT_API_URL);
|
|
@@ -972,6 +1233,23 @@ var accountOption = (y) => y.option("account", {
|
|
|
972
1233
|
describe: "Account id (from `superx accounts`); defaults to your main account",
|
|
973
1234
|
type: "string"
|
|
974
1235
|
});
|
|
1236
|
+
var advancedSettingsOptions = (y) => y.option("auto-retweet", {
|
|
1237
|
+
describe: "Auto retweet the post after this many hours (1-12); --no-auto-retweet turns it off"
|
|
1238
|
+
}).option("auto-retweet-remove", {
|
|
1239
|
+
describe: "Remove the auto retweet after this many hours (1-12); needs --auto-retweet"
|
|
1240
|
+
}).option("auto-delete", {
|
|
1241
|
+
describe: "Auto delete the post after this many hours (1-12) if it underperforms; --no-auto-delete turns it off"
|
|
1242
|
+
}).option("auto-delete-threshold", {
|
|
1243
|
+
describe: "Views threshold for --auto-delete: delete only below this many views (default 1000)"
|
|
1244
|
+
}).option("auto-plug", {
|
|
1245
|
+
describe: "Plug template id (from plug-templates:list) to auto-reply with; --no-auto-plug turns it off",
|
|
1246
|
+
type: "string"
|
|
1247
|
+
}).option("auto-plug-threshold", {
|
|
1248
|
+
describe: "Likes threshold for --auto-plug: the reply posts once the post hits this many likes"
|
|
1249
|
+
}).option("super-followers", {
|
|
1250
|
+
describe: "Post to Super Followers only (--no-super-followers turns it off)",
|
|
1251
|
+
type: "boolean"
|
|
1252
|
+
});
|
|
975
1253
|
(0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).scriptName("superx").usage("$0 <command> [options]").command(
|
|
976
1254
|
"login",
|
|
977
1255
|
"Authenticate with a SuperX API key (guided paste or --key)",
|
|
@@ -1121,6 +1399,32 @@ var accountOption = (y) => y.option("account", {
|
|
|
1121
1399
|
"Delete a signal agent (its saved leads and contact list stay untouched)",
|
|
1122
1400
|
(y) => y.positional("id", { describe: "Agent id (from signals:agents)", type: "number" }),
|
|
1123
1401
|
run(signalsDeleteAgent)
|
|
1402
|
+
).command(
|
|
1403
|
+
"engage:feeds",
|
|
1404
|
+
"List the Engage feeds set up in the app",
|
|
1405
|
+
(y) => accountOption(y).example("$0 engage:feeds", "Feed ids, types, and what one fetch costs").example("$0 engage:feeds --account <account-id>", "Feeds saved on a linked account"),
|
|
1406
|
+
run(engageFeeds)
|
|
1407
|
+
).command(
|
|
1408
|
+
"engage:posts <feedId>",
|
|
1409
|
+
"Fetch the posts an Engage feed surfaces (for review; replies are sent by a person in the app)",
|
|
1410
|
+
(y) => accountOption(y).positional("feedId", { describe: "Feed id (from engage:feeds)", type: "string" }).option("limit", {
|
|
1411
|
+
describe: "Posts to return, 1-50 (default 20). Applies to keyword feeds; list feeds return one page (about 10 to 25 posts) and --limit only trims it",
|
|
1412
|
+
type: "number"
|
|
1413
|
+
}).option("mode", {
|
|
1414
|
+
describe: "top = highest-signal posts first (default); latest = newest first",
|
|
1415
|
+
type: "string",
|
|
1416
|
+
choices: ["top", "latest"]
|
|
1417
|
+
}).option("fresh", {
|
|
1418
|
+
describe: "true = skip the cache and fetch new posts; false = allow cached posts (default)",
|
|
1419
|
+
type: "boolean"
|
|
1420
|
+
}).option("include-replied", {
|
|
1421
|
+
describe: "true = keep posts already replied to, skipped, or blocked and flag them with `replied`; false = leave them out (default)",
|
|
1422
|
+
type: "boolean"
|
|
1423
|
+
}).option("exclude", {
|
|
1424
|
+
describe: "Comma list of post ids to leave out (max 100); this is how you page: pass the ids you already have to get the next batch",
|
|
1425
|
+
type: "string"
|
|
1426
|
+
}).example("$0 engage:posts <feed-id> --limit 50", "One big page from a keyword feed").example("$0 engage:posts <feed-id> --mode latest --fresh true", "Newest posts, skipping the cache").example("$0 engage:posts <feed-id> --exclude 1234567890,1234567891", "Next batch, minus the posts you have"),
|
|
1427
|
+
run(engagePosts)
|
|
1124
1428
|
).command(
|
|
1125
1429
|
"scheduled:list",
|
|
1126
1430
|
"List drafts and the scheduled queue",
|
|
@@ -1140,7 +1444,7 @@ var accountOption = (y) => y.option("account", {
|
|
|
1140
1444
|
).command(
|
|
1141
1445
|
"scheduled:create",
|
|
1142
1446
|
"Create a draft (no --at) or scheduled post; repeat --part for a thread",
|
|
1143
|
-
(y) => accountOption(y).option("text", { describe: "Text for a single post", type: "string" }).option("part", {
|
|
1447
|
+
(y) => advancedSettingsOptions(accountOption(y)).option("text", { describe: "Text for a single post", type: "string" }).option("part", {
|
|
1144
1448
|
describe: "Thread part text (repeat the flag, 1-25 parts, in order)",
|
|
1145
1449
|
type: "string",
|
|
1146
1450
|
array: true
|
|
@@ -1163,12 +1467,12 @@ var accountOption = (y) => y.option("account", {
|
|
|
1163
1467
|
}).option("idempotency-key", {
|
|
1164
1468
|
describe: "Idempotency-Key header (max 64 chars); retries with the same key return the original result",
|
|
1165
1469
|
type: "string"
|
|
1166
|
-
}).example('$0 scheduled:create --text "Hello"', "Create a draft").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z"', "Schedule a post").example('$0 scheduled:create --part "1/ Hook" --part "2/ Detail" --part "3/ CTA"', "Draft a 3-part thread").example('$0 scheduled:create --text "Hello" --title "Launch teaser" --tag abc123', "Draft with a title and a tag").example('$0 scheduled:create --text "Chart of the week" --media "<object_key>" --alt-text "Revenue chart"', "Draft with an image"),
|
|
1470
|
+
}).example('$0 scheduled:create --text "Hello"', "Create a draft").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z"', "Schedule a post").example('$0 scheduled:create --part "1/ Hook" --part "2/ Detail" --part "3/ CTA"', "Draft a 3-part thread").example('$0 scheduled:create --text "Hello" --title "Launch teaser" --tag abc123', "Draft with a title and a tag").example('$0 scheduled:create --text "Chart of the week" --media "<object_key>" --alt-text "Revenue chart"', "Draft with an image").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z" --auto-retweet 6 --auto-retweet-remove 4', "Schedule with an auto retweet").example('$0 scheduled:create --text "Hello" --at "2026-08-01T15:00:00Z" --no-auto-retweet --no-auto-plug', "Schedule with your defaults off for this post"),
|
|
1167
1471
|
run(scheduledCreate)
|
|
1168
1472
|
).command(
|
|
1169
1473
|
"scheduled:update <id>",
|
|
1170
1474
|
"Edit a draft or scheduled post; only the flags you pass change",
|
|
1171
|
-
(y) => accountOption(y).positional("id", { describe: "Post id (from scheduled:list or scheduled:create)", type: "string" }).option("text", { describe: "Replacement text for a single post", type: "string" }).option("part", {
|
|
1475
|
+
(y) => advancedSettingsOptions(accountOption(y)).positional("id", { describe: "Post id (from scheduled:list or scheduled:create)", type: "string" }).option("text", { describe: "Replacement text for a single post", type: "string" }).option("part", {
|
|
1172
1476
|
describe: "Replacement thread part text (repeat the flag, 1-25 parts, in order)",
|
|
1173
1477
|
type: "string",
|
|
1174
1478
|
array: true
|
|
@@ -1192,13 +1496,94 @@ var accountOption = (y) => y.option("account", {
|
|
|
1192
1496
|
describe: "Replacement tag id set (repeat the flag, max 20; replaces ALL current tags)",
|
|
1193
1497
|
type: "string",
|
|
1194
1498
|
array: true
|
|
1195
|
-
}).option("clear-tags", { describe: "Remove all tags", type: "boolean" }).example('$0 scheduled:update abc123 --title "Better hook"', "Retitle a draft, everything else untouched").example('$0 scheduled:update abc123 --at "2026-08-01T15:00:00Z" --status scheduled', "Promote a draft to the queue").example("$0 scheduled:update abc123 --status draft", "Pull a post back to drafts (quota refunds)"),
|
|
1499
|
+
}).option("clear-tags", { describe: "Remove all tags", type: "boolean" }).example('$0 scheduled:update abc123 --title "Better hook"', "Retitle a draft, everything else untouched").example('$0 scheduled:update abc123 --at "2026-08-01T15:00:00Z" --status scheduled', "Promote a draft to the queue").example("$0 scheduled:update abc123 --status draft", "Pull a post back to drafts (quota refunds)").example("$0 scheduled:update abc123 --auto-delete 8 --auto-delete-threshold 500", "Add an auto delete to the post").example("$0 scheduled:update abc123 --no-auto-retweet", "Remove the post's auto retweet"),
|
|
1196
1500
|
run(scheduledUpdate)
|
|
1197
1501
|
).command(
|
|
1198
1502
|
"scheduled:delete <id>",
|
|
1199
1503
|
"Delete a draft or scheduled post by id",
|
|
1200
1504
|
(y) => y.positional("id", { describe: "Post id (from scheduled:list or scheduled:create)", type: "string" }),
|
|
1201
1505
|
run(scheduledDelete)
|
|
1506
|
+
).command(
|
|
1507
|
+
"plug-templates:list",
|
|
1508
|
+
"List your auto-plug reply templates (id, text, has_media) for --auto-plug",
|
|
1509
|
+
(y) => accountOption(y),
|
|
1510
|
+
run(plugTemplatesList)
|
|
1511
|
+
).command(
|
|
1512
|
+
"context:get",
|
|
1513
|
+
"Show the account's Context settings: profile description, interests, rules, reply settings, favorite creators, style guide, products",
|
|
1514
|
+
(y) => accountOption(y),
|
|
1515
|
+
run(contextGet)
|
|
1516
|
+
).command(
|
|
1517
|
+
"context:set",
|
|
1518
|
+
'Edit Context settings; only the flags you pass change (string flags: "" clears; lists fully replace)',
|
|
1519
|
+
(y) => accountOption(y).option("profile-description", {
|
|
1520
|
+
describe: 'Who you are and what you do, grounds the AI voice (max 500 chars; "" clears)',
|
|
1521
|
+
type: "string"
|
|
1522
|
+
}).option("profile-description-enabled", {
|
|
1523
|
+
describe: "Use the profile description in AI writing (--no-profile-description-enabled turns it off)",
|
|
1524
|
+
type: "boolean"
|
|
1525
|
+
}).option("interests", {
|
|
1526
|
+
describe: 'Comma list of topics you want content about (max 30, each max 50 chars; replaces the stored list; "" clears)',
|
|
1527
|
+
type: "string"
|
|
1528
|
+
}).option("rules", {
|
|
1529
|
+
describe: 'SuperX rules the AI must follow on every surface (max 500 chars; "" clears)',
|
|
1530
|
+
type: "string"
|
|
1531
|
+
}).option("reply-rules", {
|
|
1532
|
+
describe: 'Custom instructions for AI-generated replies (max 500 chars; "" clears)',
|
|
1533
|
+
type: "string"
|
|
1534
|
+
}).option("reply-author-name", {
|
|
1535
|
+
describe: "Let AI replies address the post author by name (--no-reply-author-name turns it off)",
|
|
1536
|
+
type: "boolean"
|
|
1537
|
+
}).option("favorite-creators", {
|
|
1538
|
+
describe: 'Comma list of X usernames whose style inspires yours (max 3; replaces the stored list; "" clears)',
|
|
1539
|
+
type: "string"
|
|
1540
|
+
}).option("own-posts-as-examples", {
|
|
1541
|
+
describe: "Use your own posts as voice examples (--no-own-posts-as-examples turns it off)",
|
|
1542
|
+
type: "boolean"
|
|
1543
|
+
}).option("style-audience", {
|
|
1544
|
+
describe: 'Manual audience description that outranks the generated style guide (max 600 chars; "" reverts to generated)',
|
|
1545
|
+
type: "string"
|
|
1546
|
+
}).option("style-vocabulary", {
|
|
1547
|
+
describe: 'Manual vocabulary and style description that outranks the generated style guide (max 1000 chars; "" reverts to generated)',
|
|
1548
|
+
type: "string"
|
|
1549
|
+
}).example('$0 context:set --rules "Never use hashtags. Keep posts under 200 chars."', "Set your SuperX rules").example('$0 context:set --interests "indie hacking,SaaS,AI agents"', "Replace your interests").example('$0 context:set --favorite-creators "levelsio,marc_louvion"', "Set favorite creators").example('$0 context:set --style-audience ""', "Revert to the generated audience description"),
|
|
1550
|
+
run(contextSet)
|
|
1551
|
+
).command(
|
|
1552
|
+
"context:products",
|
|
1553
|
+
"List the account's products (used for product mentions in generated content)",
|
|
1554
|
+
(y) => accountOption(y),
|
|
1555
|
+
run(contextProducts)
|
|
1556
|
+
).command(
|
|
1557
|
+
"context:products:set",
|
|
1558
|
+
"Add or edit ONE product by --url (creates it when new, cap 5) or --id",
|
|
1559
|
+
(y) => accountOption(y).option("id", { describe: "Product id (from context:products) to edit", type: "string" }).option("url", { describe: "Product http(s) url; creates the product when it does not exist", type: "string" }).option("name", { describe: 'Product name (max 200 chars; "" clears)', type: "string" }).option("description", { describe: 'What the product is (max 1000 chars; "" clears)', type: "string" }).option("positioning", { describe: 'Positioning and differentiation (max 2000 chars; "" clears)', type: "string" }).option("features", { describe: 'Key features (max 2000 chars; "" clears)', type: "string" }).option("updates", { describe: 'Recent updates worth mentioning (max 2000 chars; "" clears)', type: "string" }).example('$0 context:products:set --url "https://superx.so" --name "SuperX" --description "X growth platform"', "Add or edit a product by url").example('$0 context:products:set --id 3 --updates "Shipped the public API"', "Update one field by id"),
|
|
1560
|
+
run(contextProductsSet)
|
|
1561
|
+
).command(
|
|
1562
|
+
"context:products:delete <id>",
|
|
1563
|
+
"Remove a product by id (reversible by re-adding the same url)",
|
|
1564
|
+
(y) => accountOption(y).positional("id", { describe: "Product id (from context:products)", type: "string" }),
|
|
1565
|
+
run(contextProductsDelete)
|
|
1566
|
+
).command(
|
|
1567
|
+
"queue:get",
|
|
1568
|
+
"Show the account's posting schedule: predefined time slots and the timezone they run in",
|
|
1569
|
+
(y) => accountOption(y),
|
|
1570
|
+
run(queueGet)
|
|
1571
|
+
).command(
|
|
1572
|
+
"queue:set",
|
|
1573
|
+
"Change the posting schedule; changing the slots also re-flows queued posts onto them",
|
|
1574
|
+
(y) => accountOption(y).option("slots-json", {
|
|
1575
|
+
describe: `JSON array of slots, e.g. '[{"time":"09:00","days":[1,3,5]}]' (0 = Sunday; max 50, one entry per time; replaces the stored slots; '[]' clears them)`,
|
|
1576
|
+
type: "string"
|
|
1577
|
+
}).option("timezone", {
|
|
1578
|
+
describe: 'IANA timezone the slot times run in, e.g. "Europe/London" (changing only this never moves queued posts)',
|
|
1579
|
+
type: "string"
|
|
1580
|
+
}).example(
|
|
1581
|
+
`$0 queue:set --slots-json '[{"time":"09:00","days":[1,2,3,4,5]},{"time":"17:30","days":[1,3,5]}]'`,
|
|
1582
|
+
"Replace the posting slots and re-flow the queue"
|
|
1583
|
+
).example('$0 queue:set --timezone "Europe/London"', "Change the posting timezone without moving posts").example(`$0 queue:set --slots-json '[]'`, "Clear every predefined slot").epilogue(
|
|
1584
|
+
"Changing the timezone and the slots in one call usually moves nothing, because the existing posts were placed under the old timezone; to re-flow them, change the timezone first, then send the slots in a second call."
|
|
1585
|
+
),
|
|
1586
|
+
run(queueSet)
|
|
1202
1587
|
).command("tags:list", "List your tags (id, name, color)", {}, run(tagsList)).command(
|
|
1203
1588
|
"tags:create <name>",
|
|
1204
1589
|
"Create a tag",
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superx-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
7
|
-
"superx": "
|
|
7
|
+
"superx": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "tsup --watch",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "https://github.com/superx-so/superx-agent.git"
|
|
39
|
+
"url": "git+https://github.com/superx-so/superx-agent.git"
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://docs.superx.so",
|
|
42
42
|
"bugs": {
|