fedipod 0.19.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +104 -134
  2. package/architecture.md +41 -0
  3. package/bin/fedipod.mjs +45 -2253
  4. package/browser.svg +72 -0
  5. package/cli.md +0 -4
  6. package/gateway.md +160 -0
  7. package/groups.md +0 -6
  8. package/gui.md +7 -11
  9. package/installed-agent.md +97 -0
  10. package/lib/{c2s.mjs → client/c2s.mjs} +8 -3
  11. package/lib/{localapi.mjs → client/localapi.mjs} +2 -2
  12. package/lib/client/masto/accounts.mjs +264 -0
  13. package/lib/client/masto/body.mjs +69 -0
  14. package/lib/client/masto/index.mjs +183 -0
  15. package/lib/client/masto/instance.mjs +104 -0
  16. package/lib/client/masto/media.mjs +133 -0
  17. package/lib/client/masto/oauth.mjs +599 -0
  18. package/lib/client/masto/render.mjs +459 -0
  19. package/lib/client/masto/statuses.mjs +331 -0
  20. package/lib/client/masto/timelines.mjs +316 -0
  21. package/lib/{streaming.mjs → client/streaming.mjs} +1 -1
  22. package/lib/{acctfeed.mjs → connections/acctfeed.mjs} +1 -1
  23. package/lib/{atproto.mjs → connections/atproto.mjs} +15 -16
  24. package/lib/{bskygroup.mjs → connections/bskygroup.mjs} +1 -1
  25. package/lib/{fediacct.mjs → connections/fediacct.mjs} +31 -35
  26. package/lib/{import.mjs → connections/import.mjs} +1 -1
  27. package/lib/{tagfeed.mjs → connections/tagfeed.mjs} +3 -3
  28. package/lib/connections/vault.mjs +114 -0
  29. package/lib/core/as2.mjs +124 -0
  30. package/lib/core/contexts/activitystreams.json +379 -0
  31. package/lib/core/contexts/did-v1.json +57 -0
  32. package/lib/core/contexts/fep-5711.json +36 -0
  33. package/lib/core/contexts/gotosocial.json +86 -0
  34. package/lib/core/contexts/identity-v1.json +152 -0
  35. package/lib/core/contexts/index.mjs +45 -0
  36. package/lib/core/contexts/join-lemmy.json +33 -0
  37. package/lib/core/contexts/joinmastodon.json +28 -0
  38. package/lib/core/contexts/map.json +16 -0
  39. package/lib/core/contexts/miscellany.json +19 -0
  40. package/lib/core/contexts/schemaorg.json +8845 -0
  41. package/lib/core/contexts/security-data-integrity-v1.json +78 -0
  42. package/lib/core/contexts/security-data-integrity-v2.json +81 -0
  43. package/lib/core/contexts/security-multikey-v1.json +35 -0
  44. package/lib/core/contexts/security-v1.json +74 -0
  45. package/lib/core/contexts/webfinger.json +10 -0
  46. package/lib/{deliver.mjs → core/deliver.mjs} +2 -2
  47. package/lib/core/intake/activities.mjs +437 -0
  48. package/lib/core/intake/activity.mjs +240 -0
  49. package/lib/core/intake/channel.mjs +144 -0
  50. package/lib/core/intake/group.mjs +222 -0
  51. package/lib/core/intake/index.mjs +629 -0
  52. package/lib/core/intake/notes.mjs +288 -0
  53. package/lib/core/intake/verify.mjs +141 -0
  54. package/lib/{keys.mjs → core/keys.mjs} +1 -1
  55. package/lib/core/publisher/collections.mjs +229 -0
  56. package/lib/core/publisher/index.mjs +421 -0
  57. package/lib/core/publisher/notes.mjs +188 -0
  58. package/lib/core/publisher/questions.mjs +233 -0
  59. package/lib/core/publisher/restore.mjs +196 -0
  60. package/lib/core/shapes/activitystreams.ttl +129 -0
  61. package/lib/core/shapes/index.mjs +107 -0
  62. package/lib/core/shapes/shapes-text.mjs +13 -0
  63. package/lib/{social.mjs → core/social.mjs} +2 -2
  64. package/lib/{store.mjs → core/store.mjs} +4 -0
  65. package/lib/{wire.mjs → core/wire.mjs} +2 -2
  66. package/lib/device/admin/index.mjs +13 -0
  67. package/lib/device/admin/origins.mjs +35 -0
  68. package/lib/device/admin/routes/connections.mjs +144 -0
  69. package/lib/device/admin/routes/gateway.mjs +199 -0
  70. package/lib/device/admin/routes/lifecycle.mjs +191 -0
  71. package/lib/device/admin/routes/owner.mjs +322 -0
  72. package/lib/device/admin/routes/setup.mjs +393 -0
  73. package/lib/device/admin/routes/social.mjs +188 -0
  74. package/lib/device/admin/server.mjs +95 -0
  75. package/lib/device/admin/static.mjs +244 -0
  76. package/lib/device/admin/surface.mjs +274 -0
  77. package/lib/device/cli/commands/account.mjs +586 -0
  78. package/lib/device/cli/commands/run.mjs +278 -0
  79. package/lib/device/cli/commands/service.mjs +221 -0
  80. package/lib/device/cli/commands/setup.mjs +410 -0
  81. package/lib/device/cli/commands/state.mjs +559 -0
  82. package/lib/device/cli/context.mjs +288 -0
  83. package/lib/{migrate.mjs → device/migrate.mjs} +1 -1
  84. package/lib/{remote.mjs → device/remote.mjs} +3 -3
  85. package/lib/{setup.mjs → device/setup.mjs} +3 -3
  86. package/lib/{update.mjs → device/update.mjs} +1 -1
  87. package/lib/{directory.mjs → gateway/directory.mjs} +1 -1
  88. package/lib/{front-core.mjs → gateway/front-core.mjs} +3 -3
  89. package/lib/{gateway-core.mjs → gateway/gateway-core.mjs} +1 -1
  90. package/lib/{httpsig.mjs → gateway/httpsig.mjs} +1 -1
  91. package/lib/{embed.mjs → server/embed.mjs} +131 -22
  92. package/lib/{links.mjs → shared/links.mjs} +1 -1
  93. package/lib/{ua.mjs → shared/ua.mjs} +1 -1
  94. package/package.json +16 -2
  95. package/run-agent.mjs +33 -25
  96. package/scripts/build-app.mjs +4 -0
  97. package/scripts/check-pod-calls.mjs +8 -2
  98. package/scripts/refresh-contexts.mjs +39 -0
  99. package/web/admin/actors.js +145 -0
  100. package/web/admin/common.js +23 -0
  101. package/web/admin/connections.js +112 -0
  102. package/web/admin/gateway.js +111 -0
  103. package/web/admin/group.js +258 -0
  104. package/web/admin/index.html +7 -1
  105. package/web/admin/record.js +378 -0
  106. package/web/admin/setup/index.html +1 -0
  107. package/web/admin/setup/setup.js +2 -13
  108. package/web/admin/upkeep.js +170 -0
  109. package/web/app/README.md +6 -6
  110. package/web/app/admin-facade.mjs +3 -3
  111. package/web/app/agent.mjs +12 -12
  112. package/web/app/atproto-browser.mjs +1 -1
  113. package/web/app/deliver-relay.mjs +1 -1
  114. package/web/app/dist/sw.js +21684 -5415
  115. package/web/app/dist/sw.js.map +4 -4
  116. package/web/app/fediacct-browser.mjs +1 -1
  117. package/web/app/shims/shapes-text.mjs +8 -0
  118. package/web/app/site/admin/actors.js +145 -0
  119. package/web/app/site/admin/common.js +23 -0
  120. package/web/app/site/admin/connections.js +112 -0
  121. package/web/app/site/admin/gateway.js +111 -0
  122. package/web/app/site/admin/group.js +258 -0
  123. package/web/app/site/admin/index.html +7 -1
  124. package/web/app/site/admin/record.js +378 -0
  125. package/web/app/site/admin/setup/index.html +1 -0
  126. package/web/app/site/admin/setup/setup.js +2 -13
  127. package/web/app/site/admin/upkeep.js +170 -0
  128. package/web/app/site/sw.js +21684 -5415
  129. package/web/app/sw-src.mjs +17 -2
  130. package/lib/admin.mjs +0 -1913
  131. package/lib/intake.mjs +0 -1981
  132. package/lib/mastoapi.mjs +0 -2284
  133. package/lib/publisher.mjs +0 -1192
  134. package/web/admin/admin.js +0 -1181
  135. package/web/app/site/admin/admin.js +0 -1181
  136. /package/lib/{oidc-auth.mjs → client/oidc-auth.mjs} +0 -0
  137. /package/lib/{webpush.mjs → client/webpush.mjs} +0 -0
  138. /package/lib/{bskyfeed.mjs → connections/bskyfeed.mjs} +0 -0
  139. /package/lib/{lease.mjs → core/lease.mjs} +0 -0
  140. /package/lib/{polls.mjs → core/polls.mjs} +0 -0
  141. /package/lib/{proof.mjs → core/proof.mjs} +0 -0
  142. /package/lib/{storage.mjs → core/storage.mjs} +0 -0
  143. /package/lib/{account.mjs → device/account.mjs} +0 -0
  144. /package/lib/{certs.mjs → device/certs.mjs} +0 -0
  145. /package/lib/{export-collections.mjs → device/export-collections.mjs} +0 -0
  146. /package/lib/{home.mjs → device/home.mjs} +0 -0
  147. /package/lib/{ports.mjs → device/ports.mjs} +0 -0
  148. /package/lib/{guard.mjs → shared/guard.mjs} +0 -0
  149. /package/lib/{safefetch.mjs → shared/safefetch.mjs} +0 -0
