| … | … | @@ -0,0 +1,9 @@ |
| 1 | #!/usr/bin/env bare | |
| 2 | // favela-community CLI entry (Bare). Drives the community write layer — see lib/cli.js for commands. | |
| 3 | import process from 'bare-process' | |
| 4 | import { run } from '../lib/cli.js' | |
| 5 | ||
| 6 | const args = (globalThis.Bare && Array.isArray(globalThis.Bare.argv)) ? globalThis.Bare.argv.slice(2) : process.argv.slice(2) | |
| 7 | run(args) | |
| 8 | .then((code) => process.exit(code || 0)) | |
| 9 | .catch((e) => { console.error(e && (e.stack || e.message) || e); process.exit(1) }) |
| … | … | @@ -0,0 +1,238 @@ |
| 1 | // favela-community CLI — the mod-side tool that DRIVES the write layer. An owner creates + manages a | |
| 2 | // community; a mod joins and (once admitted) co-writes; anyone reads keylessly. Runs under Bare. | |
| 3 | // | |
| 4 | // State persists in a corestore (--storage, default ~/.config/favela-community/store) + a small JSON index | |
| 5 | // (rooms.json in the parent dir) mapping roomKey → the corestore NAMESPACE used for it. The namespace is | |
| 6 | // load-bearing: the autobase local writer key is derived from (corestore, namespace), so reopening a room | |
| 7 | // with the SAME namespace is what keeps the owner/mod WRITABLE across invocations — a different namespace | |
| 8 | // would mint a fresh, un-admitted writer. The index makes that identity stable. | |
| 9 | // | |
| 10 | // One-shot commands operate on the LOCAL replica (fast, no swarm). `serve` (and `--sync` on reads) join the | |
| 11 | // swarm to replicate with peers + favela-web. ⚠ Corestore holds an exclusive lock — don't run a one-shot | |
| 12 | // command against the SAME --storage while `serve` is running (that's what a control-socket daemon, favela | |
| 13 | // roadmap step 3, is for). | |
| 14 | import process from 'bare-process' | |
| 15 | import { join, dirname } from 'bare-path' | |
| 16 | import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'bare-fs' | |
| 17 | import Corestore from 'corestore' | |
| 18 | import Hyperswarm from 'hyperswarm' | |
| 19 | import { CommunityRoom } from './community-room.js' | |
| 20 | ||
| 21 | const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) | |
| 22 | const isKey = (k) => /^[0-9a-f]{64}$/i.test(String(k || '')) | |
| 23 | const normKey = (k) => String(k || '').toLowerCase() | |
| 24 | ||
| 25 | function parse (args) { | |
| 26 | const positional = [] | |
| 27 | const flags = {} | |
| 28 | for (let i = 0; i < args.length; i++) { | |
| 29 | const a = args[i] | |
| 30 | if (a.startsWith('--')) { | |
| 31 | const eq = a.indexOf('=') | |
| 32 | if (eq >= 0) { flags[a.slice(2, eq)] = a.slice(eq + 1) } else { | |
| 33 | const next = args[i + 1] | |
| 34 | if (next !== undefined && !next.startsWith('--')) { flags[a.slice(2)] = next; i++ } else flags[a.slice(2)] = true | |
| 35 | } | |
| 36 | } else positional.push(a) | |
| 37 | } | |
| 38 | return { positional, flags } | |
| 39 | } | |
| 40 | ||
| 41 | const USAGE = `favela-community — co-owned community rooms (the mod-side write tool) | |
| 42 | ||
| 43 | Owner: | |
| 44 | create [--name NAME] create a community; prints its public roomKey | |
| 45 | admit <roomKey> <modLocalKey> [--role writer|owner] [--id NAME] | |
| 46 | admit a mod as a co-writer (default role=writer, non-indexer) | |
| 47 | ||
| 48 | Mod (co-writer): | |
| 49 | join <roomKey> open a room as a mod; prints THIS peer's localKey (give it to the owner) | |
| 50 | local-key <roomKey> print this peer's local writer key for a room | |
| 51 | ||
| 52 | Writer ops (owner or admitted mod): | |
| 53 | add-member <roomKey> <did> | |
| 54 | remove-member <roomKey> <did> | |
| 55 | ban <roomKey> <did> | |
| 56 | unban <roomKey> <did> | |
| 57 | set-meta <roomKey> <key> <value> | |
| 58 | ||
| 59 | Read (anyone, keyless): | |
| 60 | members <roomKey> [--sync] list member DIDs (roster minus banned) | |
| 61 | mods <roomKey> [--sync] list co-writers | |
| 62 | meta <roomKey> [--sync] community metadata | |
| 63 | ||
| 64 | Live: | |
| 65 | serve <roomKey> keep the room open + swarm-joined (seed + stay writable) | |
| 66 | ||
| 67 | Options: | |
| 68 | --storage PATH corestore dir (default ~/.config/favela-community/store) | |
| 69 | --sync join the swarm + wait for replication before reading (for a room hosted elsewhere) | |
| 70 | --timeout MS sync/admission wait (default 15000) | |
| 71 | --json machine-readable output | |
| 72 | ` | |
| 73 | ||
| 74 | // ── persistence: storage + the roomKey→namespace index ── | |
| 75 | function storagePath (flags) { | |
| 76 | if (flags.storage) return String(flags.storage) | |
| 77 | const home = process.env.HOME || process.env.USERPROFILE || '.' | |
| 78 | return join(home, '.config', 'favela-community', 'store') | |
| 79 | } | |
| 80 | function indexPath (storage) { return join(dirname(storage), 'rooms.json') } | |
| 81 | function loadIndex (storage) { | |
| 82 | const p = indexPath(storage) | |
| 83 | try { return existsSync(p) ? JSON.parse(readFileSync(p, 'utf8')) : {} } catch { return {} } | |
| 84 | } | |
| 85 | function saveIndex (storage, index) { | |
| 86 | const dir = dirname(indexPath(storage)) | |
| 87 | try { if (!existsSync(dir)) mkdirSync(dir, { recursive: true }) } catch {} | |
| 88 | writeFileSync(indexPath(storage), JSON.stringify(index, null, 2)) | |
| 89 | } | |
| 90 | // Namespace for a room. A recorded owner/mod entry keeps its writer identity; an unknown room gets an | |
| 91 | // ephemeral read namespace (a fresh read replica — never writable). | |
| 92 | function nsFor (index, key) { return (index[key] && index[key].namespace) || `read:${key}` } | |
| 93 | ||
| 94 | async function makeSwarm (room) { | |
| 95 | const swarm = new Hyperswarm() | |
| 96 | swarm.on('connection', (conn) => room.base.replicate(conn)) // base.replicate = store.replicate + wakeup | |
| 97 | swarm.join(room.discoveryKey, { client: true, server: true }) | |
| 98 | await swarm.flush() | |
| 99 | return swarm | |
| 100 | } | |
| 101 | ||
| 102 | // run(args) → exit code. io is injectable for tests. | |
| 103 | export async function run (args, io = {}) { | |
| 104 | const out = io.out || ((s) => console.log(s)) | |
| 105 | const err = io.err || ((s) => console.error(s)) | |
| 106 | const { positional, flags } = parse(args) | |
| 107 | const cmd = positional[0] | |
| 108 | if (!cmd || cmd === 'help' || flags.help) { out(USAGE); return cmd ? 0 : 1 } | |
| 109 | ||
| 110 | const storage = storagePath(flags) | |
| 111 | const index = loadIndex(storage) | |
| 112 | const json = !!flags.json | |
| 113 | const timeout = Number(flags.timeout) || 15000 | |
| 114 | const emit = (obj, human) => { out(json ? JSON.stringify(obj) : human) } | |
| 115 | ||
| 116 | let store = null | |
| 117 | let room = null | |
| 118 | let swarm = null | |
| 119 | const cleanup = async () => { | |
| 120 | try { if (swarm) await swarm.destroy() } catch {} | |
| 121 | try { if (room) await room.close() } catch {} | |
| 122 | try { if (store) await store.close() } catch {} | |
| 123 | } | |
| 124 | ||
| 125 | try { | |
| 126 | // ── create (owner) ── | |
| 127 | if (cmd === 'create') { | |
| 128 | const ns = flags.namespace ? String(flags.namespace) : `owner-${Date.now()}` | |
| 129 | store = new Corestore(storage) | |
| 130 | room = await CommunityRoom.create(store, { namespace: ns, name: flags.name ? String(flags.name) : null }) | |
| 131 | index[normKey(room.key)] = { namespace: ns, role: 'owner', name: flags.name || null } | |
| 132 | saveIndex(storage, index) | |
| 133 | emit({ roomKey: room.key, localKey: room.localKey, role: 'owner' }, | |
| 134 | `created community ${room.key}\n role: owner (sole indexer)\n share this roomKey; run 'serve ${room.key}' to publish it`) | |
| 135 | return 0 | |
| 136 | } | |
| 137 | ||
| 138 | // ── join (mod) ── | |
| 139 | if (cmd === 'join') { | |
| 140 | const key = normKey(positional[1]) | |
| 141 | if (!isKey(key)) { err('join: a 64-hex roomKey is required'); return 1 } | |
| 142 | let ns = index[key] && index[key].namespace | |
| 143 | if (!ns) { ns = `mod-${Date.now()}`; index[key] = { namespace: ns, role: 'mod' }; saveIndex(storage, index) } | |
| 144 | store = new Corestore(storage) | |
| 145 | room = await CommunityRoom.open(store, key, { namespace: ns }) | |
| 146 | emit({ roomKey: key, localKey: room.localKey, writable: room.writable }, | |
| 147 | `joined ${key} as a mod\n your localKey: ${room.localKey}\n give it to the owner: favela-community admit ${key} ${room.localKey}\n then run 'serve ${key}' — you become writable once the owner admits + you sync`) | |
| 148 | return 0 | |
| 149 | } | |
| 150 | ||
| 151 | // ── admit (owner adds a mod) ── | |
| 152 | if (cmd === 'admit') { | |
| 153 | const key = normKey(positional[1]) | |
| 154 | const modKey = normKey(positional[2]) | |
| 155 | if (!isKey(key) || !isKey(modKey)) { err('admit: usage: admit <roomKey> <modLocalKey>'); return 1 } | |
| 156 | store = new Corestore(storage) | |
| 157 | room = await CommunityRoom.open(store, key, { namespace: nsFor(index, key) }) | |
| 158 | if (!room.writable) { err(`admit: not a writer for ${key} (only the owner / an admitted mod can admit)`); return 1 } | |
| 159 | const role = flags.role === 'owner' ? 'owner' : 'writer' | |
| 160 | await room.addMod(modKey, { role, userId: flags.id ? String(flags.id) : undefined }) | |
| 161 | swarm = await makeSwarm(room) // give the mod a window to sync the admission | |
| 162 | await sleep(1500) | |
| 163 | emit({ roomKey: key, admitted: modKey, role }, `admitted ${modKey} as ${role}${role === 'owner' ? ' (INDEXER — ensure an ODD count >=3; N=2 deadlocks)' : ''}`) | |
| 164 | return 0 | |
| 165 | } | |
| 166 | ||
| 167 | if (cmd === 'local-key') { | |
| 168 | const key = normKey(positional[1]) | |
| 169 | if (!isKey(key)) { err('local-key: a 64-hex roomKey is required'); return 1 } | |
| 170 | store = new Corestore(storage) | |
| 171 | room = await CommunityRoom.open(store, key, { namespace: nsFor(index, key) }) | |
| 172 | emit({ roomKey: key, localKey: room.localKey, writable: room.writable }, room.localKey) | |
| 173 | return 0 | |
| 174 | } | |
| 175 | ||
| 176 | // ── writer ops ── | |
| 177 | if (['add-member', 'remove-member', 'ban', 'unban', 'set-meta'].includes(cmd)) { | |
| 178 | const key = normKey(positional[1]) | |
| 179 | if (!isKey(key)) { err(`${cmd}: a 64-hex roomKey is required`); return 1 } | |
| 180 | store = new Corestore(storage) | |
| 181 | room = await CommunityRoom.open(store, key, { namespace: nsFor(index, key) }) | |
| 182 | if (!room.writable) { err(`${cmd}: not a writer for ${key} — create it here, or 'join' + have the owner admit you`); return 1 } | |
| 183 | try { | |
| 184 | if (cmd === 'add-member') await room.addMember(positional[2]) | |
| 185 | else if (cmd === 'remove-member') await room.removeMember(positional[2]) | |
| 186 | else if (cmd === 'ban') await room.ban(positional[2]) | |
| 187 | else if (cmd === 'unban') await room.unban(positional[2]) | |
| 188 | else if (cmd === 'set-meta') await room.setMeta(positional[2], positional[3]) | |
| 189 | } catch (e) { err(`${cmd}: ${e.message}`); return 1 } | |
| 190 | if (flags.sync) { swarm = await makeSwarm(room); await sleep(1500) } // best-effort propagate | |
| 191 | emit({ roomKey: key, cmd, arg: positional[2], value: positional[3] }, `${cmd} ${positional[2] || ''}${positional[3] ? ' ' + positional[3] : ''} — ok`) | |
| 192 | return 0 | |
| 193 | } | |
| 194 | ||
| 195 | // ── reads (keyless) ── | |
| 196 | if (['members', 'mods', 'meta', 'key'].includes(cmd)) { | |
| 197 | const key = normKey(positional[1]) | |
| 198 | if (!isKey(key)) { err(`${cmd}: a 64-hex roomKey is required`); return 1 } | |
| 199 | if (cmd === 'key') { emit({ roomKey: key }, key); return 0 } | |
| 200 | store = new Corestore(storage) | |
| 201 | room = await CommunityRoom.openReadonly(store, key, { namespace: nsFor(index, key) }) | |
| 202 | if (flags.sync) { | |
| 203 | swarm = await makeSwarm(room) | |
| 204 | const end = Date.now() + timeout | |
| 205 | while (Date.now() < end) { if ((await room.members()).length) break; await sleep(250) } | |
| 206 | } | |
| 207 | if (cmd === 'members') { const m = await room.members(); emit({ roomKey: key, members: m }, m.length ? m.join('\n') : '(no members)') } | |
| 208 | else if (cmd === 'mods') { const w = await room.mods(); emit({ roomKey: key, mods: w }, w.length ? w.map((x) => `${x.userId} (${x.role})`).join('\n') : '(no co-writers)') } | |
| 209 | else if (cmd === 'meta') { const mt = await room.meta(); emit({ roomKey: key, meta: mt }, Object.keys(mt).length ? Object.entries(mt).map(([k, v]) => `${k}: ${v}`).join('\n') : '(no metadata)') } | |
| 210 | return 0 | |
| 211 | } | |
| 212 | ||
| 213 | // ── serve (stay live) ── | |
| 214 | if (cmd === 'serve') { | |
| 215 | const key = normKey(positional[1]) | |
| 216 | if (!isKey(key)) { err('serve: a 64-hex roomKey is required'); return 1 } | |
| 217 | store = new Corestore(storage) | |
| 218 | room = await CommunityRoom.open(store, key, { namespace: nsFor(index, key) }) | |
| 219 | swarm = await makeSwarm(room) | |
| 220 | const members = await room.members() | |
| 221 | out(`serving ${key}\n writable: ${room.writable} · members: ${members.length}\n discovery: ${room.discoveryKey ? Buffer.from(room.discoveryKey).toString('hex').slice(0, 16) + '…' : '?'}\n (Ctrl-C to stop)`) | |
| 222 | let stop | |
| 223 | const done = new Promise((resolve) => { stop = resolve }) | |
| 224 | const onSig = () => stop() | |
| 225 | process.on('SIGINT', onSig); process.on('SIGTERM', onSig) | |
| 226 | await done | |
| 227 | out('\nstopping…') | |
| 228 | return 0 | |
| 229 | } | |
| 230 | ||
| 231 | err(`unknown command: ${cmd}\n\n${USAGE}`) | |
| 232 | return 1 | |
| 233 | } finally { | |
| 234 | await cleanup() | |
| 235 | } | |
| 236 | } | |
| 237 | ||
| 238 | export default run |
| … | … | @@ -10,10 +10,15 @@ |
| 10 | 10 | "dependencies": { |
| 11 | 11 | "autobase": "^7.28.0", |
| 12 | 12 | "b4a": "^1.8.1", |
| 13 | "hyper-rooms": "file:../hyper-rooms" | |
| 13 | "bare-fs": "^4.0.0", | |
| 14 | "bare-path": "^3.0.0", | |
| 15 | "bare-process": "^4.0.0", | |
| 16 | "corestore": "^7.4.0", | |
| 17 | "hyper-rooms": "file:../hyper-rooms", | |
| 18 | "hyperswarm": "^4.11.0" | |
| 14 | 19 | }, |
| 15 | "devDependencies": { | |
| 16 | "corestore": "^7.4.0" | |
| 20 | "bin": { | |
| 21 | "favela-community": "bin/favela-community.js" | |
| 17 | 22 | } |
| 18 | 23 | }, |
| 19 | 24 | "../hyper-rooms": { |
| … | … | @@ -51,6 +56,15 @@ |
| 51 | 56 | "unslab": "^1.3.0" |
| 52 | 57 | } |
| 53 | 58 | }, |
| 59 | "node_modules/adaptive-timeout": { | |
| 60 | "version": "1.0.1", | |
| 61 | "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz", | |
| 62 | "integrity": "sha512-+seGiBtHqbnyZ0Quq/ZGxxwP66153WBABawa07/QeyuPFzbduPBjiGQAyCiriiLDzt3dkbMBskSlfqtRwblK8Q==", | |
| 63 | "license": "Apache-2.0", | |
| 64 | "dependencies": { | |
| 65 | "xache": "^1.2.1" | |
| 66 | } | |
| 67 | }, | |
| 54 | 68 | "node_modules/autobase": { |
| 55 | 69 | "version": "7.28.1", |
| 56 | 70 | "resolved": "https://registry.npmjs.org/autobase/-/autobase-7.28.1.tgz", |
| … | … | @@ -95,6 +109,12 @@ |
| 95 | 109 | } |
| 96 | 110 | } |
| 97 | 111 | }, |
| 112 | "node_modules/bare-abort": { | |
| 113 | "version": "2.0.13", | |
| 114 | "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz", | |
| 115 | "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==", | |
| 116 | "license": "Apache-2.0" | |
| 117 | }, | |
| 98 | 118 | "node_modules/bare-addon-resolve": { |
| 99 | 119 | "version": "1.10.0", |
| 100 | 120 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", |
| … | … | @@ -139,6 +159,15 @@ |
| 139 | 159 | "bare-inspect": "^3.1.2" |
| 140 | 160 | } |
| 141 | 161 | }, |
| 162 | "node_modules/bare-env": { | |
| 163 | "version": "3.0.1", | |
| 164 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.1.tgz", | |
| 165 | "integrity": "sha512-BdoLvnzaWR0YyyJreEx4zG2Od0AC/s9JSZ+EXyMZKunpn8zEgR/dGv37hdFUaY1bjDkcRKvcKkFyJIwlJ0CaYA==", | |
| 166 | "license": "Apache-2.0", | |
| 167 | "dependencies": { | |
| 168 | "bare-os": "^3.0.1" | |
| 169 | } | |
| 170 | }, | |
| 142 | 171 | "node_modules/bare-events": { |
| 143 | 172 | "version": "2.9.1", |
| 144 | 173 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", |
| … | … | @@ -177,6 +206,12 @@ |
| 177 | 206 | } |
| 178 | 207 | } |
| 179 | 208 | }, |
| 209 | "node_modules/bare-hrtime": { | |
| 210 | "version": "2.1.1", | |
| 211 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 212 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 213 | "license": "Apache-2.0" | |
| 214 | }, | |
| 180 | 215 | "node_modules/bare-inspect": { |
| 181 | 216 | "version": "3.1.4", |
| 182 | 217 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", |
| … | … | @@ -207,18 +242,85 @@ |
| 207 | 242 | } |
| 208 | 243 | } |
| 209 | 244 | }, |
| 245 | "node_modules/bare-os": { | |
| 246 | "version": "3.9.3", | |
| 247 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz", | |
| 248 | "integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==", | |
| 249 | "license": "Apache-2.0", | |
| 250 | "engines": { | |
| 251 | "bare": ">=1.14.0" | |
| 252 | } | |
| 253 | }, | |
| 210 | 254 | "node_modules/bare-path": { |
| 211 | 255 | "version": "3.1.1", |
| 212 | 256 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", |
| 213 | 257 | "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", |
| 214 | 258 | "license": "Apache-2.0" |
| 215 | 259 | }, |
| 260 | "node_modules/bare-pipe": { | |
| 261 | "version": "4.2.2", | |
| 262 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.2.2.tgz", | |
| 263 | "integrity": "sha512-Wkf6//CmKS8inHrkgi3X/xNMeaGfs2I0ayLM0B8cbPB4DxYHcoes6Ng+muCBfXNIJ9qmEjwDDJjAUz7nRxxjgQ==", | |
| 264 | "license": "Apache-2.0", | |
| 265 | "dependencies": { | |
| 266 | "bare-events": "^2.0.0", | |
| 267 | "bare-stream": "^2.0.0" | |
| 268 | }, | |
| 269 | "engines": { | |
| 270 | "bare": ">=1.16.0" | |
| 271 | } | |
| 272 | }, | |
| 273 | "node_modules/bare-posix": { | |
| 274 | "version": "1.0.1", | |
| 275 | "resolved": "https://registry.npmjs.org/bare-posix/-/bare-posix-1.0.1.tgz", | |
| 276 | "integrity": "sha512-b4quA8dyRVvX0aFIAIiev5zFATHwIUGr42Gmt+CqiBNg/bdsNkrj4bbkeotMOMiljJS4DCME5kywJO/TFwKo1A==", | |
| 277 | "license": "Apache-2.0" | |
| 278 | }, | |
| 279 | "node_modules/bare-process": { | |
| 280 | "version": "4.5.1", | |
| 281 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.1.tgz", | |
| 282 | "integrity": "sha512-CaAvy1trputD49mtwfJ6G75vydhnirLrW/F3Sznp4H556e1uZn8YMo9ELicBTrGYy7RBNUgPl9bpB/ERzRCiDw==", | |
| 283 | "license": "Apache-2.0", | |
| 284 | "dependencies": { | |
| 285 | "bare-abort": "^2.0.13", | |
| 286 | "bare-env": "^3.0.0", | |
| 287 | "bare-events": "^2.3.1", | |
| 288 | "bare-hrtime": "^2.0.0", | |
| 289 | "bare-os": "^3.7.1", | |
| 290 | "bare-posix": "^1.0.1", | |
| 291 | "bare-signals": "^5.0.0", | |
| 292 | "bare-stdio": "^1.0.1" | |
| 293 | } | |
| 294 | }, | |
| 216 | 295 | "node_modules/bare-semver": { |
| 217 | 296 | "version": "1.1.0", |
| 218 | 297 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.1.0.tgz", |
| 219 | 298 | "integrity": "sha512-1Hw5qJ7hXdVt3uPUqjeFTuxyvBUJauvz5A1I2jk8gzjZMHp04n//6nV9MDbG9CMw78JHY2lGV0w6s//LrASm2w==", |
| 220 | 299 | "license": "Apache-2.0" |
| 221 | 300 | }, |
| 301 | "node_modules/bare-signals": { | |
| 302 | "version": "5.0.0", | |
| 303 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-5.0.0.tgz", | |
| 304 | "integrity": "sha512-8Gn8bBFUh2AUCJ9wWWFjSGWIo1HIUSIQnXRJGSm/f7GCDMqsuJhRmR1dT+HtDaDBkeu2l8Koxt2JLurfJ5yHVw==", | |
| 305 | "license": "Apache-2.0", | |
| 306 | "dependencies": { | |
| 307 | "bare-events": "^2.5.3" | |
| 308 | }, | |
| 309 | "engines": { | |
| 310 | "bare": ">=1.7.0" | |
| 311 | } | |
| 312 | }, | |
| 313 | "node_modules/bare-stdio": { | |
| 314 | "version": "1.0.3", | |
| 315 | "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.3.tgz", | |
| 316 | "integrity": "sha512-8BRMx7RWMWCbBmKhyHgBco62J+Pgss7+EPI21L3R9+w0UpwDYWUUcbE0YTOesGyt7M6N7VtoshYbzwrP0pI52Q==", | |
| 317 | "license": "Apache-2.0", | |
| 318 | "dependencies": { | |
| 319 | "bare-fs": "^4.5.2", | |
| 320 | "bare-pipe": "^4.1.5", | |
| 321 | "bare-tty": "^5.0.3" | |
| 322 | } | |
| 323 | }, | |
| 222 | 324 | "node_modules/bare-stream": { |
| 223 | 325 | "version": "2.13.3", |
| 224 | 326 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", |
| … | … | @@ -246,6 +348,20 @@ |
| 246 | 348 | } |
| 247 | 349 | } |
| 248 | 350 | }, |
| 351 | "node_modules/bare-tty": { | |
| 352 | "version": "5.1.2", | |
| 353 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.2.tgz", | |
| 354 | "integrity": "sha512-wDHPU/yVQtrDCUrKVdIVOLmfIw7dFPETz30UayuFlROvgz8Juv2SmISSMhqxYQVO+fVnG/1ZoLcbGh/35eAjDA==", | |
| 355 | "license": "Apache-2.0", | |
| 356 | "dependencies": { | |
| 357 | "bare-events": "^2.2.0", | |
| 358 | "bare-signals": "^5.0.0", | |
| 359 | "bare-stream": "^2.0.0" | |
| 360 | }, | |
| 361 | "engines": { | |
| 362 | "bare": ">=1.16.0" | |
| 363 | } | |
| 364 | }, | |
| 249 | 365 | "node_modules/bare-type": { |
| 250 | 366 | "version": "1.1.0", |
| 251 | 367 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", |
| … | … | @@ -260,6 +376,7 @@ |
| 260 | 376 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", |
| 261 | 377 | "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", |
| 262 | 378 | "license": "Apache-2.0", |
| 379 | "peer": true, | |
| 263 | 380 | "dependencies": { |
| 264 | 381 | "bare-path": "^3.0.0" |
| 265 | 382 | } |
| … | … | @@ -270,6 +387,41 @@ |
| 270 | 387 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", |
| 271 | 388 | "license": "MIT" |
| 272 | 389 | }, |
| 390 | "node_modules/bits-to-bytes": { | |
| 391 | "version": "1.3.0", | |
| 392 | "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz", | |
| 393 | "integrity": "sha512-OJoHTpFXS9bXHBCekGTByf3MqM8CGblBDIduKQeeVVeiU9dDWywSSirXIBYGgg3d1zbVuvnMa1vD4r6PA0kOKg==", | |
| 394 | "license": "ISC", | |
| 395 | "dependencies": { | |
| 396 | "b4a": "^1.5.0" | |
| 397 | } | |
| 398 | }, | |
| 399 | "node_modules/blind-relay": { | |
| 400 | "version": "1.6.1", | |
| 401 | "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.6.1.tgz", | |
| 402 | "integrity": "sha512-38YmKCl/m9NcTkwueBbcGIt9Zx3IT/Q9Nsluu6C5Gur6PqfE0zWPhPwCzia1hri/g0ICYxrqkgLtEaoWD5WeSQ==", | |
| 403 | "license": "Apache-2.0", | |
| 404 | "dependencies": { | |
| 405 | "b4a": "^1.6.4", | |
| 406 | "bare-events": "^2.2.0", | |
| 407 | "bits-to-bytes": "^1.3.0", | |
| 408 | "compact-encoding": "^3.0.0", | |
| 409 | "compact-encoding-bitfield": "^1.0.0", | |
| 410 | "protomux": "^3.5.1", | |
| 411 | "sodium-universal": "^5.0.0", | |
| 412 | "streamx": "^2.15.1" | |
| 413 | } | |
| 414 | }, | |
| 415 | "node_modules/bogon": { | |
| 416 | "version": "1.3.0", | |
| 417 | "resolved": "https://registry.npmjs.org/bogon/-/bogon-1.3.0.tgz", | |
| 418 | "integrity": "sha512-04tu0G/v0f2HPuQlSfhO635WwHDBCaITJ490rhxH9ttUlgPqOirZVKV02YB6NvPf5VkmbqJCm3bnZwrPk/eDSg==", | |
| 419 | "license": "MIT", | |
| 420 | "dependencies": { | |
| 421 | "compact-encoding": "^3.0.0", | |
| 422 | "compact-encoding-net": "^1.2.0" | |
| 423 | } | |
| 424 | }, | |
| 273 | 425 | "node_modules/codecs": { |
| 274 | 426 | "version": "3.1.0", |
| 275 | 427 | "resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz", |
| … | … | @@ -288,6 +440,24 @@ |
| 288 | 440 | "b4a": "^1.3.0" |
| 289 | 441 | } |
| 290 | 442 | }, |
| 443 | "node_modules/compact-encoding-bitfield": { | |
| 444 | "version": "1.1.0", | |
| 445 | "resolved": "https://registry.npmjs.org/compact-encoding-bitfield/-/compact-encoding-bitfield-1.1.0.tgz", | |
| 446 | "integrity": "sha512-F6wliSKHi50BsBStmnsRJAzJgYSIYzdfSe3M0oHj4uQ1RcctJK/NQVD7wF51bj/DBMne2i5gUxrGUFkNZQns5w==", | |
| 447 | "license": "Apache-2.0", | |
| 448 | "dependencies": { | |
| 449 | "compact-encoding": "^3.0.0" | |
| 450 | } | |
| 451 | }, | |
| 452 | "node_modules/compact-encoding-net": { | |
| 453 | "version": "1.3.0", | |
| 454 | "resolved": "https://registry.npmjs.org/compact-encoding-net/-/compact-encoding-net-1.3.0.tgz", | |
| 455 | "integrity": "sha512-HIYjL3aMiSENApR691aMLngXt6rkTHb9HUkEukcx3YwIZpoMUVXHXyjBzoo9kVwC7KakooBA1RRWb46Cu6qtAQ==", | |
| 456 | "license": "Apache-2.0", | |
| 457 | "dependencies": { | |
| 458 | "compact-encoding": "^3.0.0" | |
| 459 | } | |
| 460 | }, | |
| 291 | 461 | "node_modules/core-coupler": { |
| 292 | 462 | "version": "2.0.0", |
| 293 | 463 | "resolved": "https://registry.npmjs.org/core-coupler/-/core-coupler-2.0.0.tgz", |
| … | … | @@ -301,7 +471,6 @@ |
| 301 | 471 | "version": "7.11.1", |
| 302 | 472 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.11.1.tgz", |
| 303 | 473 | "integrity": "sha512-X6UsGFyHeAcqffTqgoDV7WDZjhfvolWabwzws6GbdqeXQY0CTM6gNpNMGEi6RGvAdZDPnFaboF5jFBy1lw1rDA==", |
| 304 | "dev": true, | |
| 305 | 474 | "license": "MIT", |
| 306 | 475 | "dependencies": { |
| 307 | 476 | "b4a": "^1.6.7", |
| … | … | @@ -336,6 +505,26 @@ |
| 336 | 505 | "ready-resource": "^1.2.0" |
| 337 | 506 | } |
| 338 | 507 | }, |
| 508 | "node_modules/dht-rpc": { | |
| 509 | "version": "6.27.0", | |
| 510 | "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.27.0.tgz", | |
| 511 | "integrity": "sha512-NsfgRlFDQnkA2+H+hJXMPLmBOn/TSIIc0cRMV8q42M4xc1uAXWtpvIIQsONF5tYcYC6px0bslrUGideN1h4S4A==", | |
| 512 | "license": "MIT", | |
| 513 | "dependencies": { | |
| 514 | "adaptive-timeout": "^1.0.1", | |
| 515 | "b4a": "^1.6.1", | |
| 516 | "bare-events": "^2.2.0", | |
| 517 | "compact-encoding": "^3.0.0", | |
| 518 | "compact-encoding-net": "^1.2.0", | |
| 519 | "fast-fifo": "^1.1.0", | |
| 520 | "kademlia-routing-table": "^1.0.1", | |
| 521 | "nat-sampler": "^1.0.1", | |
| 522 | "sodium-universal": "^5.0.0", | |
| 523 | "streamx": "^2.13.2", | |
| 524 | "time-ordered-set": "^2.0.0", | |
| 525 | "udx-native": "^1.5.3" | |
| 526 | } | |
| 527 | }, | |
| 339 | 528 | "node_modules/encryption-encoding": { |
| 340 | 529 | "version": "1.0.3", |
| 341 | 530 | "resolved": "https://registry.npmjs.org/encryption-encoding/-/encryption-encoding-1.0.3.tgz", |
| … | … | @@ -507,6 +696,46 @@ |
| 507 | 696 | "xache": "^1.2.1" |
| 508 | 697 | } |
| 509 | 698 | }, |
| 699 | "node_modules/hyperdht": { | |
| 700 | "version": "6.33.0", | |
| 701 | "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.33.0.tgz", | |
| 702 | "integrity": "sha512-veSvVKptjPTu2di3q5IWI62ZDFzEmTj1QSTAkiXW/cg0+lgMUlwfVWB4dX+WI14xNb54vogVIjA/Ph3KiVuRJQ==", | |
| 703 | "license": "MIT", | |
| 704 | "dependencies": { | |
| 705 | "@hyperswarm/secret-stream": "^6.6.2", | |
| 706 | "b4a": "^1.3.1", | |
| 707 | "bare-events": "^2.2.0", | |
| 708 | "blind-relay": "^1.3.0", | |
| 709 | "bogon": "^1.0.0", | |
| 710 | "compact-encoding": "^3.0.0", | |
| 711 | "dht-rpc": "^6.15.1", | |
| 712 | "hypercore-crypto": "^3.3.0", | |
| 713 | "hypercore-id-encoding": "^1.2.0", | |
| 714 | "hyperdht-address": "^1.0.1", | |
| 715 | "noise-curve-ed": "^2.0.0", | |
| 716 | "noise-handshake": "^4.0.0", | |
| 717 | "record-cache": "^1.1.1", | |
| 718 | "safety-catch": "^1.0.1", | |
| 719 | "signal-promise": "^1.0.3", | |
| 720 | "sodium-universal": "^5.0.1", | |
| 721 | "streamx": "^2.16.1", | |
| 722 | "unslab": "^1.3.0", | |
| 723 | "xache": "^1.1.0" | |
| 724 | }, | |
| 725 | "bin": { | |
| 726 | "hyperdht": "bin.js" | |
| 727 | } | |
| 728 | }, | |
| 729 | "node_modules/hyperdht-address": { | |
| 730 | "version": "1.1.1", | |
| 731 | "resolved": "https://registry.npmjs.org/hyperdht-address/-/hyperdht-address-1.1.1.tgz", | |
| 732 | "integrity": "sha512-Mu/+7SW2cwvHxMXswa5mOrhrS5BJyntViAuB25k8d8wNtA8eAPs4LaIq6TwN1SS/KQY02h1r+gM8cQeN/k360w==", | |
| 733 | "license": "Apache-2.0", | |
| 734 | "dependencies": { | |
| 735 | "compact-encoding": "^3.0.0", | |
| 736 | "hyperschema": "^1.20.1" | |
| 737 | } | |
| 738 | }, | |
| 510 | 739 | "node_modules/hyperschema": { |
| 511 | 740 | "version": "1.21.0", |
| 512 | 741 | "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.21.0.tgz", |
| … | … | @@ -519,6 +748,21 @@ |
| 519 | 748 | "generate-string": "^1.0.1" |
| 520 | 749 | } |
| 521 | 750 | }, |
| 751 | "node_modules/hyperswarm": { | |
| 752 | "version": "4.17.0", | |
| 753 | "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.0.tgz", | |
| 754 | "integrity": "sha512-oe86sK961Ueg7rvDN/veFwG8xH+Iv6vObPhGDkPJcDVxk/NduW41ZhAcVDnHzRbm7S0eLU7WaDUvehOYoKSpRQ==", | |
| 755 | "license": "MIT", | |
| 756 | "dependencies": { | |
| 757 | "b4a": "^1.3.1", | |
| 758 | "bare-events": "^2.2.0", | |
| 759 | "hyperdht": "^6.21.0", | |
| 760 | "safety-catch": "^1.0.2", | |
| 761 | "shuffled-priority-queue": "^2.1.0", | |
| 762 | "streamx": "^2.22.1", | |
| 763 | "unslab": "^1.3.0" | |
| 764 | } | |
| 765 | }, | |
| 522 | 766 | "node_modules/index-encoder": { |
| 523 | 767 | "version": "3.5.0", |
| 524 | 768 | "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz", |
| … | … | @@ -543,6 +787,15 @@ |
| 543 | 787 | "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", |
| 544 | 788 | "license": "MIT" |
| 545 | 789 | }, |
| 790 | "node_modules/kademlia-routing-table": { | |
| 791 | "version": "1.0.6", | |
| 792 | "resolved": "https://registry.npmjs.org/kademlia-routing-table/-/kademlia-routing-table-1.0.6.tgz", | |
| 793 | "integrity": "sha512-Ve6jwIlUCYvUzBnXnzVRHDZCFgXURW9gmF3r7n05kZs/2rNbLHXwGdcq0qIaSwdmJCvtosgR4JensnVU65hzNQ==", | |
| 794 | "license": "MIT", | |
| 795 | "dependencies": { | |
| 796 | "bare-events": "^2.2.0" | |
| 797 | } | |
| 798 | }, | |
| 546 | 799 | "node_modules/mutexify": { |
| 547 | 800 | "version": "1.4.0", |
| 548 | 801 | "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", |
| … | … | @@ -558,6 +811,12 @@ |
| 558 | 811 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", |
| 559 | 812 | "license": "ISC" |
| 560 | 813 | }, |
| 814 | "node_modules/nat-sampler": { | |
| 815 | "version": "1.0.1", | |
| 816 | "resolved": "https://registry.npmjs.org/nat-sampler/-/nat-sampler-1.0.1.tgz", | |
| 817 | "integrity": "sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==", | |
| 818 | "license": "MIT" | |
| 819 | }, | |
| 561 | 820 | "node_modules/noise-curve-ed": { |
| 562 | 821 | "version": "2.1.0", |
| 563 | 822 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", |
| … | … | @@ -666,6 +925,15 @@ |
| 666 | 925 | "bare-events": "^2.2.0" |
| 667 | 926 | } |
| 668 | 927 | }, |
| 928 | "node_modules/record-cache": { | |
| 929 | "version": "1.2.0", | |
| 930 | "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz", | |
| 931 | "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==", | |
| 932 | "license": "MIT", | |
| 933 | "dependencies": { | |
| 934 | "b4a": "^1.3.1" | |
| 935 | } | |
| 936 | }, | |
| 669 | 937 | "node_modules/refcounter": { |
| 670 | 938 | "version": "1.0.0", |
| 671 | 939 | "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz", |
| … | … | @@ -726,6 +994,15 @@ |
| 726 | 994 | "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==", |
| 727 | 995 | "license": "Apache-2.0" |
| 728 | 996 | }, |
| 997 | "node_modules/shuffled-priority-queue": { | |
| 998 | "version": "2.1.0", | |
| 999 | "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz", | |
| 1000 | "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==", | |
| 1001 | "license": "MIT", | |
| 1002 | "dependencies": { | |
| 1003 | "unordered-set": "^2.0.1" | |
| 1004 | } | |
| 1005 | }, | |
| 729 | 1006 | "node_modules/signal-promise": { |
| 730 | 1007 | "version": "1.0.3", |
| 731 | 1008 | "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz", |
| … | … | @@ -844,6 +1121,12 @@ |
| 844 | 1121 | "b4a": "^1.6.4" |
| 845 | 1122 | } |
| 846 | 1123 | }, |
| 1124 | "node_modules/time-ordered-set": { | |
| 1125 | "version": "2.0.1", | |
| 1126 | "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-2.0.1.tgz", | |
| 1127 | "integrity": "sha512-VJEKmgSN2UiOLB8BpN8Sh2b9LGMHTP5OPrQRpnKjvOheOyzk0mufbjzjKTIG2gO4A+Y+vDJ+0TcLbpUmMLsg8A==", | |
| 1128 | "license": "MIT" | |
| 1129 | }, | |
| 847 | 1130 | "node_modules/timeout-refresh": { |
| 848 | 1131 | "version": "2.0.1", |
| 849 | 1132 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", |
| … | … | @@ -859,6 +1142,27 @@ |
| 859 | 1142 | "b4a": "^1.6.0" |
| 860 | 1143 | } |
| 861 | 1144 | }, |
| 1145 | "node_modules/udx-native": { | |
| 1146 | "version": "1.20.7", | |
| 1147 | "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.20.7.tgz", | |
| 1148 | "integrity": "sha512-FNG0QpnSt0us2xbLP5mmG5Q0UKdLYjKHuh2eR9VrJSFmoZMzeszYLLhK12+iq5x3eq/rgoGG/lLwPn7IDAr6pg==", | |
| 1149 | "license": "Apache-2.0", | |
| 1150 | "dependencies": { | |
| 1151 | "b4a": "^1.5.0", | |
| 1152 | "bare-events": "^2.2.0", | |
| 1153 | "require-addon": "^1.1.0", | |
| 1154 | "streamx": "^2.22.0" | |
| 1155 | }, | |
| 1156 | "engines": { | |
| 1157 | "bare": ">=1.17.4" | |
| 1158 | } | |
| 1159 | }, | |
| 1160 | "node_modules/unordered-set": { | |
| 1161 | "version": "2.0.1", | |
| 1162 | "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", | |
| 1163 | "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==", | |
| 1164 | "license": "MIT" | |
| 1165 | }, | |
| 862 | 1166 | "node_modules/unslab": { |
| 863 | 1167 | "version": "1.3.0", |
| 864 | 1168 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", |
| … | … | @@ -4,15 +4,20 @@ |
| 4 | 4 | "description": "Co-owned, plaintext community rooms for Favela — membership + moderation on a hyper-rooms Autobase. Read KEYLESSLY by the gateway (favela-web); WRITTEN by mods (hpp / full-node / CLI). One community protocol, shared across surfaces.", |
| 5 | 5 | "type": "module", |
| 6 | 6 | "main": "index.js", |
| 7 | "bin": { | |
| 8 | "favela-community": "bin/favela-community.js" | |
| 9 | }, | |
| 7 | 10 | "scripts": { |
| 8 | "test": "bare test/community-room.test.mjs" | |
| 11 | "test": "bare test/community-room.test.mjs && bare test/cli.test.mjs" | |
| 9 | 12 | }, |
| 10 | 13 | "dependencies": { |
| 11 | 14 | "hyper-rooms": "file:../hyper-rooms", |
| 12 | 15 | "autobase": "^7.28.0", |
| 16 | "corestore": "^7.4.0", | |
| 17 | "hyperswarm": "^4.11.0", | |
| 18 | "bare-process": "^4.0.0", | |
| 19 | "bare-path": "^3.0.0", | |
| 20 | "bare-fs": "^4.0.0", | |
| 13 | 21 | "b4a": "^1.8.1" |
| 14 | }, | |
| 15 | "devDependencies": { | |
| 16 | "corestore": "^7.4.0" | |
| 17 | 22 | } |
| 18 | 23 | } |
| … | … | @@ -0,0 +1,71 @@ |
| 1 | // favela-community CLI — drives run() against a temp storage: the local owner flow (create → manage → | |
| 2 | // read) + the mod-not-admitted guard + arg/error handling. Cross-machine admission/replication is proven | |
| 3 | // by community-room.test.mjs. Bare-native. Run: bare test/cli.test.mjs | |
| 4 | import { run } from '../lib/cli.js' | |
| 5 | ||
| 6 | const assert = (c, m) => { if (!c) { console.error('FAIL:', m); process.exit(1) } console.log(' ok -', m) } | |
| 7 | const stamp = Date.now() | |
| 8 | const STORE = 'data/cli-' + stamp + '/store' // owner storage | |
| 9 | const STORE2 = 'data/cli2-' + stamp + '/store' // a separate mod's storage | |
| 10 | const DID = 'did:plc:member2222222222222222222' | |
| 11 | ||
| 12 | // capture out/err; return the parsed JSON of the last out line (commands use --json) | |
| 13 | function cap () { | |
| 14 | const out = []; const err = [] | |
| 15 | return { io: { out: (s) => out.push(String(s)), err: (s) => err.push(String(s)) }, out, err } | |
| 16 | } | |
| 17 | async function cli (args) { | |
| 18 | const c = cap() | |
| 19 | const code = await run(args, c.io) | |
| 20 | let json = null | |
| 21 | try { json = JSON.parse(c.out[c.out.length - 1]) } catch {} | |
| 22 | return { code, out: c.out, err: c.err, json } | |
| 23 | } | |
| 24 | ||
| 25 | // ── create (owner) ── | |
| 26 | const created = await cli(['create', '--name', 'Test Community', '--storage', STORE, '--json']) | |
| 27 | assert(created.code === 0 && created.json && /^[0-9a-f]{64}$/.test(created.json.roomKey), 'create → roomKey + exit 0') | |
| 28 | assert(created.json.role === 'owner' && /^[0-9a-f]{64}$/.test(created.json.localKey), 'create reports owner + localKey') | |
| 29 | const KEY = created.json.roomKey | |
| 30 | ||
| 31 | // ── read: empty roster, meta from create ── | |
| 32 | const m0 = await cli(['members', KEY, '--storage', STORE, '--json']) | |
| 33 | assert(m0.code === 0 && Array.isArray(m0.json.members) && m0.json.members.length === 0, 'members (fresh) = []') | |
| 34 | const meta0 = await cli(['meta', KEY, '--storage', STORE, '--json']) | |
| 35 | assert(meta0.json.meta && meta0.json.meta.name === 'Test Community', 'meta.name set at create') | |
| 36 | ||
| 37 | // ── owner writes: add-member, then read it back (writer identity persists via the namespace index) ── | |
| 38 | const add = await cli(['add-member', KEY, DID, '--storage', STORE, '--json']) | |
| 39 | assert(add.code === 0, 'add-member → exit 0 (owner is still writable on reopen)') | |
| 40 | const m1 = await cli(['members', KEY, '--storage', STORE, '--json']) | |
| 41 | assert(m1.json.members.length === 1 && m1.json.members[0] === DID, 'members now = [DID]') | |
| 42 | ||
| 43 | // ── moderation: ban removes from roster ── | |
| 44 | await cli(['ban', KEY, DID, '--storage', STORE, '--json']) | |
| 45 | const m2 = await cli(['members', KEY, '--storage', STORE, '--json']) | |
| 46 | assert(m2.json.members.length === 0, 'ban removes DID from the roster') | |
| 47 | ||
| 48 | // ── set-meta update ── | |
| 49 | await cli(['set-meta', KEY, 'name', 'Renamed', '--storage', STORE, '--json']) | |
| 50 | const meta1 = await cli(['meta', KEY, '--storage', STORE, '--json']) | |
| 51 | assert(meta1.json.meta.name === 'Renamed', 'set-meta updates name') | |
| 52 | ||
| 53 | // ── mod (separate storage): join prints a localKey; writing without admission is refused ── | |
| 54 | const join = await cli(['join', KEY, '--storage', STORE2, '--json']) | |
| 55 | assert(join.code === 0 && /^[0-9a-f]{64}$/.test(join.json.localKey) && join.json.writable === false, 'join → mod localKey, not yet writable') | |
| 56 | const badWrite = await cli(['add-member', KEY, DID, '--storage', STORE2, '--json']) | |
| 57 | assert(badWrite.code === 1 && badWrite.err.some((e) => /not a writer/.test(e)), 'un-admitted mod CANNOT write (add-member refused)') | |
| 58 | ||
| 59 | // ── local-key is stable for the mod (same as join) ── | |
| 60 | const lk = await cli(['local-key', KEY, '--storage', STORE2, '--json']) | |
| 61 | assert(lk.json.localKey === join.json.localKey, 'local-key is stable for a joined mod (namespace index)') | |
| 62 | ||
| 63 | // ── errors / help ── | |
| 64 | const bad = await cli(['members', 'not-a-key', '--storage', STORE, '--json']) | |
| 65 | assert(bad.code === 1 && bad.err.some((e) => /64-hex roomKey/.test(e)), 'bad roomKey → exit 1 + message') | |
| 66 | const help = await cli(['help']) | |
| 67 | assert(help.code === 0 && help.out.some((l) => /favela-community/.test(l) && /create/.test(l)), 'help prints usage') | |
| 68 | const none = await cli([]) | |
| 69 | assert(none.code === 1, 'no command → exit 1 (usage)') | |
| 70 | ||
| 71 | console.log('\nFAVELA-COMMUNITY CLI OK (Bare-native): owner create/manage/read + mod-not-admitted guard + errors') |