Metadata-Version: 2.5
Name: org-social-tui
Version: 1.0.0
Summary: Terminal client for Org Social, powered by the shared Swift client core
Project-URL: Homepage, https://git.andros.dev/org-social/tui-app
Project-URL: Repository, https://git.andros.dev/org-social/tui-app
Project-URL: Issues, https://git.andros.dev/org-social/tui-app/issues
Author-email: Andros Fenollosa <andros@fenollosa.email>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Keywords: decentralized,org-mode,org-social,social-network,textual,tui
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console :: Curses
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications
Classifier: Topic :: Internet
Requires-Python: >=3.12
Requires-Dist: textual>=8.2
Description-Content-Type: text/markdown

# Org Social TUI

A terminal client for [Org Social](https://git.andros.dev/org-social/org-social), a decentralized social network based on plain-text `social.org` files served over HTTP.

![Timeline](https://git.andros.dev/org-social/tui-app/raw/branch/main/screenshots/timeline.png)
![Thread](https://git.andros.dev/org-social/tui-app/raw/branch/main/screenshots/thread.png)
![Reply with a mention](https://git.andros.dev/org-social/tui-app/raw/branch/main/screenshots/compose.png)
![Profile](https://git.andros.dev/org-social/tui-app/raw/branch/main/screenshots/profile.png)
![Groups](https://git.andros.dev/org-social/tui-app/raw/branch/main/screenshots/groups.png)

## 🚀 Quick Start

### New to Org Social?

Follow this quick tutorial to get started without needing to know all the technical details:

**[Quick Tutorial to Get Started on Org Social](https://en.andros.dev/blog/ddd78757/quick-tutorial-to-get-started-on-org-social/)**

### Install and run

You need Python 3.12+, [uv](https://docs.astral.sh/uv/) and a Swift 6 toolchain (macOS 14+ or Linux) to build the engine.

1. Build the engine and put it on your `$PATH`:

```sh
git clone https://git.andros.dev/org-social/client-core.git
swift build -c release --package-path client-core --product orgsocial-engine
cp client-core/.build/release/orgsocial-engine ~/.local/bin/
```

2. Install the client from [PyPI](https://pypi.org/project/org-social-tui/) and run it:

```sh
uv tool install org-social-tui
org-social
```

The first time it opens read-only, showing the whole network. Press `s` to set up your account, or write the configuration file by hand (see Configuration below).

## ✨ Features

- **Timeline**: the people you follow, or the whole network (`a` to switch), with infinite scroll.
- **Threads** with nested replies, reply and boost counts, reactions and poll results.
- **Notifications**: mentions, replies, reactions and boosts, with an unread counter on the tab.
- **Search** by text or `#tag`.
- **Profiles**: follow or unfollow (`F`), and edit your own (`E`).
- **Groups**: browse them, join or leave (`J`) and post to a group (`n`).
- **Write**: new posts, polls, replies (they start with a mention of the author), reactions, boosts and votes. Your posts can be edited (`e`), pinned (`P`) or deleted (`d`). Reacting, boosting or voting again on a post undoes it.
- **Mentions**: `ctrl+t` in the composer lists the people you follow and inserts `@nick` at the cursor.
- **Moderation**: block an author (`x`) or mute words. Hidden posts disappear from every list.
- **Org rendering**: bold, italic, code, strikethrough, headings, links (clickable in terminals that support them), source and quote blocks, images and tags.
- **Read-only mode** without an account.

## ⚙️ Configuration

The configuration lives in `~/.config/org-social-tui/config.toml` (`$XDG_CONFIG_HOME/org-social-tui/config.toml` if that variable is set). Use another file with `--config`:

```sh
org-social --config ~/work/org-social.toml
```

You don't need to write it by hand: press `s` inside the app, fill in the form and save with `ctrl+s`. The file is created with `0600` permissions because it contains your upload credentials.

### Complete example

```toml
mirror = "https://mirror.org-social.org/api"
feed = "https://host.org-social.org/alice/social.org"
lang = "en"
blocked = ["https://spam.example.com/social.org"]
muted_words = ["crypto", "giveaway"]

[upload]
method = "vfile"
url = "https://host.org-social.org/vfile?token=...&ts=...&sig=..."
```

| Key | Description |
|-----|-------------|
| `feed` | Public URL of your `social.org`. Leave it empty to browse read-only. |
| `mirror` | Org Social mirror API used for every read. The default works for everyone. |
| `lang` | Default language of new posts (ISO 639-1 code). |
| `blocked` | Feed URLs whose posts are hidden. `x` on a post adds its author. |
| `muted_words` | Words or phrases; posts containing any of them (body or tags, case-insensitive) are hidden. |
| `[upload]` | How the client uploads your `social.org` after every change. See below. |

### Upload methods

Every action that changes your feed (posting, reacting, following, editing your profile...) downloads your `social.org`, modifies it and uploads it again. Choose how it is uploaded:

**vfile**: your feed is hosted on an Org Social host. This is the easiest option: sign up at [host.org-social.org/signup](https://host.org-social.org/signup) and you will get a public URL (for `feed`) and a vfile URL (for `upload.url`).

```toml
feed = "https://host.org-social.org/alice/social.org"

[upload]
method = "vfile"
url = "https://host.org-social.org/vfile?token=...&ts=...&sig=..."
```

**GitHub**: your `social.org` lives in a GitHub repository. The token needs write access to the repository contents.

```toml
feed = "https://raw.githubusercontent.com/alice/social/main/social.org"

[upload]
method = "github"
token = "github_pat_..."
owner = "alice"
repo = "social"
path = "social.org"   # optional, default social.org
branch = "main"       # optional, default main
```

**Codeberg** (or any Forgejo/Gitea instance):

```toml
feed = "https://codeberg.org/alice/social/raw/branch/main/social.org"

[upload]
method = "codeberg"
token = "..."
owner = "alice"
repo = "social"
path = "social.org"                     # optional, default social.org
branch = "main"                         # optional, default main
instance = "https://codeberg.org"       # optional, for other Forgejo/Gitea hosts
```

**WebDAV**: any server accepting `PUT` uploads (Nextcloud, Apache `mod_dav`, nginx...).

```toml
feed = "https://example.com/social.org"

[upload]
method = "webdav"
url = "https://dav.example.com/files/alice/social.org"
username = "alice"    # optional
password = "..."      # optional
```

### State

Notifications you have already seen are remembered in `state.json`, next to `config.toml`. Delete it to mark everything as unread again.

## ⌨️ Keys

| Key | Action |
|-----|--------|
| `1` `2` `3` `4` | Home, Notifications, Search, Groups |
| `/` | Search (`esc` leaves the search field, so digits switch tabs again) |
| `a` | Following / whole network |
| `ctrl+r` | Refresh |
| `n` | New post (inside a group: post to the group) |
| `m` | Your profile |
| `s` | Account settings |
| `esc` | Back |
| `q` | Quit |

On a post:

| Key | Action |
|-----|--------|
| `enter` | Open the thread |
| `r` | Reply |
| `f` | React (again: remove the reaction) |
| `b` | Boost (again: undo the boost) |
| `v` | Vote on a poll (again: remove the vote) |
| `p` | Author's profile |
| `o` | Open a link |
| `x` | Block the author |
| `e` | Edit (your posts) |
| `P` | Pin or unpin (your posts) |
| `d` | Delete (your posts) |

On a profile: `F` follows or unfollows, `E` edits your own profile. In the Groups tab and on a group: `J` joins or leaves.

In the composer: `ctrl+t` inserts a mention, `ctrl+s` publishes, `esc` cancels. To create a poll, write the question as the text and fill in the options separated by `|` (`Emacs | Vim | Nano`) and how many days it stays open.

## 🧩 How it works

The interface is written in Python with [Textual](https://textual.textualize.io/). All the logic (parsing, writing posts, rendering Org, the mirror API, uploads, the write queue) comes from `orgsocial-engine`, the engine of the shared Swift [client core](https://git.andros.dev/org-social/client-core), the same code the iOS app uses.

```
┌──────────────────────────┐    JSON-RPC 2.0     ┌─────────────────────────────┐
│ Python + Textual (UI)    │ ◄─────────────────► │ orgsocial-engine (Swift)    │
│ src/org_social_tui/      │    stdin/stdout     │ ../client-core              │
└──────────────────────────┘                     └─────────────────────────────┘
```

The app looks for the engine in `../client-core/.build/release`, then `../client-core/.build/debug`, then `$PATH`. Set `ORG_SOCIAL_ENGINE` to use another binary. The protocol is documented in the client core's [ENGINE.md](https://git.andros.dev/org-social/client-core/src/branch/main/ENGINE.md); any program able to spawn a process and read JSON can use it.

## 🛠️ Development

Clone both repositories side by side; the app finds the engine in `../client-core` without installing it:

```sh
git clone https://git.andros.dev/org-social/client-core.git
git clone https://git.andros.dev/org-social/tui-app.git
swift build -c release --package-path client-core --product orgsocial-engine
cd tui-app
uv sync
uv run org-social
```

```sh
uv run pytest                       # all tests (integration ones need the engine built)
uv run pytest -m "not integration"  # without the engine
uv run ruff check && uv run ruff format --check
```

The integration tests run the real engine against a local HTTP server that acts as feed host and mirror, so nothing is published.

## Contributing

Contributions are welcome! Please see the [contribution guidelines](https://git.andros.dev/andros/contribute) for instructions on how to submit issues or pull requests.

## License

GPL-3.0