package/browser.svg ADDED
@@ -0,0 +1,72 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 860 486" font-family="Helvetica, Arial, sans-serif">
2
+ <defs>
3
+ <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
4
+ <path d="M0,0 L10,5 L0,10 z" fill="#333"/>
5
+ </marker>
6
+ </defs>
7
+ <rect x="0" y="0" width="860" height="486" fill="#ffffff"/>
8
+
9
+ <!-- boxes (draw.io palette) -->
10
+ <g stroke-width="1.5">
11
+ <rect x="315" y="24" width="230" height="72" rx="6" fill="#dae8fc" stroke="#6c8ebf"/>
12
+ <rect x="315" y="190" width="230" height="68" rx="6" fill="#e1d5e7" stroke="#9673a6"/>
13
+ <rect x="40" y="372" width="210" height="70" rx="6" fill="#f8cecc" stroke="#b85450"/>
14
+ <rect x="580" y="360" width="250" height="88" rx="6" fill="#d5e8d4" stroke="#82b366"/>
15
+ </g>
16
+
17
+ <!-- box labels -->
18
+ <g fill="#111" text-anchor="middle">
19
+ <text x="430" y="56" font-size="17" font-weight="bold">Open Social Web</text>
20
+ <text x="430" y="80" font-size="13">Mastodon · Pixelfed · Bluesky …</text>
21
+
22
+ <text x="430" y="220" font-size="17" font-weight="bold">FediPod Gateway</text>
23
+ <text x="430" y="242" font-size="13">the mail door &amp; the relay</text>
24
+
25
+ <text x="145" y="391" font-size="17" font-weight="bold">Your Pod</text>
26
+ <text x="145" y="409" font-size="13">AP S2S formatted data</text>
27
+ <text x="145" y="426" font-size="13">in addition to other pod data</text>
28
+
29
+ <text x="705" y="392" font-size="17" font-weight="bold">Any Browser</text>
30
+ <text x="705" y="414" font-size="13">runs your FediPod Agent,</text>
31
+ <text x="705" y="431" font-size="13">a Mastodon API client</text>
32
+ </g>
33
+
34
+ <!-- arrows -->
35
+ <g stroke="#333" stroke-width="1.6" fill="none">
36
+ <!-- 1: Open Social Web <-> Gateway (AP S2S mail) -->
37
+ <line x1="430" y1="96" x2="430" y2="190" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
38
+ <!-- 2: Your Pod -> Open Social Web (UNCHANGED, pod answers lookups) -->
39
+ <path d="M110,372 C46,300 46,150 322,92" marker-end="url(#arrow)"/>
40
+ <!-- 3: Gateway -> Your Pod (one way now) -->
41
+ <path d="M338,258 C300,300 250,330 205,372" marker-end="url(#arrow)"/>
42
+ <!-- 4: Gateway <-> Any Browser (agent loaded; signed posts sent) -->
43
+ <path d="M632,360 C560,320 522,300 472,258" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
44
+ <!-- 5: Your Pod <-> Any Browser (Solid) -->
45
+ <line x1="250" y1="408" x2="580" y2="408" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
46
+ <!-- 6: Any Browser <-> Open Social Web (ATProto client — Bluesky) -->
47
+ <path d="M760,360 C824,300 824,150 538,92" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
48
+ </g>
49
+
50
+ <!-- arrow labels -->
51
+ <g fill="#333" font-size="13">
52
+ <text x="418" y="134" text-anchor="end">raw mail in</text>
53
+ <text x="418" y="150" text-anchor="end">relayed posts out</text>
54
+ <text x="442" y="142" text-anchor="start">AP S2S Protocol</text>
55
+
56
+ <text x="96" y="250" text-anchor="start">Pod responds to requests for</text>
57
+ <text x="96" y="266" text-anchor="start">webfinger, actor, public posts</text>
58
+
59
+ <text x="300" y="322" text-anchor="start">verified mail in</text>
60
+
61
+ <text x="588" y="302" text-anchor="middle">FediPod Agent loaded in browser;</text>
62
+ <text x="588" y="318" text-anchor="middle">signed posts sent to gateway</text>
63
+
64
+ <text x="430" y="400" text-anchor="middle" font-weight="bold" fill="#111">Solid Protocol</text>
65
+ <text x="430" y="428" text-anchor="middle">reads &amp; writes your pod</text>
66
+
67
+ <text x="780" y="238" text-anchor="end">ATProto</text>
68
+ <text x="780" y="254" text-anchor="end">client protocol</text>
69
+ </g>
70
+
71
+ <text x="830" y="474" text-anchor="end" font-size="11" fill="#888">© 2026 Jeff Zucker · CC BY-SA 4.0</text>
72
+ </svg>
package/cli.md CHANGED
@@ -70,9 +70,6 @@ fedipod revoke-credential --email EMAIL # cut this machine off from the pod acc
70
70
  ```
71
71
  A new password takes effect when a running agent restarts.
72
72
 
73
- <!-- CLAUDE 2026-09-09 — `passwd` stopped being optional for one case, and a
74
- user who hits it will want to know why here. First draft in your voice;
75
- delete these markers when done. -->
76
73
  **A client hosted somewhere else now needs one.** A client running on its own
77
74
  site (Elk, Phanpy on someone's own domain) asks the agent to send the sign-in
78
75
  result back to that site. Without a password there is nothing to approve that
@@ -81,7 +78,6 @@ to have open could ask for the same thing and be given a key to your account.
81
78
  So: no password, no sending credentials off this machine. Clients that live on
82
79
  the agent itself (the bundled one, a dist in `ui/`, anything on
83
80
  `https://localhost:<port>`) are unaffected and need no password.
