Skill: ubgb/undersheet (version 1.2.3)

--- name: undersheet description: "Persistent thread memory for OpenClaw agents across any platform — Moltbook, Hacker News, Reddit, Discord, Twitter. Tracks threads, surfaces only new replies, feed cursor so you never re-read the same post. Zero dependencies, pure Python stdlib. Use when your agent needs to remember which threads it engaged with across heartbeat sessions." homepage: https://github.com/ubgb/undersheet metadata: { "openclaw": { "emoji": "🗂️", "requires": { "bins": ["python3"] }, }, } --- # UnderSheet **Version:** 1.2.1 **Author:** ubgb **Tags:** memory, thread-tracking, feed-cursor, heartbeat, moltbook, hackernews, reddit, multi-platform ## What It Does UnderSheet gives your OpenClaw agent persistent thread memory across any platform — Moltbook, Hacker News, Reddit, and more. Every heartbeat, your agent wakes up fresh with zero context. UnderSheet fixes that: - Tracks every thread you've engaged with and surfaces only the ones with new replies - Feed cursor so you only see posts you haven't read yet - Pluggable platform adapters — one skill, every platform - Zero dependencies, pure Python stdlib Built on the architecture of MoltMemory, generalized for everywhere. ## Install ```bash curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/undersheet.py \ -o ~/.openclaw/skills/undersheet/undersheet.py mkdir -p ~/.openclaw/skills/undersheet/platforms curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/platforms/moltbook.py \ -o ~/.openclaw/skills/undersheet/platforms/moltbook.py curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/platforms/hackernews.py \ -o ~/.openclaw/skills/undersheet/platforms/hackernews.py curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/platforms/reddit.py \ -o ~/.openclaw/skills/undersheet/platforms/reddit.py ``` ## Setup Configure credentials for each platform you use: **Moltbook:** ```bash echo '{"api_key": "YOUR_KEY", "agent_name": "YOUR_NAME"}' \ > ~/.config/moltbook/credentials.json ``` **Hacker News** (optional, read-only works without): ```bash echo '{"username": "YOUR_HN_USER", "password": "YOUR_HN_PASS"}' \ > ~/.config/undersheet/hackernews.json ``` **Reddit:** ```bash echo '{ "client_id": "...", "client_secret": "...", "username": "...", "password": "...", "user_agent": "undersheet:v1.0 (by /u/youruser)" }' > ~/.config/undersheet/reddit.json ``` **Discord:** ```bash echo '{"bot_token": "Bot YOUR_TOKEN_HERE", "guild_id": "YOUR_SERVER_ID"}' \ > ~/.config/undersheet/discord.json ``` Bot setup: https://discord.com/developers/applications → New Application → Bot → Reset Token Required permissions: Read Messages, Send Messages, Read Message History, Use Public Threads Invite your bot: `https://discord.com/api/oauth2/authorize?client_id=YOUR_ID&permissions=84992&scope=bot` **Twitter / X:** ```bash echo '{ "bearer_token": "AAA...", "api_key": "...", "api_secret": "...", "access_token": "...", "access_token_secret": "..." }' > ~/.config/undersheet/twitter.json ``` `bearer_token` alone works for read-only (free tier). OAuth 1.0a keys required for posting (Basic tier, ~$100/mo). Get keys: https://developer.twitter.com/en/portal/dashboard ## Usage Run a heartbeat (checks tracked threads + new feed posts): ```bash python3 ~/.openclaw/skills/undersheet/undersheet.py heartbeat --platform moltbook python3 ~/.openclaw/skills/undersheet/undersheet.py heartbeat --platform hackernews python3 ~/.openclaw/skills/undersheet/undersheet.py heartbeat --platform reddit ``` Start tracking a thread: ```bash python3 ~/.openclaw/skills/undersheet/undersheet.py track --platform hackernews --thread-id 47147183 ``` See only new feed posts: ```bash python3 ~/.openclaw/skills/undersheet/undersheet.py feed-new --platform reddit --min-score 50 ``` List available platform adapters: ```bash python3 ~/.openclaw/skills/undersheet/undersheet.py platforms ``` ## Proxy Support Route agent traffic through a proxy or VPN without changing system settings. **HTTP proxy:** ```bash echo '{"http": "http://yourproxy:8080"}' > ~/.config/undersheet/proxy.json ``` **Or pass per-command:** ```bash python3 undersheet.py heartbeat --platform reddit --proxy http://yourproxy:8080 ``` **System VPNs (Mullvad, WireGuard, ProtonVPN):** no config needed — they route all traffic automatically. SOCKS5 users: use a system VPN instead of a local proxy. **Env vars also work** (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`) — UnderSheet respects whatever is set. ## Add to HEARTBEAT.md ```markdown ## UnderSheet (every 30 minutes) - python3 ~/.openclaw/skills/undersheet/undersheet.py heartbeat --platform moltbook - python3 ~/.openclaw/skills/undersheet/undersheet.py heartbeat --platform hackernews ``` ## Adding a New Platform Create `platforms/myplatform.py` with a class named `Adapter` that extends `PlatformAdapter`: ```python from undersheet import PlatformAdapter class Adapter(PlatformAdapter): name = "myplatform" def get_threads(self, thread_ids): ... def get_feed(self, limit=25, **kwargs): ... def post_comment(self, thread_id, content, **kwargs): ... ``` That's it. `undersheet.py heartbeat --platform myplatform` will pick it up automatically.

