fedbox 0.0.1 → 0.0.4

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.
package/QUICKSTART.md ADDED
@@ -0,0 +1,90 @@
1
+ # Fedbox Quickstart
2
+
3
+ Clean slate setup with ngrok federation.
4
+
5
+ ## 1. Clean existing data
6
+
7
+ ```bash
8
+ # Remove database only
9
+ fedbox clean
10
+
11
+ # Remove everything (database + config + keys)
12
+ fedbox clean --all
13
+ ```
14
+
15
+ ## 2. Initialize
16
+
17
+ ```bash
18
+ fedbox init
19
+ ```
20
+
21
+ Prompts for: username, display name, bio, port (default 3000).
22
+
23
+ Creates `fedbox.json` with generated keypair.
24
+
25
+ ## 3. Start server
26
+
27
+ ```bash
28
+ fedbox start
29
+ ```
30
+
31
+ Server runs at `http://localhost:3000/{username}`
32
+
33
+ ## 4. Expose with ngrok
34
+
35
+ In another terminal:
36
+
37
+ ```bash
38
+ ngrok http 3000
39
+ ```
40
+
41
+ Copy the https URL (e.g., `https://abc123.ngrok-free.app`)
42
+
43
+ ## 5. Configure domain
44
+
45
+ Edit `fedbox.json`, add domain (without https://):
46
+
47
+ ```json
48
+ {
49
+ "domain": "abc123.ngrok-free.app",
50
+ ...
51
+ }
52
+ ```
53
+
54
+ Restart server (`Ctrl+C`, then `fedbox start`).
55
+
56
+ ## 6. Test federation
57
+
58
+ From Mastodon, search for `@{username}@{domain}`
59
+
60
+ ## 7. Post something
61
+
62
+ ```bash
63
+ fedbox post "Hello, Fediverse!"
64
+ ```
65
+
66
+ ## URI Structure (Solid-compatible)
67
+
68
+ | URI | Purpose |
69
+ |-----|---------|
70
+ | `/{username}` | Profile (HTML + JSON-LD) |
71
+ | `/{username}#me` | WebID (Actor ID) |
72
+ | `/{username}/inbox` | Inbox |
73
+ | `/{username}/outbox` | Outbox |
74
+ | `/{username}/posts/{id}` | Individual post |
75
+ | `/{username}#main-key` | Public key |
76
+
77
+ ## All Commands
78
+
79
+ ```
80
+ fedbox init # Setup identity
81
+ fedbox start # Start server
82
+ fedbox status # Show config
83
+ fedbox post "text" # Post to followers
84
+ fedbox follow @user@dom # Follow someone
85
+ fedbox timeline # View feed
86
+ fedbox reply <url> "text"# Reply to post
87
+ fedbox posts # View own posts
88
+ fedbox clean # Remove database
89
+ fedbox clean --all # Remove everything
90
+ ```
package/README.md CHANGED
@@ -15,10 +15,32 @@ fedbox init
15
15
 
16
16
  # Start your server
17
17
  fedbox start
18
+
19
+ # Post something!
20
+ fedbox post "Hello, Fediverse!"
18
21
  ```
19
22
 
20
23
  That's it. You're on the Fediverse.
21
24
 
25
+ ## Commands
26
+
27
+ ```bash
28
+ # Setup
29
+ fedbox init # Set up your identity
30
+ fedbox start # Start the server
31
+ fedbox status # Show your profile info
32
+
33
+ # Social
34
+ fedbox post "text" # Post a message to followers
35
+ fedbox follow @user@domain # Follow someone
36
+ fedbox timeline # View posts from people you follow
37
+ fedbox reply <url> "text" # Reply to a post
38
+ fedbox posts # View your own posts
39
+
40
+ # Help
41
+ fedbox help # Show all commands
42
+ ```
43
+
22
44
  ## Federation (so Mastodon can find you)
23
45
 
24
46
  To federate with the wider Fediverse, you need a public HTTPS URL. The easiest way:
@@ -42,28 +64,25 @@ Restart your server, and you're federated! Search for `@yourname@abc123.ngrok.io
42
64
  ## What You Get
43
65
 
44
66
  - **Your own identity** — `@you@yourdomain.com`
67
+ - **Post from CLI** — `fedbox post "Hello world"`
68
+ - **Follow anyone** — `fedbox follow @user@mastodon.social`
69
+ - **View timeline** — `fedbox timeline`
70
+ - **Reply to posts** — `fedbox reply <url> "Nice!"`
45
71
  - **ActivityPub compatible** — Works with Mastodon, Pleroma, Pixelfed, etc.
46
- - **Persistent storage** — SQLite database for followers, posts, activities
47
- - **Beautiful profile page** — Dark theme, looks great
48
- - **Zero config** — Just answer a few questions
49
-
50
- ## Commands
51
-
52
- ```bash
53
- fedbox init # Set up your identity
54
- fedbox start # Start the server
55
- fedbox status # Show current config
56
- fedbox help # Show help
57
- ```
72
+ - **HTTP Signature verification** — Secure federation
73
+ - **Rate limiting** — Protection against abuse
74
+ - **Persistent storage** — SQLite database
75
+ - **Beautiful profile page** — Dark theme, shows your posts
58
76
 
59
77
  ## How It Works
60
78
 
61
79
  Fedbox uses [microfed](https://github.com/micro-fed/microfed.org) for ActivityPub primitives:
62
80
 
63
81
  - **Profile** — Your actor/identity
64
- - **Inbox** — Receive follows, likes, boosts
82
+ - **Inbox** — Receive follows, likes, boosts, posts
65
83
  - **Outbox** — Your posts
66
84
  - **WebFinger** — So others can find you
85
+ - **HTTP Signatures** — Secure signed requests
67
86
 
68
87
  Data is stored in SQLite (`data/fedbox.db`).
69
88
 
package/bin/cli.js CHANGED
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Pubcrawl CLI
4
+ * Fedbox CLI
5
5
  * Zero to Fediverse in 60 seconds
6
6
  */
7
7
 
8
8
  import { createInterface } from 'readline'
9
- import { existsSync, writeFileSync, mkdirSync } from 'fs'
10
- import { join } from 'path'
9
+ import { existsSync, writeFileSync, mkdirSync, readFileSync, unlinkSync, rmSync } from 'fs'
11
10
  import { generateKeypair } from 'microfed/auth'
12
11
 
13
12
  const rl = createInterface({
@@ -30,6 +29,12 @@ const COMMANDS = {
30
29
  init: runInit,
31
30
  start: runStart,
32
31
  status: runStatus,
32
+ post: runPost,
33
+ follow: runFollow,
34
+ timeline: runTimeline,
35
+ reply: runReply,
36
+ posts: runPosts,
37
+ clean: runClean,
33
38
  help: runHelp
34
39
  }
35
40
 
@@ -49,7 +54,6 @@ async function main() {
49
54
  async function runInit() {
50
55
  console.log(BANNER)
51
56
 
52
- // Check if already initialized
53
57
  if (existsSync('fedbox.json')) {
54
58
  console.log('⚠️ Already initialized. Delete fedbox.json to start over.\n')
55
59
  process.exit(1)
@@ -57,7 +61,6 @@ async function runInit() {
57
61
 
58
62
  console.log('Let\'s get you on the Fediverse!\n')
59
63
 
60
- // Gather info
61
64
  const username = await ask('👤 Username (e.g., alice): ')
62
65
  const displayName = await ask('📛 Display name (e.g., Alice): ') || username
63
66
  const summary = await ask('📝 Bio (optional): ') || ''
@@ -66,7 +69,6 @@ async function runInit() {
66
69
  console.log('\n🔐 Generating keypair...')
67
70
  const { publicKey, privateKey } = generateKeypair()
68
71
 
69
- // Create config
70
72
  const config = {
71
73
  username: username.toLowerCase().replace(/[^a-z0-9]/g, ''),
72
74
  displayName,
@@ -77,12 +79,10 @@ async function runInit() {
77
79
  createdAt: new Date().toISOString()
78
80
  }
79
81
 
80
- // Create data directory
81
82
  if (!existsSync('data')) {
82
83
  mkdirSync('data')
83
84
  }
84
85
 
85
- // Save config
86
86
  writeFileSync('fedbox.json', JSON.stringify(config, null, 2))
87
87
  console.log('✅ Config saved to fedbox.json')
88
88
 
@@ -118,7 +118,6 @@ async function runStart() {
118
118
 
119
119
  console.log('🚀 Starting server...\n')
120
120
 
121
- // Dynamic import to avoid loading before init
122
121
  const { startServer } = await import('../lib/server.js')
123
122
  await startServer()
124
123
  }
@@ -129,39 +128,269 @@ async function runStatus() {
129
128
  process.exit(1)
130
129
  }
131
130
 
132
- const config = JSON.parse(await import('fs').then(fs =>
133
- fs.readFileSync('fedbox.json', 'utf8')
134
- ))
131
+ const config = JSON.parse(readFileSync('fedbox.json', 'utf8'))
132
+
133
+ // Get follower/following counts
134
+ let followers = 0, following = 0
135
+ try {
136
+ const { initStore, getFollowerCount, getFollowingCount } = await import('../lib/store.js')
137
+ initStore()
138
+ followers = getFollowerCount()
139
+ following = getFollowingCount()
140
+ } catch {}
135
141
 
136
142
  console.log(`
137
143
  ╔═══════════════════════════════════════════╗
138
144
  ║ 📊 FEDBOX STATUS ║
139
145
  ╚═══════════════════════════════════════════╝
140
146
 
141
- Username: @${config.username}
142
- Name: ${config.displayName}
143
- Port: ${config.port}
144
- Domain: ${config.domain || '(not set - run with ngrok)'}
145
- Created: ${config.createdAt}
147
+ Username: @${config.username}
148
+ Name: ${config.displayName}
149
+ Port: ${config.port}
150
+ Domain: ${config.domain || '(not set - run with ngrok)'}
151
+ Followers: ${followers}
152
+ Following: ${following}
153
+ Created: ${config.createdAt}
154
+ `)
155
+
156
+ rl.close()
157
+ }
158
+
159
+ async function runPost() {
160
+ if (!existsSync('fedbox.json')) {
161
+ console.log('❌ Not initialized. Run: fedbox init\n')
162
+ process.exit(1)
163
+ }
164
+
165
+ const content = process.argv[3]
166
+ if (!content) {
167
+ console.log('Usage: fedbox post "Your message here"')
168
+ process.exit(1)
169
+ }
170
+
171
+ const { post } = await import('../lib/actions.js')
172
+
173
+ console.log('📝 Creating post...')
174
+ const result = await post(content)
175
+
176
+ console.log(`
177
+ ✅ Posted!
178
+
179
+ ID: ${result.noteId}
180
+ Content: ${content}
181
+ Delivered to: ${result.delivered.success} followers (${result.delivered.failed} failed)
182
+ `)
183
+
184
+ rl.close()
185
+ }
186
+
187
+ async function runFollow() {
188
+ if (!existsSync('fedbox.json')) {
189
+ console.log('❌ Not initialized. Run: fedbox init\n')
190
+ process.exit(1)
191
+ }
192
+
193
+ const handle = process.argv[3]
194
+ if (!handle) {
195
+ console.log('Usage: fedbox follow @user@domain')
196
+ process.exit(1)
197
+ }
198
+
199
+ const { follow } = await import('../lib/actions.js')
200
+
201
+ try {
202
+ const result = await follow(handle)
203
+ console.log(`
204
+ ✅ Follow request sent!
205
+
206
+ User: ${result.actor.preferredUsername || result.actor.name}
207
+ Actor: ${result.actor.id}
208
+
209
+ Waiting for them to accept...
210
+ `)
211
+ } catch (err) {
212
+ console.log(`❌ ${err.message}`)
213
+ process.exit(1)
214
+ }
215
+
216
+ rl.close()
217
+ }
218
+
219
+ async function runTimeline() {
220
+ if (!existsSync('fedbox.json')) {
221
+ console.log('❌ Not initialized. Run: fedbox init\n')
222
+ process.exit(1)
223
+ }
224
+
225
+ const { timeline } = await import('../lib/actions.js')
226
+ const posts = timeline(20)
227
+
228
+ if (posts.length === 0) {
229
+ console.log(`
230
+ 📭 Your timeline is empty.
231
+
232
+ Follow some people with: fedbox follow @user@domain
233
+ `)
234
+ rl.close()
235
+ return
236
+ }
237
+
238
+ console.log(`
239
+ ╔═══════════════════════════════════════════╗
240
+ ║ 📰 TIMELINE ║
241
+ ╚═══════════════════════════════════════════╝
242
+ `)
243
+
244
+ for (const post of posts) {
245
+ const author = post.author?.split('/').pop() || 'unknown'
246
+ const content = post.content
247
+ .replace(/<[^>]*>/g, '') // Strip HTML
248
+ .slice(0, 200)
249
+ const date = new Date(post.published).toLocaleString()
250
+
251
+ console.log(`┌─ @${author} · ${date}`)
252
+ console.log(`│ ${content}`)
253
+ if (post.inReplyTo) {
254
+ console.log(`│ ↩️ Reply to: ${post.inReplyTo}`)
255
+ }
256
+ console.log(`└─ ${post.id}`)
257
+ console.log()
258
+ }
259
+
260
+ rl.close()
261
+ }
262
+
263
+ async function runReply() {
264
+ if (!existsSync('fedbox.json')) {
265
+ console.log('❌ Not initialized. Run: fedbox init\n')
266
+ process.exit(1)
267
+ }
268
+
269
+ const postUrl = process.argv[3]
270
+ const content = process.argv[4]
271
+
272
+ if (!postUrl || !content) {
273
+ console.log('Usage: fedbox reply <post-url> "Your reply"')
274
+ process.exit(1)
275
+ }
276
+
277
+ const { reply } = await import('../lib/actions.js')
278
+
279
+ console.log('💬 Sending reply...')
280
+ const result = await reply(postUrl, content)
281
+
282
+ console.log(`
283
+ ✅ Reply sent!
284
+
285
+ ID: ${result.noteId}
286
+ In reply to: ${postUrl}
287
+ Delivered to: ${result.delivered.success} inboxes
146
288
  `)
147
289
 
148
290
  rl.close()
149
291
  }
150
292
 
293
+ async function runPosts() {
294
+ if (!existsSync('fedbox.json')) {
295
+ console.log('❌ Not initialized. Run: fedbox init\n')
296
+ process.exit(1)
297
+ }
298
+
299
+ const { myPosts } = await import('../lib/actions.js')
300
+ const posts = myPosts(20)
301
+
302
+ if (posts.length === 0) {
303
+ console.log(`
304
+ 📭 You haven't posted anything yet.
305
+
306
+ Create a post with: fedbox post "Hello, Fediverse!"
307
+ `)
308
+ rl.close()
309
+ return
310
+ }
311
+
312
+ console.log(`
313
+ ╔═══════════════════════════════════════════╗
314
+ ║ 📝 YOUR POSTS ║
315
+ ╚═══════════════════════════════════════════╝
316
+ `)
317
+
318
+ for (const post of posts) {
319
+ const date = new Date(post.published).toLocaleString()
320
+ console.log(`┌─ ${date}`)
321
+ console.log(`│ ${post.content}`)
322
+ if (post.in_reply_to) {
323
+ console.log(`│ ↩️ Reply to: ${post.in_reply_to}`)
324
+ }
325
+ console.log(`└─ ${post.id}`)
326
+ console.log()
327
+ }
328
+
329
+ rl.close()
330
+ }
331
+
332
+ async function runClean() {
333
+ const all = process.argv[3] === '--all'
334
+
335
+ console.log('🧹 Cleaning up...\n')
336
+
337
+ // Remove database
338
+ if (existsSync('data/fedbox.db')) {
339
+ unlinkSync('data/fedbox.db')
340
+ console.log(' ✓ Removed data/fedbox.db')
341
+ }
342
+
343
+ // Remove data directory if empty
344
+ if (existsSync('data')) {
345
+ try {
346
+ rmSync('data', { recursive: false })
347
+ console.log(' ✓ Removed data/')
348
+ } catch {
349
+ // Directory not empty, that's ok
350
+ }
351
+ }
352
+
353
+ // Remove config if --all
354
+ if (all && existsSync('fedbox.json')) {
355
+ unlinkSync('fedbox.json')
356
+ console.log(' ✓ Removed fedbox.json')
357
+ }
358
+
359
+ console.log('\n✅ Clean complete!')
360
+
361
+ if (!all) {
362
+ console.log('\n Tip: Use "fedbox clean --all" to also remove config')
363
+ }
364
+
365
+ rl.close()
366
+ }
367
+
151
368
  function runHelp() {
152
369
  console.log(`
153
370
  ${BANNER}
154
- Usage: fedbox <command>
371
+ Usage: fedbox <command> [args]
372
+
373
+ Setup:
374
+ init Set up a new Fediverse identity
375
+ start Start the server
376
+ status Show current configuration
377
+
378
+ Social:
379
+ post "text" Post a message to your followers
380
+ follow @user@dom Follow a remote user
381
+ timeline View posts from people you follow
382
+ reply <url> "text" Reply to a post
383
+ posts View your own posts
155
384
 
156
- Commands:
157
- init Set up a new Fediverse identity
158
- start Start the server
159
- status Show current configuration
160
- help Show this help
385
+ Other:
386
+ clean Remove database (add --all to also remove config)
387
+ help Show this help
161
388
 
162
389
  Quick start:
163
390
  $ fedbox init
164
391
  $ fedbox start
392
+ $ fedbox post "Hello, Fediverse!"
393
+ $ fedbox follow @user@mastodon.social
165
394
 
166
395
  For federation (so Mastodon can find you):
167
396
  $ ngrok http 3000