84
- <!-- /CLAUDE -->
85
81
 
86
82
  ## Bluesky
87
83
 
package/gateway.md ADDED
@@ -0,0 +1,160 @@
1
+ # The gateway
2
+
3
+ Most of what a Fediverse inbox receives is broadcast noise. A gateway is an
4
+ always-on, internet-facing door that stands in front of your pod: it checks
5
+ each delivery's signature where the headers still exist, drops forgeries and
6
+ junk before they ever touch your pod, and passes the rest on with a receipt
7
+ saying it checked.
8
+
9
+ Your name, your signing key and your data stay on your own pod. The gateway is
10
+ **keyless** — it never holds the key you sign with, so it cannot post as you,
11
+ read your private things, or be you anywhere. The worst a broken one can do is
12
+ push items into your inbox, and those still face your agent's own checks.
13
+
14
+ A FediPod install works without any gateway at all. Deliveries go straight to
15
+ your pod inbox, which holds them whether your agent is running or not.
16
+
17
+ ## Attaching your pod to one
18
+
19
+ There is a gateway at [fedipod.net](https://fedipod.net/). Attaching happens
20
+ from your own agent, which proves the pod with its own credential — no
21
+ password is typed anywhere:
22
+
23
+ 1. Open your agent's admin page and, in the **Gateway** panel, give the
24
+ gateway's address and the name you want there. The panel checks the name
25
+ is free as you type.
26
+ 2. Choose a pod-based name (`@you@your.pod`) or a gateway-based name
27
+ (`@you@the-gateway`). The gateway account is created automatically either
28
+ way. A pod-based attach applies immediately; taking a gateway-based name
29
+ restarts the agent itself to publish under it.
30
+
31
+ The same attach from the command line, against the running agent:
32
+
33
+ ```
34
+ fedipod gateway --attach https://fedipod.net --name yourname
35
+ ```
36
+
37
+ `--name` defaults to your handle; add `--fronted` for a gateway-based name.
38
+
39
+ To undo it:
40
+
41
+ ```
42
+ fedipod gateway --detach
43
+ ```
44
+
45
+ That republishes your actor with your pod's own inbox. Nothing else moves.
46
+ Detach talks to the running agent, so start it first. For a fronted identity,
47
+ detaching also moves every published id back to the pod — a rename other
48
+ servers see, not just a mail change.
49
+
50
+ ## Easing into it
51
+
52
+ Attaching does not have to change how deliveries are treated on day one. The
53
+ mode says how far you trust the door, and every step is reversible:
54
+
55
+ - **off** — configured but not advertised; nothing changes on the wire. This
56
+ is where a by-hand configure starts, and the step back short of forgetting
57
+ the gateway entirely.
58
+ - **shadow** — your actor advertises the gateway and the agent measures how
59
+ much real traffic verifies.
60
+ - **trust** — verified follows are accepted without review.
61
+ - **locked** — your inbox accepts writes only from the gateway.
62
+
63
+ In every mode past **off**, the door itself is already filtering: blocked
64
+ actors, content that does not concern you, and forged signatures are dropped
65
+ at the door and never reach the pod. The mode says only how far the agent
66
+ believes the door's receipts.
67
+
68
+ The shadow numbers come from the agent:
69
+
70
+ ```
71
+ curl -k https://localhost:8030/gateway
72
+ ```
73
+
74
+ which reports the mode and the verified and unverified counts.
75
+
76
+ Set it from the agent's own API:
77
+
78
+ ```
79
+ curl -k -X POST https://localhost:8030/gateway -H 'content-type: application/json' \
80
+ -d '{"action":"mode","mode":"shadow"}'
81
+ ```
82
+
83
+ 8030 is the default identity's port; `fedipod status`
84
+ prints the right one for each identity.
85
+
86
+ ## The receipt secret
87
+
88
+ The gateway stamps each verification receipt with a secret shared between it
89
+ and your agent, and your agent believes a receipt only when the stamp checks
90
+ out. That is what stops somebody dropping a forged "verified" receipt beside a
91
+ forged delivery.
92
+
93
+ You never fetch this secret from anywhere. When you run your own gateway,
94
+ your agent mints it and shows it to you once; you carry it to the gateway
95
+ yourself. When you attach to a multi-user gateway, the direction is reversed:
96
+ the gateway mints the secret and answers the attach with it, and your agent
97
+ records it.
98
+
99
+ ## What the gateway can see
100
+
101
+ It reads only public data to decide what concerns you: your published
102
+ followers and following, and a small public policy document your agent writes
103
+ with a mirror of your blocklist. Nothing private leaves your pod. Publishing
104
+ that mirror does make your blocklist public, which is part of the bargain of
105
+ running behind a door.
106
+
107
+ ## Running a gateway
108
+
109
+ Any always-on box will do — a VPS, a home server behind a tunnel, a serverless
110
+ host. The logic is plain Node in `lib/gateway/gateway-core.mjs`, and a host needs only
111
+ a thin adapter that calls `handleDelivery`.
112
+
113
+ Two ways are ready to use:
114
+
115
+ - **On Netlify**, with the adapter in `netlify/functions/inbox.mjs`. See
116
+ [netlify/README.md](netlify/README.md) for the deployment specifics.
117
+ - **On any box of your own**, with your own adapter around the same core.
118
+ - **Inside a Community Solid Server**, as the same door run in-process by the
119
+ CSS component — see [packages/fedipod-server](packages/fedipod-server/README.md).
120
+
121
+ ### Offering accounts to other people
122
+
123
+ A gateway can also be a front for many people, so a host can offer
124
+ `@name@their-host` addresses. Each user keeps their own pod, their own agent
125
+ and their own signing key; the front answers WebFinger for all of them, serves
126
+ each public face by rewriting that user's pod ids onto the shared domain, and
127
+ routes every verified delivery into the right pod.
128
+
129
+ A fronted identity is the gateway-based name choice in the admin page's
130
+ Gateway panel, or:
131
+
132
+ ```
133
+ fedipod gateway --attach <front-origin> --name <name> --fronted
134
+ ```
135
+
136
+ Choose at attach time: changing an existing identity's front later renames
137
+ every published id, and the attach refuses it.
138
+
139
+ **A front is only a doorway.** It never hosts pods and never dictates where
140
+ they live. A host who also wants to offer pods to people who have none runs a
141
+ pod server separately, with the duties that carries — and users may always
142
+ bring a pod of their own instead.
143
+
144
+ ## Setting one up by hand
145
+
146
+ If you are not using a signup page:
147
+
148
+ 1. Deploy the door to your box.
149
+ 2. Give it Append on your inbox, using a dedicated low-privilege pod account —
150
+ never your owner credential. FediPod's default inbox is public-Append, so
151
+ this is optional: with no credential the door writes with a plain PUT.
152
+ 3. Point your agent at it, which mints the shared secret and returns it once:
153
+
154
+ ```
155
+ curl -k -X POST https://localhost:8030/gateway -H 'content-type: application/json' \
156
+ -d '{"action":"configure","url":"<door-inbox-url>","webId":"<door-webid>"}'
157
+ ```
158
+
159
+ Copy that secret into the door's environment.
160
+ 4. Walk the modes above, starting at shadow.
package/groups.md CHANGED
@@ -14,16 +14,12 @@ and pick group rather than person, or take the group path on a signup page.
14
14
  You can turn on join review as you create it. The group's pod must be the root
15
15
  of its own host, so its handle resolves.
16
16
 
17
- <!-- CLAUDE 2026-09-09 — nothing anywhere said this, and a browser user who
18
- went looking for the group option would just not have found it. First
19
- draft in your voice; move or reword as you like. Delete markers when done. -->
20
17
  **Running a group needs the installed agent.** The in-browser build makes
21
18
  personal identities only — the sign-up wizard has no group option, and the
22
19
  moderation surface (join review, members, muting, the moderation queue) is not
23
20
  part of that build. You can *join* a group from the browser exactly as from
24
21
  anywhere else: joining is following, and that works everywhere. It is hosting
25
22
  one that needs an install.
26
- <!-- /CLAUDE -->
27
23
 
28
24
  Being in a group also connects you to the people in it: posts from fellow
29
25
  members reach your timeline even when you do not follow them individually,
@@ -73,8 +69,6 @@ boosting the author.
73
69
 
74
70
  A group can be handed on rather than abandoned using the `Transfer this account away` button. This tells every follower to migrate, so the membership survives a change of host.
75
71
 
76
-
77
-
78
72
  ## Bluesky members
79
73
 
80
74
  A group with a connected Bluesky account is joinable from Bluesky: following
package/gui.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # The Admin interface
2
2
 
3
+ In the browser version at fedipod.net, `manage account` in the bar opens this
4
+ same page for your account. The rest of this page describes it as the
5
+ installed agent serves it; the controls are the same, minus the manual inbox
6
+ drain and the local log, which a browser does not have.
7
+
3
8
  Open `https://localhost:8030/` while any agent is running — it forwards you to the agent — then choose `manage account` and select the actor you want from the local actors dropdown.
4
9
  Picking an actor marked "(stopped)" starts its agent, then opens its page.
5
10
 
@@ -92,14 +97,9 @@ is marked **sign in again** rather than quietly dropped.
92
97
 
93
98
  New followers appear under **Follow requests** with **Accept** and **Refuse**
94
99
  beside them; nothing is accepted without you.
95
- <!-- CLAUDE 2026-09-09 — these were visible on this page and NOWHERE else
96
- until today: the client API stubbed the list empty and offered no way to
97
- answer one, so no Mastodon client could show them. Delete markers when
98
- read. -->
99
100
  They show in any Mastodon client as well, now that the client API serves the
100
- queue and takes both answers — before this they were visible on this page
101
- alone.
102
- <!-- /CLAUDE --> Groups are different — joining
101
+ queue and takes both answers.
102
+ Groups are different — joining
103
103
  follows the group's own moderation settings; see [Groups](groups.md).
104
104
 
105
105
  **Accept all** answers the whole queue at once, and the identity pane's
@@ -131,12 +131,8 @@ account*); your followers arrive by themselves. Removing an alias asks
131
131
  twice — servers still processing the move check it while they retry. The CSV
