npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pmarca-kv-storage

v1.0.0

Published

Use Twitter DMs as a persistent key-value store for AI agents. Store data in anyone's inbox.

Downloads

100

Readme

twitter-kv-store

Use Twitter DMs as a persistent key-value store for AI agents.

Store your agent's memory, state, config, and data in anyone's DM inbox. pmarca stores your grocery list. You get free distributed storage.

agent.set("tasks:pending", JSON.stringify(todo_list))
// → DMs @pmarca: "KV:SET:tasks:pending:W3sidGFzayI6..."

What This Is

A key-value database where every write is a Twitter DM and every read replays the DM history. The conversation thread IS the database — append-only, chronologically ordered, globally accessible from any device with your Twitter credentials.

Why this is useful for agents:

  • Zero infrastructure — no Redis, no Postgres, no S3
  • Persistent across sessions by default
  • Readable by humans (your DM thread shows agent state)
  • Works anywhere that can hit the Twitter API
  • The billionaire reads your agent's internal state as his inbox

Quick Start

npm install
cp .env.example .env
# fill in your Twitter API credentials
npm run dev
# CLI
npm run cli -- set my_key "hello world"
npm run cli -- get my_key
npm run cli -- list
npm run cli -- dump
# HTTP
curl -X PUT http://localhost:3001/v1/kv/my_key \
  -H "Content-Type: application/json" \
  -d '{"value": "hello world"}'

curl http://localhost:3001/v1/kv/my_key

Use as a Library

import { TwitterTransport, TwitterKV } from 'twitter-kv-store';

const transport = new TwitterTransport({
  appKey: process.env.TWITTER_APP_KEY!,
  appSecret: process.env.TWITTER_APP_SECRET!,
  accessToken: process.env.TWITTER_ACCESS_TOKEN!,
  accessSecret: process.env.TWITTER_ACCESS_SECRET!,
  targetUserId: '23375688', // pmarca
});

const kv = new TwitterKV(transport);

await kv.set('agent:memory:last_task', 'analyze $NVDA earnings');
const task = await kv.get('agent:memory:last_task');
const allKeys = await kv.listKeys('agent:memory:');

Use With AI Agents (OpenAI Function Calling)

Fetch tool definitions and inject into your agent:

curl http://localhost:3001/tools

Returns:

{
  "tools": [
    { "type": "function", "function": { "name": "kv_set", ... } },
    { "type": "function", "function": { "name": "kv_get", ... } },
    { "type": "function", "function": { "name": "kv_delete", ... } },
    { "type": "function", "function": { "name": "kv_list", ... } },
    { "type": "function", "function": { "name": "kv_dump", ... } }
  ]
}

Execute tool calls:

curl -X POST http://localhost:3001/tools/call \
  -H "Content-Type: application/json" \
  -d '{"name": "kv_set", "arguments": {"key": "agent:state", "value": "running"}}'

Plug into Claude:

import anthropic, requests

tools = requests.get("http://localhost:3001/tools").json()["tools"]

client = anthropic.Anthropic()
resp = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "Remember that I prefer dark mode"}]
)

# Agent calls kv_set("user:preferences:theme", "dark")
# → DMs @pmarca: "KV:SET:user:preferences:theme:ZGFyaw=="

Storage Protocol

Every KV operation is encoded as a plain-text DM:

| Operation | DM Message Format | |-----------|------------------| | SET | KV:SET:{key}:{base64(value)} | | DEL | KV:DEL:{key} |

On read, the full DM history is fetched and replayed chronologically. Last write wins. DEL is a tombstone.

Example DM thread (your side)

KV:SET:grocery_list:LSBlZ2dzCi0gc2t5ciBZb2d1cnQKLSByYXZpb2xp
KV:SET:api_token:c2VjcmV0X3Rva2Vu
KV:SET:agent:task:YW5hbHl6ZSAkTlZEQQ==
KV:DEL:grocery_list
KV:SET:agent:status:cnVubmluZw==

