Metadata-Version: 2.4
Name: x-to-obsidian
Version: 0.1.2
Summary: Follow X accounts and archive new posts as local Markdown — for Obsidian, search, and downstream LLM workflows.
Project-URL: Homepage, https://github.com/kajensenn/x-to-obsidian
Project-URL: Repository, https://github.com/kajensenn/x-to-obsidian
Project-URL: Issues, https://github.com/kajensenn/x-to-obsidian/issues
Author-email: kaj <kajbayley@proton.me>
License-Expression: MIT
License-File: LICENSE
Keywords: archive,etl,markdown,obsidian,second-brain,twitter,x
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: <3.13,>=3.12
Requires-Dist: click==8.1.7
Requires-Dist: pydantic-settings==2.6.1
Requires-Dist: pydantic==2.7.4
Requires-Dist: pyyaml==6.0.2
Requires-Dist: sqlalchemy==2.0.36
Requires-Dist: structlog==24.4.0
Requires-Dist: tweepy==4.14.0
Requires-Dist: typer==0.12.5
Provides-Extra: dev
Requires-Dist: freezegun==1.5.1; extra == 'dev'
Requires-Dist: mypy==1.11.2; extra == 'dev'
Requires-Dist: pytest-cov==5.0.0; extra == 'dev'
Requires-Dist: pytest==8.3.3; extra == 'dev'
Requires-Dist: respx==0.21.1; extra == 'dev'
Requires-Dist: ruff==0.6.9; extra == 'dev'
Requires-Dist: types-pyyaml==6.0.12.20240917; extra == 'dev'
Description-Content-Type: text/markdown

# 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](https://docs.astral.sh/uv/)
- An X (Twitter) API bearer token with read access — create a project/app at the [X developer portal](https://developer.x.com/). 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

```bash
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

```bash
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
```

- `.env` holds 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.yaml` is one entry per handle. Set `enabled: false` to skip an account without removing it.

## Usage

```bash
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 days
- `fetch --account <handle>` — a single account
- `fetch --no-media` — skip media downloads
- `reemit` — 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](ARCHITECTURE.md) for the internals.

## License

MIT — see [LICENSE](LICENSE).