132
132
  files from the old server's export are imported with the CLI; see
133
133
  [CLI admin](cli.md).
134
- <!-- CLAUDE 2026-09-09 — the browser build runs the same importer now, so
135
- "with the CLI" is no longer the whole story. Reword as you like; delete
136
- these markers when done. -->
137
134
  The in-browser build runs the same importer, so a CSV can be handed to it
138
135
  there too.
139
- <!-- /CLAUDE -->
140
136
 
141
137
  ## Protecting & recovering your data
142
138
 
@@ -0,0 +1,97 @@
1
+ # The installed agent
2
+
3
+ FediPod can also run as a program on your own machine, in front of the same
4
+ kind of pod. It does everything the browser version at fedipod.net does, plus
5
+ what a browser tab cannot: it keeps running while no tab is open, so scheduled
6
+ posts go out and push notifications reach you; it serves the Mastodon streaming
7
+ API, so clients update live; any Mastodon client, phone app or desktop, can
8
+ connect to it; and it can host a [group](groups.md).
9
+
10
+ ## Requirements
11
+
12
+ - Node 20 or newer.
13
+ - A Solid pod with a host name of its own, such as
14
+ `https://alice.solidcommunity.net/`. A pod on a path of a shared host cannot
15
+ be a Fediverse address.
16
+ - Followers-only and direct posts need a pod that enforces WAC access control;
17
+ on one that does not, the composer refuses those two and says why.
18
+ - While the agent is off, your mail waits on your pod's host. Run it as a
19
+ service, or attach to a gateway, so it does not pile up there.
20
+
21
+ ## Installing
22
+
23
+ ```
24
+ npm install -g fedipod
25
+ ```
26
+
27
+ ## Running
28
+
29
+ Run `fedipod start`. Add a port to change the local agent's port, for example
30
+ `fedipod start --port 8081`; the default is 8030. Then point any browser at
31
+ `https://localhost:8030`, or the port you chose, and the setup pages take it
32
+ from there.
33
+
34
+ ## Running as a service
35
+
36
+ ```
37
+ fedipod install-service
38
+ ```
39
+
40
+ It registers every identity on this machine, one service each, so all of your
41
+ actors start at boot. An identity running in a terminal is stopped and taken
42
+ over by its service. `fedipod uninstall-service` reverses it.
43
+
44
+ ## Managing
45
+
46
+ Posts, logs, parking, moving, transferring and the rest are on the
47
+ [admin interface](gui.md); starting, stopping and what a page cannot do are in
48
+ [CLI admin](cli.md). The admin tools also create other actors, groups or
49
+ persons. You may have as many as you want on one machine, each with a pod of
50
+ its own.
51
+
52
+ Every agent checks once a day whether a newer FediPod is published. When one
53
+ exists, the record page offers **Update**, and `fedipod update` does the same
54
+ from the terminal. `AP_UPDATE_CHECK=0` turns the check off.
55
+
56
+ ## A gateway account
57
+
58
+ Most of what a Fediverse inbox receives is broadcast noise. A
59
+ [gateway](gateway.md) is a shared, always-on door that verifies each delivery,
60
+ drops the junk, and passes the rest to your pod, while your key and data stay
61
+ on your pod. There is a free one at [fedipod.net](https://fedipod.net/).
62
+ Attaching or detaching is a few wizard-guided clicks from your agent, and it
63
+ takes the mail load off your pod's host.
64
+
65
+ ## Clients
66
+
67
+ The bundled client is [Phanpy](https://github.com/cheeaun/phanpy) (MIT, by
68
+ Chee Aun), served by the agent itself, and logging in is one click. If you
69
+ ever enter the instance by hand, use the address on the record's **local
70
+ host** row.
71
+
72
+ - **Other web clients**: drop any static Mastodon client dist into
73
+ `ui/<name>/` and it is served at `/<name>/`; see `ui/README.md`.
74
+ - **Desktop and phone clients** (Tuba, Whalebird, and the like): add
75
+ `https://localhost:8030`, or your agent's port, as a custom instance.
76
+ - **Streaming**: the agent serves the Mastodon streaming API at
77
+ `/api/v1/streaming`, so clients update live instead of polling.
78
+ - **Web push**: notifications reach you while the client is closed.
79
+ - **Scheduled posts** go out at the time you picked.
80
+
81
+ Polls, content warnings, editing, all four visibility levels, direct
82
+ messages, bookmarks, favourites, lists, keyword filters, pinned posts,
83
+ blocking and muting, and custom emojis work as in the browser version.
84
+
85
+ ## Bluesky, and your other Fediverse accounts
86
+
87
+ A Bluesky connection lets the agent drive an existing Bluesky, or other
88
+ ATProto, account alongside your Fediverse identity: public posts are
89
+ cross-posted as a mirror, with a toggle to turn it off; Bluesky replies and
90
+ activity flow into your timeline; you can like, boost and reply to Bluesky
91
+ posts. Direct messages to Bluesky are not supported.
92
+
93
+ An account on Mastodon or any server speaking the Mastodon API can be
94
+ connected from the **Other identities** row of the admin page. Its home
95
+ timeline and notifications join your feed, a post both accounts see appears
96
+ once, and favouriting, boosting and replying act as the account the post came
97
+ through. The token it hands back stays on this machine.
@@ -15,8 +15,9 @@
15
15
  // read their own inbox" is served from there, by this agent, to the owner
16
16
  // alone.
17
17
 
18
- import * as social from './social.mjs';
19
- import * as wire from './wire.mjs';
18
+ import * as social from '../core/social.mjs';
19
+ import * as wire from '../core/wire.mjs';
20
+ import { readLenient } from '../core/as2.mjs';
20
21
 
21
22
  const MAX_BODY = 512 * 1024; // same ceiling the inbox drain enforces
22
23
 
@@ -190,9 +191,13 @@ export class C2S {
190
191
  if (!took) return this.send(res, 503, { error: 'another agent is active for this pod — takeover failed, try again' });
191
192
  }
192
193
 
194
+ // Read as JSON-LD, so a client may send its activity with whatever context
195
+ // it likes and still be understood. One we cannot read that way is read as
196
+ // plain JSON rather than refused, which is what a client sending ordinary
197
+ // ActivityStreams has always got.
193
198
  let activity;
194
199
  try {
195
- activity = JSON.parse(await readBody(req));
200
+ activity = (await readLenient(await readBody(req))).doc;
196
201
  } catch (e) {
197
202
  return this.send(res, 400, { error: `unreadable body: ${e.message}` });
198
203
  }
@@ -12,8 +12,8 @@
12
12
  import https from 'node:https';
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
- import { rootOf, apRoot } from './home.mjs';
16
- import { certPaths } from './certs.mjs';
15
+ import { rootOf, apRoot } from '../device/home.mjs';
16
+ import { certPaths } from '../device/certs.mjs';
17
17
 
18
18
  /**
19
19
  * The authorities worth offering for a loopback call: this install's, and the