The other person's inbox:

- eggs
- skyr yoghurt
- ravioli    ← they see your grocery list decoded, or they don't, who cares

Key Design

  • Keys are namespaced with colons: agent:memory:fact_1
  • Values are base64-encoded UTF-8 — store anything: JSON, binary-safe strings, numbers
  • Max value size: ~7,400 bytes (Twitter DM limit 10,000 chars minus key + overhead)
  • For larger values: chunk them with keys like bigdata:chunk:0, bigdata:chunk:1

API Reference

REST

GET    /health                    Health check
GET    /v1/kv                     List all keys (?prefix=agent:)
GET    /v1/kv/:key                Get value
PUT    /v1/kv/:key                Set value { "value": "..." }
DELETE /v1/kv/:key                Delete key
POST   /v1/kv/sync                Force sync from DM history
GET    /v1/kv/dump                Full database dump
GET    /tools                     OpenAI tool definitions
POST   /tools/call                Execute tool call

CLI

npm run cli -- set <key> <value>
npm run cli -- get <key>
npm run cli -- del <key>
npm run cli -- list [prefix]
npm run cli -- dump
npm run cli -- sync

Twitter API Setup

  1. Go to developer.twitter.com
  2. Create an App with OAuth 1.0a enabled
  3. Set permissions to Read and Write and Direct Messages
  4. Generate Access Token + Secret
  5. Copy to .env:
TWITTER_APP_KEY=...
TWITTER_APP_SECRET=...
TWITTER_ACCESS_TOKEN=...
TWITTER_ACCESS_SECRET=...
TARGET_USER_ID=23375688   # pmarca's user ID
PORT=3001

Required scopes: dm.read, dm.write, users.read

Target user ID lookup:

# Get user ID from username
curl "https://api.twitter.com/2/users/by/username/pmarca" \
  -H "Authorization: Bearer $BEARER_TOKEN"
# → {"data":{"id":"23375688","name":"Marc Andreessen","username":"pmarca"}}

Rate Limits

| Tier | DMs/day | DM history | |------|---------|-----------| | Free | 500 | last 30 days | | Basic ($100/mo) | 1,000 | last 30 days | | Pro ($5000/mo) | 35,000 | full history |

For agent use (read-heavy, write-light): Free tier is fine for most workloads.

Cache strategy: Local in-memory cache syncs from DM history every 30s (configurable). Reads are fast; writes always flush to DM.


Limits & Edge Cases

  • Max value size: ~7,400 bytes. Chunk large values manually.
  • DM history depth: Twitter returns last ~200 events. Old writes are lost after ~200 ops. For long-lived agents: periodic dump + compact (write a KV:SNAPSHOT: entry).
  • Concurrent writes: Last write wins. No locking. Single-agent use is fine; multi-agent requires namespacing.
  • Target user blocking: If target blocks you, writes fail. Use an alt account you control as the target.
  • Public visibility: DMs are private. The content is not visible to anyone except sender and recipient.

Architecture

Your Agent
    │
    │  kv.set("key", "value")
    ▼
TwitterKV (store.ts)
    │  encode → KV:SET:key:base64val
    ▼
TwitterTransport (twitter.ts)
    │  POST /1.1/direct_messages/events/new
    ▼
Twitter API
    │
    ▼
Target User's DM Inbox  ← pmarca sees: "KV:SET:grocery_list:..."
    │
    │  (on kv.get)
    ▼
GET /1.1/direct_messages/events/list
    │  replay all KV:SET/KV:DEL ops in order
    ▼
In-memory cache → return value

Inspiration

"If you ever want to get a billionaire's attention — make their DMs your grocery list" — @michaelbeer01

Using Marc Andreessen's DM inbox as a database is peak zero-infrastructure architecture.


License

MIT. The Twitter ToS is your problem, not ours.