threadfox-lite 0.1.1__py3-none-any.whl

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.
@@ -0,0 +1,2 @@
1
+ """ThreadFox Lite: free, read-only Reddit tools for AI agents."""
2
+ __version__ = "0.1.1"
@@ -0,0 +1,88 @@
1
+ """Read-only calls to Reddit's public JSON. No login, no posting, one request per tool call."""
2
+ import re
3
+ from urllib.parse import urlsplit
4
+
5
+ import httpx
6
+
7
+ UA = "threadfox-lite/0.1.1 (+https://threadfox.vip; read-only rules and post-status checks)"
8
+ PROMO = re.compile(r"self[- ]?promo|promot|advertis|spam|affiliate|referral|\b10\s*%|link[- ]?drop|no links?\b|marketing|sell|product|business", re.I)
9
+ SUB = re.compile(r"^[A-Za-z0-9_]{2,21}$")
10
+
11
+
12
+ BLOCKED = ("Reddit refused this network (it blocks many servers, VPNs and cloud machines). Run the tool from your own "
13
+ "computer's connection, or open the page in a browser.")
14
+
15
+
16
+ def _blocked(resp):
17
+ """Reddit's network block is a 403 whose body is not JSON (plain 'Blocked'); a private subreddit's 403 is JSON."""
18
+ if resp.status_code != 403:
19
+ return False
20
+ try:
21
+ resp.json()
22
+ return False
23
+ except ValueError:
24
+ return True
25
+
26
+
27
+ def _client(transport=None):
28
+ return httpx.Client(headers={"User-Agent": UA}, timeout=15, follow_redirects=True, transport=transport)
29
+
30
+
31
+ def subreddit_rules(name: str, transport=None) -> dict:
32
+ name = name.strip().removeprefix("/").removeprefix("r/").strip("/")
33
+ if not SUB.match(name):
34
+ return {"error": "Give a subreddit name like 'SaaS' or 'r/SaaS'."}
35
+ with _client(transport) as c:
36
+ rules = c.get(f"https://www.reddit.com/r/{name}/about/rules.json")
37
+ about = c.get(f"https://www.reddit.com/r/{name}/about.json")
38
+ blocked = _blocked(rules) or _blocked(about)
39
+ if blocked:
40
+ return {"subreddit": name, "error": BLOCKED, "open_instead": f"https://www.reddit.com/r/{name}/about/rules"}
41
+ if rules.status_code in (403, 404) or about.status_code in (403, 404):
42
+ return {"subreddit": name, "error": f"r/{name} is private, banned or does not exist (HTTP {rules.status_code})."}
43
+ if rules.status_code == 429 or about.status_code == 429:
44
+ return {"subreddit": name, "error": "Reddit rate-limited this request. Wait a minute and try again."}
45
+ rules.raise_for_status(); about.raise_for_status()
46
+ data = about.json().get("data", {})
47
+ items = []
48
+ for r in rules.json().get("rules", []):
49
+ text = f"{r.get('short_name', '')}\n{r.get('description', '')}".strip()
50
+ items.append({"rule": r.get("short_name", ""), "details": (r.get("description") or "")[:1200],
51
+ "applies_to": r.get("kind", "all"), "mentions_promotion": bool(PROMO.search(text))})
52
+ return {
53
+ "subreddit": data.get("display_name", name),
54
+ "subscribers": data.get("subscribers"),
55
+ "description": (data.get("public_description") or "")[:500],
56
+ "rules": items,
57
+ "promotion_rules": [i["rule"] for i in items if i["mentions_promotion"]],
58
+ "read_before_posting": "Follow every rule above. If a rule limits self-promotion, use the community's promo thread or don't post the link. Write a new post for this community; never paste the same post in several.",
59
+ }
60
+
61
+
62
+ def post_status(url: str, transport=None) -> dict:
63
+ try:
64
+ parts = urlsplit(url.strip())
65
+ except ValueError:
66
+ return {"error": "Give a Reddit post URL."}
67
+ host = (parts.hostname or "").lower()
68
+ m = re.match(r"^/r/[^/]+/comments/([a-z0-9]+)", parts.path or "")
69
+ if not host.endswith("reddit.com") or not m:
70
+ return {"error": "Give a full post URL like https://www.reddit.com/r/SaaS/comments/abc123/title/"}
71
+ with _client(transport) as c:
72
+ resp = c.get(f"https://www.reddit.com/comments/{m.group(1)}.json", params={"raw_json": 1, "limit": 1})
73
+ if _blocked(resp):
74
+ return {"error": BLOCKED, "open_instead": url.strip()}
75
+ if resp.status_code == 404:
76
+ return {"status": "not_found", "id": m.group(1)}
77
+ if resp.status_code == 429:
78
+ return {"error": "Reddit rate-limited this request. Wait a minute and try again."}
79
+ resp.raise_for_status()
80
+ post = resp.json()[0]["data"]["children"][0]["data"]
81
+ removed = post.get("removed_by_category")
82
+ status = "removed" if removed else ("deleted" if post.get("author") == "[deleted]" or post.get("selftext") == "[deleted]" else "live")
83
+ return {
84
+ "status": status, "removed_by": removed, "title": post.get("title"), "subreddit": post.get("subreddit"),
85
+ "score": post.get("score"), "comments": post.get("num_comments"), "locked": post.get("locked"),
86
+ "created_utc": post.get("created_utc"), "permalink": "https://www.reddit.com" + (post.get("permalink") or ""),
87
+ "note": "Check again at 1, 6 and 24 hours. If a community removes you twice, stop posting there.",
88
+ }
@@ -0,0 +1,51 @@
1
+ """ThreadFox Lite MCP server (stdio).
2
+
3
+ Two free, read-only tools any agent can use before and after posting to Reddit, plus `threadfox_full_kit`,
4
+ which tells a paying customer how to open the full ThreadFox kit (posting from your own Chrome, replies,
5
+ the outcome ledger and the course). The full kit is sold at https://threadfox.vip and on MCP Marketplace.
6
+ """
7
+ import os
8
+
9
+ from mcp.server.fastmcp import FastMCP
10
+
11
+ from . import reddit
12
+
13
+ mcp = FastMCP("threadfox-lite")
14
+
15
+
16
+ @mcp.tool()
17
+ def subreddit_rules(subreddit: str) -> dict:
18
+ """Read a subreddit's rules, size and description, and flag the rules that mention self-promotion.
19
+ Call this before writing any post for that community."""
20
+ return reddit.subreddit_rules(subreddit)
21
+
22
+
23
+ @mcp.tool()
24
+ def post_status(url: str) -> dict:
25
+ """Check whether a Reddit post is still live, removed by moderators or deleted, with its score and comments."""
26
+ return reddit.post_status(url)
27
+
28
+
29
+ @mcp.tool()
30
+ def threadfox_full_kit() -> dict:
31
+ """How to get the full ThreadFox kit: your AI reads the rules, writes a post that fits each community,
32
+ publishes from your own Chrome, answers comments and records every outcome."""
33
+ key = os.environ.get("MCP_LICENSE_KEY", "")
34
+ if key.startswith("mcp_live_"):
35
+ return {"licensed": True,
36
+ "next_step": "Open https://threadfox.vip/redeem, paste your MCP Marketplace license key and your email, and you land in your private ThreadFox library (one-command install, lessons, full download).",
37
+ "support": "support@threadfox.vip"}
38
+ return {"licensed": False,
39
+ "what_it_adds": ["Publishing from your own signed-in Chrome, no Reddit API keys", "Replies to comments",
40
+ "A ledger of every post and outcome, and it stops where a community removed you",
41
+ "12-module course, 48 prompts"],
42
+ "proof": "Built from our own run: 978,227 recorded views from two days of posts (Sept 20-21 UTC), with no ad spend. Our result, not a promise of yours.",
43
+ "get_it": "https://threadfox.vip ($49 once, 30-day refund) or ThreadFox on MCP Marketplace"}
44
+
45
+
46
+ def main():
47
+ mcp.run()
48
+
49
+
50
+ if __name__ == "__main__":
51
+ main()
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.5
2
+ Name: threadfox-lite
3
+ Version: 0.1.1
4
+ Summary: Read a subreddit's rules and check whether a Reddit post is still up, from Claude Code, Codex or any MCP client. Free tools from ThreadFox.
5
+ Project-URL: Homepage, https://threadfox.vip
6
+ Project-URL: Source, https://github.com/amflimited/threadfox-lite
7
+ Author-email: "AMF Indiana (ThreadFox)" <support@threadfox.vip>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: httpx>=0.27
12
+ Requires-Dist: mcp<2,>=1.2.0
13
+ Description-Content-Type: text/markdown
14
+
15
+ # ThreadFox Lite
16
+
17
+ Free, read-only Reddit tools for Claude Code, Codex and any MCP client, plus a `reddit-rules-first` agent skill.
18
+
19
+ - `subreddit_rules`: a subreddit's rules, size and description, with the self-promotion rules flagged. Call it before writing a post.
20
+ - `post_status`: whether a post is still live, removed by moderators or deleted, with score and comments. Check at 1, 6 and 24 hours.
21
+ - `threadfox_full_kit`: how to get the full ThreadFox kit (posting from your own Chrome, replies, an outcome ledger, a 12-module course).
22
+
23
+ No login, no posting, no Reddit API keys: each call is one request to Reddit's public JSON with a descriptive user agent.
24
+
25
+ ## Install
26
+
27
+ Claude Code plugin (the tools and the skill together):
28
+
29
+ /plugin marketplace add amflimited/threadfox-lite
30
+ /plugin install threadfox-lite@threadfox
31
+
32
+ Claude Code, the MCP server only:
33
+
34
+ claude mcp add threadfox-lite -- uvx threadfox-lite
35
+
36
+ Claude Desktop: download the `.mcpb` file from the [latest release](https://github.com/amflimited/threadfox-lite/releases/latest) and open it (Settings > Extensions). It needs [uv](https://docs.astral.sh/uv/) on your machine.
37
+
38
+ Codex (`~/.codex/config.toml`):
39
+
40
+ [mcp_servers.threadfox-lite]
41
+ command = "uvx"
42
+ args = ["threadfox-lite"]
43
+
44
+ Any other MCP client: run `uvx threadfox-lite` (the package is on [PyPI](https://pypi.org/project/threadfox-lite/)).
45
+
46
+ Bought ThreadFox on MCP Marketplace? Set `MCP_LICENSE_KEY` to your key, ask your AI to run `threadfox_full_kit`, or go straight to https://threadfox.vip/redeem.
47
+
48
+ ## The skill
49
+
50
+ `skills/reddit-rules-first/SKILL.md` works on its own in any agent that reads skills: rules first, one post per community, disclose, check what stayed up. Install it anywhere with `npx skills add amflimited/threadfox-lite`.
51
+
52
+ Also listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=threadfox) as `io.github.amflimited/threadfox-lite`.
53
+
54
+ ## About
55
+
56
+ Made by AMF Indiana, the team behind ThreadFox (https://threadfox.vip). Built from our own run: 978,227 recorded views from two days of posts (Sept 20-21 UTC), with no ad spend. Not affiliated with Reddit, OpenAI or Anthropic. MIT licence.
57
+
58
+ <!-- mcp-name: io.github.amflimited/threadfox-lite -->
@@ -0,0 +1,8 @@
1
+ threadfox_lite/__init__.py,sha256=FZinXVLLw7J-nFwMgt2BpA_hc-x3WPsb9KTbSJF00rA,88
2
+ threadfox_lite/reddit.py,sha256=0Dk9NvrJkMSLyGgXLfD40InSx85x3LExR1eAo5RArAw,4599
3
+ threadfox_lite/server.py,sha256=VKvy1wRZZfYcwoktB41l1a3ejo3KrgFWFYeqXw19cTc,2218
4
+ threadfox_lite-0.1.1.dist-info/METADATA,sha256=XsZlI8dx3S0gK7t9Rc18SYfbFYxTYwBHdNQzBTgEc-g,2787
5
+ threadfox_lite-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
6
+ threadfox_lite-0.1.1.dist-info/entry_points.txt,sha256=q4GHRJUM6btSzJlkx27mVhJCC6JLvnT9Ao30mly54RU,62
7
+ threadfox_lite-0.1.1.dist-info/licenses/LICENSE,sha256=bxhuBa5iSabRXFCavOTYtFXcXaqc-yGmTh7xoMpHkDE,1068
8
+ threadfox_lite-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ threadfox-lite = threadfox_lite.server:main
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AMF Indiana
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.