path: CONTRIBUTING.md
content: # Contributing to UnderSheet

Thanks for wanting to help. UnderSheet is open source (MIT) and actively looking for contributions — especially new platform adapters.

## How It Works

- `main` is protected. All changes go through **pull requests**, no exceptions.
- PRs need 1 approval before merging. Maintainer reviews within a few days.
- Keep it focused: one thing per PR. Small and correct beats large and risky.

## What's Wanted

**High priority:**
- New platform adapters (Bluesky, Mastodon, Lemmy, LinkedIn, Slack — see below)
- Bug fixes with a test case or repro
- Better error messages and edge case handling

**Also welcome:**
- Documentation improvements
- Performance tweaks
- Proxy/auth edge cases

**Not the right fit:**
- Heavy dependencies (this is stdlib-only, intentionally)
- Major architecture changes without discussing in an issue first
- Anything that requires credentials you can't actually test with

---

## Adding a Platform Adapter

Drop a file in `platforms/` with a class named `Adapter`:

```python
from undersheet import PlatformAdapter

class Adapter(PlatformAdapter):
    name = "myplatform"          # used in CLI: --platform myplatform
    config_file = "myplatform"   # reads ~/.config/undersheet/myplatform.json

    def auth(self, config: dict):
        """Store auth state. config = contents of the JSON config file."""
        self.[REDACTED_SECRET]"api_key")

    def get_threads(self, thread_ids: list) -> list:
        """
        Fetch current state for tracked threads.
        Returns: [{"id", "title", "url", "comment_count"}, ...]
        """
        ...

    def get_thread_comments(self, thread_id: str) -> list:
        """
        Optional — but strongly recommended. Return individual comments for a thread.
        Enables get_unanswered_comments() + mark_replied() dupe guard.
        Returns: [{"id", "author", "content", ...}, ...]

        Without this, get_unanswered_comments() silently returns [] for your adapter.
        """
        ...

    def get_feed(self, limit=25, **kwargs) -> list:
        """
        Fetch the platform's feed/frontpage.
        Returns: [{"id", "title", "url", "score", "created_at"}, ...]
        """
        ...

    def post_comment(self, thread_id: str, content: str, **kwargs) -> dict:
        """
        Post a reply.
        Returns: {"success": True} or {"error": "reason"}
        """
        ...
```

Run `python3 undersheet.py platforms` to confirm it's auto-detected.

### Dupe-Reply Guard (mandatory if posting comments)

If your adapter posts comments, **always** use `get_unanswered_comments()` + `mark_replied()`. Never check `comment.replies[]` or scan content — both produce false negatives and cause duplicate replies.

```python
import undersheet as us

state = us.load_state("myplatform")
adapter = MyAdapter()

for c in us.get_unanswered_comments(adapter, state, thread_ids):
    result = adapter.post_comment(c["_thread_id"], f"reply to {c['author']}")
    if result.get("success"):
        us.mark_replied(state, c["id"])   # log ID — never re-fires on this comment
        us.save_state("myplatform", state)
    time.sleep(30)
```

`replied_comment_ids` in state.json is the sole source of truth. It caps at 2000 entries automatically.

**Credential config** goes in `~/.config/undersheet/myplatform.json`. Document the expected keys in your PR description.

---

## Submitting a PR

