From dbc09745da7d97130a49023d4d3f17fda722c0d1 Mon Sep 17 00:00:00 2001 From: Avi Date: Tue, 4 Aug 2026 16:28:34 -0500 Subject: [PATCH] Add open-source README and MIT license --- LICENSE | 21 ++++ README.md | 290 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 256 insertions(+), 55 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2ebd3c4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Avi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 1fa6972..aa711f3 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,43 @@ # Nostr Feed Manager -A friendly Linux desktop application for managing Nostr profiles and publishing text notes. + +[![Version](https://img.shields.io/badge/version-0.1.0-blue)]() +[![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE) +[![Platform](https://img.shields.io/badge/platform-Linux-lightgrey)]() +[![Status](https://img.shields.io/badge/status-early%20beta-orange)]() -It pairs a Rust core (the same library behind the original command-line tool) with a polished -Electron + React interface. All Nostr logic — key generation, signing, relay communication — -runs in the Rust backend, which the GUI talks to over a JSON-lines IPC channel. +A friendly Linux desktop application for managing [Nostr](https://nostr.com/) profiles and +publishing text notes. + +**Status: early beta (v0.1.0).** The app is usable daily, actively developed, and not yet +packaged for distribution repositories. Expect rough edges and API churn until 1.0. + +Nostr Feed Manager pairs a Rust core (the same library behind the original command-line tool) with +a polished Electron + React interface. All Nostr logic — key generation, signing, relay +communication — runs in the Rust backend, which the GUI talks to over a JSON-lines IPC channel. + +--- ## Features -- Create and switch between Nostr profiles (`npub` addresses) -- Publish short text notes to the Nostr network -- Per-relay publish reports: you always know where a note was accepted -- Add, remove, enable/disable, and test relays -- Light / dark / system theme, configurable publish confirmation and key shortening -- Back up your vault from the UI -- Password-protected vault: secret keys are encrypted at rest with AES-256-GCM under an - Argon2id-derived key. Without a password set, the vault is stored in plaintext (readable only by - your user account) and this is disclosed in the app -- Reveal a profile's secret key (hex + `nsec1...`) from the app or the CLI — only after the vault - password is entered, so keys stay encrypted at rest +- **Profile management** — create and switch between Nostr profiles (`npub` addresses) +- **Compose screen** — write or preview a note before publishing: + - **Write / Preview tabs** with a soft length limit and live character count + - **Image attachments** — pick a local image, upload it to [nostr.build](https://nostr.build), + and publish a note that carries NIP-92 `imeta` (plus legacy `image`) tags so clients render it + - **Link preview cards** — up to three web links per note get a title/image/description card + (fetched locally; your note text is unchanged, clients render their own cards too) +- **Publishing with receipts** — per-relay publish reports: you always know where a note was accepted +- **Relay management** — add, remove, enable/disable, and test relays +- **Encrypted vault** — secret keys are encrypted at rest with AES-256-GCM under an Argon2id-derived + key. Without a password set, the vault is stored in plaintext (readable only by your user account) + and this is disclosed in the app +- **Secret key recovery** — reveal a profile's secret key (hex + `nsec1...`) from the app or the CLI, + only after the vault password is entered, so keys stay encrypted at rest +- **Settings** — light / dark / system theme, publish confirmation, key shortening, and vault backup +- **One binary, two interfaces** — the same Rust crate provides a full CLI and the GUI's IPC server ## Architecture @@ -34,53 +53,119 @@ runs in the Rust backend, which the GUI talks to over a JSON-lines IPC channel. - `src/` — the Rust library (`nostr-manager-backend`). Exposes the same functionality as a command-line binary and as a long-running `serve` process. - `frontend/electron/` — the Electron main and preload scripts. The main process spawns the - Rust backend and correlates requests by `id`. -- `frontend/src/` — the React renderer. It talks only to a thin `window.backend` bridge; - profile summaries and publish reports contain no secret material. + Rust backend and correlates requests by `id`. Native-adjacent work that Rust shouldn't do (file + pickers, HTTP, clipboard) lives here. +- `frontend/src/` — the React renderer. It talks only to a thin `window.backend` bridge; profile + summaries and publish reports contain no secret material. + +**Security model:** secret keys never leave the Rust backend, and the renderer never sees them. +Image uploads to nostr.build are authorized with a NIP-98 auth event signed by the active profile's +key inside the backend. + +## Tech stack + +**Backend (Rust)** + +| Tool | Purpose | +| --- | --- | +| [nostr-sdk](https://crates.io/crates/nostr-sdk) 0.40 (NIP-44, NIP-98) | Nostr protocol, relay client, signing | +| tokio | async runtime | +| argon2 + aes-gcm | vault key derivation and at-rest encryption | +| serde / serde_json | IPC and storage encoding | +| rpassword | interactive password prompts | + +**Frontend (TypeScript)** + +| Tool | Purpose | +| --- | --- | +| Electron 33 | desktop shell, native dialogs, IPC host | +| React 18 + TypeScript | renderer | +| Vite 5 | build tool + dev server | +| Vitest + Testing Library | renderer tests against a fake backend | +| ESLint + Prettier | linting and formatting | +| electron-builder | Linux packaging | ## Requirements -- Rust (stable) and Cargo -- Node.js 20+ and npm -- Linux with a display server (X11 or Wayland) +- **Linux** with a display server (X11 or Wayland) +- **Rust** (stable) and Cargo +- **Node.js 20+** and npm -The Rust backend also exists as a standalone CLI if you prefer the terminal. +No Docker or database is required. The Rust backend also exists as a standalone CLI if you prefer +the terminal. -## Building and running the GUI +## Installation & quick start + +### 1. Clone and build ```sh -# 1. Build the Rust backend (release) +git clone https://github.com//.git +cd nost-feed-manager + +# Build the Rust backend (release) cargo build --release -# 2. Install frontend dependencies +# Install frontend dependencies cd frontend npm install +``` -# 3. Run the app (production-style, served from the built bundle via app://) +### 2. Run + +```sh +# Production-style: renders from the built bundle via app:// npm start ``` -During development you can use Vite's live-reloading renderer instead: +On first launch the app finds a vault left behind by the original Python CLI (if any) in its +working directory, backs it up to a timestamped `*.backup-` file, and imports your profiles. +The original file is left untouched. -```sh -# terminal 1 -npm run dev # starts Vite on http://localhost:5173 +### 3. Configure -# terminal 2 -NOSTR_GUI_DEV_URL=http://localhost:5173 npm start +The app reads configuration from environment variables — it does not load `.env` files itself, but +you can export them from your shell or an `.env` sourced before launching: + +```bash +# .env — sourced by your shell, not read directly by the app + +# Vault password for non-interactive CLI use (create / publish / show-secret) +NFM_PASSWORD= + +# Renderer dev-server URL for hot reload (development only) +NOSTR_GUI_DEV_URL=http://localhost:5173 + +# Override the data directory (defaults to ~/.local/share/nost-feed-manager) +XDG_DATA_HOME= ``` -### Packaging +### 4. Package (optional) ```sh cd frontend -npm run dist # builds the renderer + backend and runs electron-builder +npm run dist # builds renderer + backend and runs electron-builder ``` The unpacked application lands in `frontend/release/linux-unpacked/`; launch it with `./nost-feed-manager`. -## Command-line usage +## Usage + +### Desktop app + +The GUI covers profiles, composing/publishing, relays, and settings. + +> **Screenshot placeholder** — add `docs/screenshots/compose.png` (the Compose screen with the +> Write / Preview tabs, an attached image, and a link-preview card) and reference it here. +> +> ```md +> ![Compose screen](docs/screenshots/compose.png) +> ``` +> +> **GIF placeholder** — add `docs/screenshots/compose-demo.gif` (attaching an image and publishing) +> and reference it here. + +### Command-line interface The Rust crate builds a single binary with both a CLI and the GUI IPC server: @@ -104,38 +189,72 @@ prompted interactively. They are never accepted as command-line arguments. `crea prompt for the vault password automatically when the vault is encrypted, and so does `show-secret` — the key is only displayed after the vault password is entered. -## Storage and migration +### IPC protocol (for tooling) + +The `serve` command speaks one JSON object per line. The Electron main process does the same thing +when it spawns the backend: + +```json +{"id": 1, "method": "publish_note", "params": {"npub": "...", "content": "Hello world"}} +{"id": 1, "status": "ok", "data": {"npub": "...", "reports": [{"relay": "wss://nos.lol", "accepted": true}]}} +``` + +## Storage and security - The vault (`profiles_vault.json`) and settings live in `$XDG_DATA_HOME/nost-feed-manager/` (defaulting to `~/.local/share/nost-feed-manager/`), created with permissions `0700` for the directory and `0600` for the files. -- The original CLI saved `profiles_vault.json` in its working directory. On first launch this - app finds that file, copies it to a timestamped `*.backup-` next to it, and imports your - profiles into the new location. The original file is left untouched. - The vault is stored in plaintext until you set a password (Settings → Storage, or `set-password` in the CLI). Once protected, every secret key is encrypted at rest with AES-256-GCM under a key derived from your password with Argon2id. Labels and public keys stay - readable so profiles can be browsed while the vault is locked. You unlock once per session; - the derived key lives only in memory and is never written to disk. Anyone with access to your - user account can still read the vault file, so the password is a defence-in-depth layer, not a - replacement for keeping your account secure. + readable so profiles can be browsed while the vault is locked. You unlock once per session; the + derived key lives only in memory and is never written to disk. +- Anyone with access to your user account can still read the vault file, so the password is a + defence-in-depth layer, not a replacement for keeping your account secure. -## Development +## Development guide + +### Building from source ```sh +cargo build --release # Rust backend +cd frontend +npm install +npm run build # renderer type-check + Vite build +npm run electron:build # compile Electron main/preload +``` + +### Running with hot reload + +```sh +# terminal 1 — Vite dev server +cd frontend +npm run dev + +# terminal 2 — Electron pointed at the dev server +NOSTR_GUI_DEV_URL=http://localhost:5173 npm start +``` + +### Tests and linting + +All checks must pass before merging (the test suites exercise the real IPC protocol through a +fake backend): + +```sh +# Rust +cargo test # 64 unit tests +cargo fmt --check +cargo clippy --all-targets + +# Frontend cd frontend npm run typecheck # TypeScript (renderer + electron) npm run lint # ESLint npm run format:check # Prettier -npm test # Vitest (jsdom), including IPC-level fake backend tests - -cd .. -cargo test # Rust unit tests -cargo fmt --check # formatting -cargo clippy --all-targets +npm test # Vitest (jsdom), 65 tests across 12 files ``` -## Project layout +### Project layout ``` src/ Rust library + CLI + IPC server @@ -145,13 +264,74 @@ src/ Rust library + CLI + IPC server ipc.rs JSON-lines serve() loop and request/reply envelope main.rs CLI entry point profiles.rs profile create/list/select - publish.rs note publishing with per-relay reports + publish.rs note publishing with per-relay reports, image/`imeta` tagging relays.rs default relays, validation, connection tests settings.rs theme and user preferences + uploads.rs NIP-98 authorization for image-host uploads vault.rs vault storage (plaintext or password-encrypted) and migration frontend/ - electron/ Electron main + preload (backend spawn, IPC, clipboard) - src/ React app (components, screens, state, styles) - src/test/ Vitest suite with a fake backend speaking the real protocol + electron/ Electron main + preload (backend spawn, native dialogs, + image uploads, link-preview fetching, clipboard) + src/ + screens/ Compose, Home, Profiles, Relays, Settings + lib/ types, api bridge, media helpers (URL extraction) + state/ React context (AppProvider) + test/ Vitest suite with a fake backend speaking the real protocol + styles.css global styles package.json scripts and electron-builder config ``` + +## Contributing + +Contributions are welcome — bug reports, documentation, and pull requests all help. Please be +respectful and constructive; everyone is expected to follow the [Code of Conduct](#code-of-conduct). + +### Reporting bugs + +Open an issue on the [issue tracker](https://github.com///issues) and include: + +- A clear title and description of the expected vs. actual behavior +- Steps to reproduce +- Platform details (distro, Wayland/X11, app version from `info`) +- Any relevant log output + +### Requesting features + +Open a feature request issue and describe the problem you're trying to solve, not just the feature +you want. That makes it easier to design something that fits the existing architecture. + +### Opening a pull request + +1. **Fork** the repository on GitHub. +2. **Branch** from `master` with a descriptive name: `fix/relay-test-timeout`, + `feat/avatar-support`, etc. +3. **Make your change.** Match the surrounding style (see below) and keep the diff focused. +4. **Run the full verification suite** (see [Tests and linting](#tests-and-linting)) — everything + must pass. +5. **Write tests** for new behavior. Existing tests exercise the IPC protocol end to end, so + prefer testing through that public surface. +6. **Update docs** if the change is user-visible (README, feature list). +7. **Commit** with a concise imperative subject line (e.g. `Add relay connection timeout`), then + **push** and open a pull request against `master`. +8. In the PR description, summarize the change, link any related issues, and list what you tested. + +### Code style + +- **Rust:** formatted with `cargo fmt`; `cargo clippy --all-targets` clean; public items get doc + comments; errors use the structured `AppError` types. +- **TypeScript / React:** formatted with Prettier; ESLint clean; `tsconfig` strictness is + enforced by `npm run typecheck`; tests use Vitest + Testing Library (user-event, never raw + `fireEvent` where user-event applies). +- **General:** no secrets in code, commits, or logs; no generated files checked in; keep changes + minimal and reviewable. + +## Community & support + +- **Issues** — [bug reports and feature requests](https://github.com///issues) +- **Discussions** — [questions and ideas](https://github.com///discussions) +- **Code of Conduct** — until `CODE_OF_CONDUCT.md` is added, + contributors are expected to follow the [Contributor Covenant v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). + +## License + +MIT. See the [LICENSE](LICENSE) file. Copyright (c) 2026 Avi.