// bootstrap-global-registry.js — one-shot to create the global registry
// room + readOnly invite that godot-keet and hyper-tps bake into their
// source as GLOBAL_REGISTRY_ROOM_ID + GLOBAL_REGISTRY_INVITE.
//
// PRECONDITIONS:
// 1. No other keet-daemon process running against ~/.config/keet-cli/
// (RocksDB takes an exclusive lock — stop godot-keet + hyper-tps first)
// 2. ~/.config/keet-cli/ has an active identity with a valid vault
// (i.e. you've used godot-keet or hyper-tps before on this machine)
// 3. keet-daemon source has `npm install` done + postinstall ran
//
// Run from packages/keet-daemon:
// $ bare scripts/bootstrap-global-registry.js
//
// Output: two lines to paste into the two source files. Exits cleanly.
import path from 'bare-path'
import os from 'bare-os'
import { startDaemon } from '../index.js'
const TITLE = 'Hyper-Fleeky Global Registry'
const SOCKET = '/tmp/keet-daemon-bootstrap.sock' // separate from production sock
const STORAGE = path.join(os.homedir(), '.config', 'keet-cli')
async function main () {
console.log('[bootstrap] storage:', STORAGE)
console.log('[bootstrap] socket:', SOCKET)
console.log('[bootstrap] starting source-tree daemon...')
const daemon = await startDaemon({ storage: STORAGE, socketPath: SOCKET })
console.log('[bootstrap] daemon ready')
const router = daemon.router
try {
console.log('[bootstrap] creating registry room: ' + TITLE)
const createRes = await router.handle('room.create', { title: TITLE, roomType: 0 })
const roomId = (createRes && (createRes.data?.roomId || createRes.data || createRes.roomId)) || null
if (!roomId || typeof roomId !== 'string') {
throw new Error('room.create returned no roomId: ' + JSON.stringify(createRes))
}
console.log('[bootstrap] roomId: ' + roomId)
// NOTE: meta.set is daemon-supported per SYSTEMATIC_PLAN.md but if the
// installed router doesn't expose it yet, this is a no-op marker. The
// registry pattern works on _hs:room-listing messages regardless of
// meta.type. Phase B of the plan will surface meta.set once the UI
// wiring catches up.
try {
await router.handle('meta.set', {
roomId,
metadata: { type: 'registry', topics: ['public', 'discovery'], discoverable: true }
})
console.log('[bootstrap] meta.set OK (type=registry)')
} catch (err) {
console.log('[bootstrap] meta.set skipped: ' + err.message)
}
console.log('[bootstrap] creating readOnly subscribe invite...')
const inviteRes = await router.handle('room.invite', { roomId, readOnly: true })
const token = (inviteRes && (inviteRes.data?.token || inviteRes.token)) || null
if (!token || typeof token !== 'string') {
throw new Error('room.invite returned no token: ' + JSON.stringify(inviteRes))
}
console.log('[bootstrap] creating WRITE curator invite (save this PRIVATELY)...')
const curatorRes = await router.handle('room.invite', { roomId, role: 'member' })
const curatorToken = (curatorRes && (curatorRes.data?.token || curatorRes.token)) || null
console.log('')
console.log('==================== PASTE INTO SOURCE ====================')
console.log('')
console.log(' GLOBAL_REGISTRY_ROOM_ID = ' + JSON.stringify(roomId))
console.log(' GLOBAL_REGISTRY_INVITE = ' + JSON.stringify(token))
console.log('')
console.log('============ KEEP CURATOR INVITE PRIVATE ============')
console.log('')
console.log(' curator (write) invite: ' + curatorToken)
console.log('')
console.log(' → hand out narrowly to people allowed to register new rooms')
console.log('=============================================================')
} finally {
console.log('[bootstrap] shutting down daemon...')
try { await daemon.uds.stop() } catch {}
try { await daemon.router.core.close() } catch {}
try { await daemon.core.close() } catch {}
console.log('[bootstrap] done.')
}
Bare.exit(0)
}
main().catch(err => {
console.error('[bootstrap] FAILED:', err.message)
if (err.stack) console.error(err.stack)
Bare.exit(1)
})