Initial build: stash server + browser extension
Server (Express + better-sqlite3 + FTS5, mirroring keep's stack): /api/capture (upsert-on-url, per PROPOSAL.md's dedup decision), /api/articles (list/search/mark-read/delete), plus a minimal server-rendered reading list and reader view at / and /read/:id (token passed as ?t= there since browser navigation can't set an Authorization header). Bearer-token auth generated via `npm run token` — no login flow, matching keep's one-time identity registration pattern. Extension (Manifest V3, sideloaded, no store distribution): toolbar click or context menu "Send to stash" runs @mozilla/readability (vendored into extension/lib/) against the current page, falling back to raw rendered HTML when extraction comes back too thin. Options page for one-time server URL + token entry. Verified end-to-end with the real extension loaded in a live Chromium instance (not just unit-tested extraction logic): capture against a real page, dedup-on-recapture, mark-read, FTS5 search, 401 on missing auth, and both server-rendered views all confirmed working. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
53
README.md
Normal file
53
README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# stash
|
||||
|
||||
A self-hosted read-later tool. Click "Send to stash" in the browser, the
|
||||
current page's article content is extracted and stored on your own
|
||||
server — full-text searchable, with a plain reader view. No third party
|
||||
ever sees your reading list.
|
||||
|
||||
Two pieces, neither useful alone:
|
||||
|
||||
- **Server** (`src/server`) — Express + SQLite (via `better-sqlite3`),
|
||||
FTS5 full-text search, a minimal server-rendered reading list + reader
|
||||
view, and the `/api/*` endpoints the extension talks to.
|
||||
- **Extension** (`extension/`) — Manifest V3, sideloaded (not published to
|
||||
any store). Runs [`@mozilla/readability`](https://github.com/mozilla/readability)
|
||||
against the current page to strip nav/ads/chrome and keep just the
|
||||
article, falling back to the raw rendered page if extraction comes back
|
||||
too thin to be useful.
|
||||
|
||||
Full design and the reasoning behind the scope boundaries: [PROPOSAL.md](./PROPOSAL.md).
|
||||
|
||||
## Server setup
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
npm install
|
||||
npm run token -- my-laptop # prints a bearer token — paste it into the extension's options
|
||||
npm run dev:server
|
||||
```
|
||||
|
||||
## Extension setup
|
||||
|
||||
1. `chrome://extensions` → enable Developer mode → "Load unpacked" → select `extension/`
|
||||
2. Click the extension icon once, or use "Options" from its context menu, to open the options page
|
||||
3. Enter your server's URL and the token from `npm run token`
|
||||
|
||||
Then either click the toolbar icon or right-click a page → "Send to stash".
|
||||
|
||||
## Reading
|
||||
|
||||
Visit `http://your-server:3070/?t=<token>` — a plain list of what's been
|
||||
captured, with search and an unread filter. Click through to `/read/:id`
|
||||
for the cleaned reader view.
|
||||
|
||||
## Status
|
||||
|
||||
Built and verified end-to-end: capture → server storage → full-text
|
||||
search → mark-read → reader view, driven through the actual extension
|
||||
(loaded in a real Chromium instance, not just unit-tested logic) against
|
||||
a live server. Not yet deployed anywhere.
|
||||
|
||||
## License
|
||||
|
||||
MIT.
|
||||
Reference in New Issue
Block a user