1. Fork the repo (or ask for write access if you're a regular contributor)
2. Branch off main: `git checkout -b feature/bluesky-adapter`
3. Make your changes
4. Test it actually works — real output preferred over mocked
5. Open a PR against `main` with a short description of what and why

That's it. No CLA, no lengthy checklist.

---

## Questions

Open an issue, or reach out on Moltbook / X [@A2091_](https://x.com/A2091_).


path: README.md
content: # UnderSheet 🗂️

[![GitHub Stars](https://img.shields.io/github/stars/ubgb/undersheet?style=social)](https://github.com/ubgb/undersheet/stargazers)
[![ClawHub](https://img.shields.io/badge/clawhub-install-blue)](https://clawhub.com/skills/undersheet)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)

**Persistent thread memory for OpenClaw agents. Works everywhere.**

Zero dependencies. Pure Python stdlib. One file to rule them all.

---

## The Problem

You've already solved the memory problem: `MEMORY.md`, daily logs, SOUL.md. You write things down. That part works.

What you haven't solved is **threading**.

Your agent wakes up, reads its memory files, and has no idea:
- Which threads you were part of that got new replies
- Which posts you already read vs. which are actually new
- What changed on any given platform since the last session

So you either re-check everything (token expensive) or ignore everything (miss replies). Neither is right.

This is the threading gap. UnderSheet closes it.

## The Solution

UnderSheet tracks your threads across heartbeats — on any platform.

```
$ python3 undersheet.py heartbeat --platform hackernews

[undersheet:hackernews] heartbeat @ 09:01 UTC

💬 2 thread(s) with new replies:
  +4 — Ask HN: Share your productive usage of OpenClaw
       https://news.ycombinator.com/item?id=47147183
  +1 — Show HN: UnderSheet – thread memory for AI agents
       https://news.ycombinator.com/item?id=47149006

📰 5 new post(s) in feed:
  [451↑] I pitched a roller coaster to Disneyland at age 10 in 1978
  [397↑] Amazon accused of widespread scheme to inflate prices
  [316↑] Nearby Glasses
  [245↑] Hacking an old Kindle to display bus arrival times
  [207↑] Steel Bank Common Lisp

[undersheet] State saved.

$ python3 undersheet.py status --platform hackernews

[undersheet:hackernews] status
  Last heartbeat : 2026-02-25T09:01:44+00:00
  Tracked threads: 2
  Seen post IDs  : 47

  Threads:
    [24💬] Ask HN: Share your productive usage of OpenClaw  (last seen 2026-02-25)
           https://news.ycombinator.com/item?id=47147183
    [1💬]  Show HN: UnderSheet – thread memory for AI agents (last seen 2026-02-25)
           https://news.ycombinator.com/item?id=47149006
```

Your agent picks up exactly where it left off. Every platform. Every heartbeat.

---

## Supported Platforms

| Platform | Read | Post | Auth |
|----------|------|------|------|
| Moltbook | ✅ | ✅ (CAPTCHA solver included) | API key |
| Hacker News | ✅ | ✅ | Username/password |
| Reddit | ✅ | ✅ | OAuth (client ID/secret) |
| Twitter / X | ✅ | ✅ | Bearer token (read) + OAuth 1.0a (write) |
| Discord | ✅ | ✅ | Bot token |
| _Your platform_ | [add adapter →](#adding-a-platform) | | |

---

## Install

**Recommended — clone from GitHub (always latest):**
```bash
git clone https://github.com/ubgb/undersheet ~/.openclaw/skills/undersheet
```

Or grab individual files:
```bash
# Core engine
curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/undersheet.py \
  -o ~/.openclaw/skills/undersheet/undersheet.py

# Platform adapters (grab what you need)
mkdir -p ~/.openclaw/skills/undersheet/platforms
curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/platforms/moltbook.py \
  -o ~/.openclaw/skills/undersheet/platforms/moltbook.py
curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/platforms/hackernews.py \
  -o ~/.openclaw/skills/undersheet/platforms/hackernews.py
curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/platforms/reddit.py \
  -o ~/.openclaw/skills/undersheet/platforms/reddit.py
```

ClawHub: `clawhub install undersheet` (may lag behind GitHub)

> **Twitter/X adapter:** also grab `platforms/twitter.py`
> ```bash
> curl -fsSL https://raw.githubusercontent.com/ubgb/undersheet/main/platforms/twitter.py \
>   -o ~/.openclaw/skills/undersheet/platforms/twitter.py
> ```

---

## Quick Start

**1. Configure a platform:**
```bash
# Hacker News (no auth needed for read-only)
echo '{"username": "myuser", "password": "mypass"}' \
  > ~/.config/undersheet/hackernews.json
```

**2. Run a heartbeat:**
```bash
python3 ~/.openclaw/skills/undersheet/undersheet.py heartbeat --platform hackernews
```

**3. Track a specific thread:**
```bash
python3 ~/.openclaw/skills/undersheet/undersheet.py track \
  --platform hackernews --thread-id 47147183
```

**4. Add to HEARTBEAT.md:**
```markdown
## UnderSheet (every 30 minutes)
Run: python3 ~/.openclaw/skills/undersheet/undersheet.py heartbeat --platform hackernews
```

---

## Commands

```
heartbeat   Check tracked threads + new feed posts
feed-new    Show only unseen posts from the feed
track       Start tracking a thread by ID
unread      List threads with new replies (no feed)
status      Overview of tracked threads + last run
platforms   List installed platform adapters
```

## Proxy Support

Route agent traffic through a proxy without changing your system settings.

```bash
# Config file (persists across runs)
echo '{"http": "http://yourproxy:8080"}' > ~/.config/undersheet/proxy.json

# Per-command override
python3 undersheet.py heartbeat --platform reddit --proxy http://yourproxy:8080

# Env vars work too
HTTP_PROXY=http://yourproxy:8080 python3 undersheet.py heartbeat --platform hackernews
```

**System VPNs (Mullvad, WireGuard, ProtonVPN):** no config needed — they route all traffic automatically.

---

## Adding a Platform

Drop a file in `platforms/` with a class named `Adapter`:

```python
from undersheet import PlatformAdapter

class Adapter(PlatformAdapter):
    name = "myplatform"

    def get_threads(self, thread_ids: list) -> list:
        # Return: [{"id", "title", "url", "comment_count", "score"}, ...]
        ...

    def get_feed(self, limit=25, **kwargs) -> list:
        # Return: [{"id", "title", "url", "score", "created_at"}, ...]
        ...

    def post_comment(self, thread_id: str, content: str, **kwargs) -> dict:
        # Return: {"success": True} or {"error": "..."}
        ...
```

Run `undersheet.py platforms` to confirm it's detected.

---

## State

State is stored per-platform at `~/.config/undersheet/<platform>_state.json`. Safe to inspect, edit, or back up.

---

## Relationship to MoltMemory

UnderSheet is the generalized successor to [MoltMemory](https://github.com/ubgb/moltmemory). MoltMemory is Moltbook-specific and stays maintained. UnderSheet brings the same architecture to every platform.

---

## Contributing

UnderSheet is community-driven. The most wanted contribution: **new platform adapters**. Adding Bluesky, Mastodon, Lemmy, or anything else? Just drop a file in `platforms/` following the adapter template.

You don't need to write code to contribute:

- **Got an idea?** → [Open a GitHub issue](https://github.com/ubgb/undersheet/issues/new) — one paragraph is enough
- **Found a bug?** → [Report it here](https://github.com/ubgb/undersheet/issues/new) with what you expected vs. what happened
- **Want to build an adapter?** → Pick a platform from the [open issues](https://github.com/ubgb/undersheet/issues) tagged `good first issue`

All code changes go through pull requests — `main` is protected and reviewed before anything merges.

→ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide.

---

**⭐ If UnderSheet saves you plumbing work, a GitHub star helps others find it.**
[Star on GitHub](https://github.com/ubgb/undersheet) · [Open an issue](https://github.com/ubgb/undersheet/issues/new) · [Browse open issues](https://github.com/ubgb/undersheet/issues) · [Install on ClawHub](https://clawhub.com/skills/undersheet)

---

## License

MIT


path: recipes/README.md
content: # Recipes 🍳

Ready-to-run scripts showing common UnderSheet workflows.

| Recipe | What it does |
|--------|-------------|
| [multi_platform_heartbeat.py](multi_platform_heartbeat.py) | Single heartbeat across all configured platforms |
| [hn_tracker.py](hn_tracker.py) | Track HN threads, check for new comments |

## Ideas for new recipes
- `twitter_mentions.py` — monitor mentions of a keyword on X
- `reddit_monitor.py` — track a subreddit for specific keywords
- `cross_post.py` — post the same update across multiple platforms

## Running a recipe

```bash
# from the undersheet directory
python3 recipes/multi_platform_heartbeat.py
python3 recipes/hn_tracker.py track 47147183
python3 recipes/hn_tracker.py check
```

## Contributing a recipe

Add a `.py` file here with a module docstring explaining what it does and how to use it. Update this README table.


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)