Follow X accounts and archive new posts as local Markdown — for Obsidian, search, and downstream LLM workflows.
Project description
x-to-obsidian
Follow a set of X (Twitter) accounts and archive every new post as local Markdown — for Obsidian, search, and LLM workflows.
One file per post, fetched incrementally: a durable, searchable archive of the sources you care about. Obsidian-friendly, not Obsidian-only.
How it's different
Many "tweet to Markdown" tools focus on saving a single post or thread on demand, or doing a one-time import of an existing Twitter archive. x-to-obsidian is built for the opposite job: define a set of accounts once, and keep a local Markdown archive up to date over repeated runs. It tracks what it has already fetched, so each run only pulls what's new.
It's for staying current with a watchlist, not capturing one post.
Who it's for
- Traders keeping a watchlist of accounts and catalysts searchable offline
- Researchers and analysts following a field without living in the timeline
- Journalists monitoring a set of sources over time
- Anyone maintaining a niche-topic feed they want as a local, queryable corpus
The real job isn't "tweets in Obsidian" — it's "a local, queryable archive of the sources I follow," whatever you do with it next.
What it does
- Pulls posts from the accounts you list and writes one Markdown note per post to
<vault>/<handle>/YYYY-MM-DD-<post_id>.md. - Tracks per-account state in SQLite, so reruns only fetch what's new.
- Optionally downloads post media alongside each note.
- Writes a per-run manifest so you can see exactly what was added each run.
What it does not do
No analysis, no summaries, no LLM. It's a clean ETL into Markdown — what you build on top (search, an LLM over the folder, a dashboard) is up to you.
Requirements
- Python 3.12 and uv
- An X (Twitter) API bearer token with read access — create a project/app at the X developer portal. Access tiers and rate limits are set by X.
- Linux, macOS, or WSL2. Native (non-WSL) Windows is not supported: a
C:\...path is translated to/mnt/c/..., which only makes sense under WSL.
Install
pip install x-to-obsidian # or: uv tool install x-to-obsidian
This gives you the x-to-obsidian command. You'll also need a .env and an accounts.yaml — copy the templates from this repo (.env.example, accounts.example.yaml) and edit them. For development, clone the repo and use uv sync + uv run x-to-obsidian as shown below.
Setup
uv sync # install dependencies
cp .env.example .env # set X_BEARER_TOKEN and VAULT_PATH
cp accounts.example.yaml accounts.yaml # list the handles you want to follow
.envholds your bearer token, the destination folder (VAULT_PATH— an Obsidian vault is the usual choice, but any folder works), and toggles (replies, retweets, media, subfolders).accounts.yamlis one entry per handle. Setenabled: falseto skip an account without removing it.
Usage
uv run x-to-obsidian init # create the database and check your config
uv run x-to-obsidian fetch # mock mode: reads a bundled sample, makes no API calls
uv run x-to-obsidian fetch --live # fetch from the live X API
uv run x-to-obsidian status # accounts, last-fetched times, total post count
Notes are written to <vault>/<handle>/YYYY-MM-DD-<post_id>.md.
Common options:
fetch --since-days N— only the last N daysfetch --account <handle>— a single accountfetch --no-media— skip media downloadsreemit— rebuild notes from the database without calling the API
Run uv run x-to-obsidian --help for the full list.
How it works
Each post is saved to a local SQLite database before the next API call, so a run can stop and resume safely — the next run continues where it left off and fetches only new posts. See ARCHITECTURE.md for the internals.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file x_to_obsidian-0.1.2.tar.gz.
File metadata
- Download URL: x_to_obsidian-0.1.2.tar.gz
- Upload date:
- Size: 60.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8c5839a2bf20f7012693ac0755d016f01733a0d967bd56b6f291b7dce2e0a2b
|
|
| MD5 |
cc9617efd99ba075991117c1f5834cfe
|
|
| BLAKE2b-256 |
9cfb8dbbccfa533ed2597a4270f466d593e307c0ed3d793db36241b2802a5a49
|
File details
Details for the file x_to_obsidian-0.1.2-py3-none-any.whl.
File metadata
- Download URL: x_to_obsidian-0.1.2-py3-none-any.whl
- Upload date:
- Size: 30.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53b75b4116a27b077889edaaf855d1654f3dd9583bb75db63ea4df1619cd7917
|
|
| MD5 |
15511bfab339c06bca5a23c86be54df9
|
|
| BLAKE2b-256 |
252986d0b35f66b0875d845316e5b934924ab401c3ccbc2573d2891255158851
|