OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer commits / 54f5915

README: reference, not essay
Cut the prose sections and replaced them with tables: config, the full CLI grouped by area, the primitives with their roster and engine, and the record/interaction rules as flat bullets. Prose lines 57 -> 30; the CLI now appears at all, which it did not before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54f5915146d292f5c33a8d3eeb8afa2dd1a25ace
by plankatron <fleeky@gmail.com> · 2026-09-09T21:34:34.000Z

Changed files (1) +117 -84

README.md +117 -84
……@@ -1,16 +1,12 @@
11# favela-peer
22
3**A social feed you actually hold.** Posts are markdown files in an append-only log that lives on
4your machine. There is no server, no account, and nobody to ask.
3The favela daemon. Feeds, rooms and namespaces of markdown records over Hypercore.
4Holds the data, replicates it, and answers RPC on a unix socket.
55
6This is the daemon. It keeps your data, talks to other peers, and answers questions over a unix
7socket. You read it through `favela-surface` in a browser, or the `favela` CLI below.
6No atproto, no ActivityPub — those are downstream projections that read this.
87
98## Install
109
11Deploy is publish then install — there is no hand build, and the release carries prebuilds for
12every platform:
13
1410```sh
1511pgh publish .
1612pgh app install pgh://<registry-key>/favela-peer --name favela-peer
……@@ -23,87 +19,124 @@ favela-peer # the pgh-installed launcher
2319bare index.js # or straight from a checkout
2420```
2521
26Storage is `~/.favela`, socket `~/.favela/favela.sock` — `FAVELA_STORAGE` and `FAVELA_SOCK` move
27them. Set `FAVELA_DEBUG=1` for JSON logs. Only one daemon may hold a socket; a second refuses to
28start rather than leaving the first unreachable.
29
30## Using it
31
32`favela` is the CLI. Run it bare for the full list; these are the ones you need first:
33
34```
35favela whoami # this node's feed key
36favela link # attest this device from your identity vault
37favela claim alice # take a handle — you get e.g. alice0
38favela post "hello" # write a post
39favela follow <feedKey> # record a follow AND start holding the feed
40favela timeline # posts from every feed you hold
41favela invite # a one-time token to sign a browser in
42```
43
44It is a testing surface rather than the product surface — `favela-surface` is what a person uses —
45but everything the daemon can do is reachable from it, and it is the fastest way to see whether
46something works.
47
48## The ideas you need
49
50**Following is a record, not a subscription.** To follow someone you write a record in *your own*
51feed naming theirs. Nothing is written to theirs. They need not be online, need not consent, and
52need never know. Likes and replies work the same way. This is why a feed's writer set never grows
53past its owner's devices no matter how popular it is — and why counts are per-observer rather than
54summed. Your "12 likes" and mine are both honest and may differ.
55
56**The timeline is local, and it is a projection.** There is no relay and no aggregator. Your
57timeline is computed from the feeds this node happens to be holding, so two people with identical
58follow lists can legitimately see different timelines. A feed you follow but have not replicated
59contributes nothing — which is correct, and is why `favela missing` exists. A gap you can name is a
60different thing from a gap that is silently empty.
61
62**A post is a markdown file** — JSON frontmatter, then a body. It is mutable and edited in place,
63so the content hash changes on every edit and the name is the stable identity, never the hash.
64The bytes live content-addressed in hyper-overlay; the log holds a pointer. See `favela-markdown`.
65
66## The three primitives
67
68**Feed** — one identity's log. Its writers are that identity's devices, and its roster is flat —
69the only question is whether a writer is on it. You have exactly one.
70
71**Room** — many members, each with their own devices, and membership is the audience. Two levels
72rather than one, which matters more than it sounds: a role belongs to a person, not to a laptop.
73The vocabulary is keet's — admin, moderator, peer, read-only — because there was no reason to
74invent another. A DM is a room with one other member in it.
75
76The engine underneath is autobee, not autobase. Autobase needs `floor(N/2)+1` indexers, so at
77N=2 one member going offline freezes the view while it keeps growing, and it cannot heal by
78dropping to one. A person's two devices make N=2 the ordinary case, not an edge case. Weights
79retire that whole class of problem.
80
81**Namespace** — human names that resolve to feeds. It is a subscription, not an authority: two
82people subscribed to different namespaces resolve the same string to different feeds, and that is
83correct rather than a conflict. We give up global uniqueness and get, in exchange, a name that is
84always qualified by a namespace you can point at. Resolution runs handle → self-label → key, and
85the key is always shown.
86
87## The boundary
88
89⛔ **No `atproto-*` or `ap-*` imports. No DAG-CBOR, no MST, no DID, no signed commit.**
90
91This is a clean break from atproto and ActivityPub. Bridges come *after* the MVP and they are
92downstream — they read this, this never reads them. The moment the daemon imports the projection,
93the dependency has inverted and the recentring has failed.
94
95That is an acceptance criterion rather than an aspiration (`hyper-social` #61 §10), so
96`test/boundary.test.js` enforces it over every file in `lib/` and over the dependency list.
22Refuses to start if another daemon already holds the socket.
23
24## Config
25
26| var | default | |
27|---|---|---|
28| `FAVELA_STORAGE` | `~/.favela` | corestore + overlay + working tree |
29| `FAVELA_SOCK` | `<storage>/favela.sock` | RPC socket |
30| `FAVELA_DEBUG` | unset | set to any value for JSON logs on stdout |
31
32## CLI
33
34`favela` speaks to the daemon over `FAVELA_SOCK`. It holds no state.
35
36### identity and devices
37
38| | |
39|---|---|
40| `whoami` | this node's feed key |
41| `link [--vault <path>]` | attest THIS device from your identity vault |
42| `devices` | admitted devices, and who owns this feed |
43| `unlink <deviceKey>` | revoke a device, and everything it attested |
44| `pair` | offer this device's credential to another machine |
45| `pair --accept <invite>` | take an invite and link this machine |
46| `pair --cancel` | withdraw an outstanding invite |
47
48### handles and namespaces
49
50| | |
51|---|---|
52| `namespace` | which namespace this node resolves handles in |
53| `namespace --join <key>` | resolve handles in a different one, from now on |
54| `namespace --invite` | mint a single-use invite (admins) |
55| `namespace --request [--token T]` | ask to carry this namespace's log |
56| `namespace --withdraw <hash>` | withdraw an unspent invite |
57| `namespace --admit <writerKey>` | answer an ask (admins) |
58| `namespace --grant <rootKey>` | make an identity an admin here |
59| `namespace --revoke <deviceKey>` | retire a device, and what it carries |
60| `claim <name>` | claim a handle — you get e.g. `alice0` |
61| `rename <oldHandle> <newName>` | give one up and take another |
62| `release <handle>` | |
63| `handles [query]` | every handle in the namespace, or those matching |
64| `resolve <handle>` | handle → feed, and whether the feed agrees |
65
66### profile and browsers
67
68| | |
69|---|---|
70| `profile` | display name and picture |
71| `set-profile [--name X] [--bio "…"] [--avatar <file>]` | `--bio ""` removes |
72| `invite [--ttl <ms>]` | one-time invite for a browser |
73| `browsers` | browsers you have admitted |
74| `revoke <publicKey>` | retire a browser |
75
76### posts and social
77
78| | |
79|---|---|
80| `post <text>` | write a post |
81| `timeline [--limit N]` | posts from every feed you hold |
82| `follow <feedKey>` | record a follow AND hold the feed |
83| `unfollow <feedKey>` | |
84| `following` | who you follow |
85| `missing` | followed feeds you cannot read |
86| `unresolved` | records you hold pointers for and cannot read |
87| `like`/`unlike`/`likes` `<feed> <collection> <name>` | |
88| `reply <feed> <collection> <name> <text>` | |
89| `replies <feed> <collection> <name>` | |
90| `search <query> [--limit N]` | over what you already hold |
91| `knock <feedKey>` | ring someone's doorbell |
92| `knocks` | who rang yours |
93| `admit <feedKey>` | accept a knock — this follows them |
94| `dismiss <feedKey>` | |
95| `directory` | listings across registries you hold |
96
97`lib/rpc.js` answers 92 methods; the CLI covers the ones a human drives.
98
99## Primitives
100
101| | roster | engine |
102|---|---|---|
103| **feed** | device-flat — one identity, writers are its devices | autobase |
104| **room** | members, each with devices; membership is the audience | autobee |
105| **namespace** | subscription; not an authority | autobee |
106
107- One feed per identity.
108- Room roles are keet's: admin, moderator, peer, read-only. A role belongs to a person, not a device.
109- A DM is a room with one other member.
110- autobee, not autobase, for rooms: autobase needs `floor(N/2)+1` indexers and N=2 is the ordinary
111 case for one person's two devices.
112- Handle resolution order: handle → self-label → key. The key is always shown.
113- Two namespaces resolve the same string to different feeds. That is by design.
114
115## Records
116
117- A record is a markdown file: JSON frontmatter, then a body. See `favela-markdown`.
118- Mutable, edited in place — the name is the stable identity, the hash is not.
119- Bytes are content-addressed in hyper-overlay; the log holds a pointer.
120
121## Interactions
122
123- A like, reply or follow is a record in **your own** feed naming a target.
124- Nothing is ever written to someone else's feed, so a writer set never grows past its owner's devices.
125- Counts are per-observer, never summed.
126- The timeline is local: computed from feeds this node holds. No relay, no aggregator.
127- Identical follow lists can yield different timelines. `favela missing` names the gap.
128
129## Boundary
130
131⛔ No `atproto-*` or `ap-*` imports. No DAG-CBOR, no MST, no DID, no signed commit.
132
133`test/boundary.test.js` enforces this over every file in `lib/` and over the dependency list.
97134
98135## Status
99136
100Working, and in daily use on tabertron alongside `favela-surface` and `favela-web`.
101
102`npm test` runs 342 tests over feeds, rooms, namespaces, handles, device pairing and attestation,
103browser sessions, files and avatars, the mirror, the follow graph, search, notifications and
104replication. All passing.
137Working. 0.0.48, running on tabertron, ashatron and stortron.
105138
106Build order and the two-peer acceptance tests are in `hyper-social` #61 §10 and §11.2.
139`npm test` — 342 tests, 813 asserts, passing.
107140
108141## License
109142