#!/usr/bin/env node
/**
* Mini CLI example for trust-map
*
* Usage:
* node examples/cli.js init Init storage
* node examples/cli.js grant <pubkey> <resource> <perms,...> Grant perms
* node examples/cli.js revoke <pubkey> <resource> [perms,...] Revoke
* node examples/cli.js check <pubkey> <resource> <perm> Check
* node examples/cli.js list List all
* node examples/cli.js for <pubkey> List for one pubkey
* node examples/cli.js demo Generate keypairs and play
*
* Storage path: TRUST_MAP_STORE env var, default ./.trust-map-cli/
*
* NOTE: this CLI uses its OWN storage path by default. The web example
* (examples/web.js) uses ./.trust-map-web/ — they don't share state.
* RocksDB has a single-writer lock, so two processes can't open the same
* Corestore at once. For shared state across CLI/web/native interfaces in
* production, embed trust-map in a single daemon (e.g. pear-git-v3) and
* have all clients talk to the daemon via hyper-svc (planned).
*/
import Corestore from 'corestore'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import { TrustMap } from '../index.js'
const args = process.argv.slice(2)
const cmd = args[0]
const storagePath = process.env.TRUST_MAP_STORE || './.trust-map-cli'
async function open () {
const store = new Corestore(storagePath)
await store.ready()
const core = store.get({ name: 'trust-map' })
await core.ready()
const tm = new TrustMap({ core })
await tm.ready()
return { tm, store, core }
}
async function close ({ tm, store, core }) {
await tm.close()
await core.close()
await store.close()
}
async function main () {
if (cmd === 'init') {
const ctx = await open()
console.log('Initialized trust map at: %s', storagePath)
await close(ctx)
return
}
if (cmd === 'grant') {
const [, pubkey, resource, permsStr] = args
if (!pubkey || !resource || !permsStr) {
console.error('Usage: cli.js grant <pubkey> <resource> <perm1,perm2,...>')
process.exit(1)
}
const perms = permsStr.split(',').map(s => s.trim())
const ctx = await open()
await ctx.tm.grant(pubkey, resource, perms)
console.log('Granted %s on %s to %s', perms.join(','), resource, pubkey.slice(0, 16) + '...')
await close(ctx)
return
}
if (cmd === 'revoke') {
const [, pubkey, resource, permsStr] = args
if (!pubkey || !resource) {
console.error('Usage: cli.js revoke <pubkey> <resource> [perm1,perm2,...]')
process.exit(1)
}
const perms = permsStr ? permsStr.split(',').map(s => s.trim()) : undefined
const ctx = await open()
await ctx.tm.revoke(pubkey, resource, perms)
console.log('Revoked %s on %s for %s', perms ? perms.join(',') : '(all)', resource, pubkey.slice(0, 16) + '...')
await close(ctx)
return
}
if (cmd === 'check') {
const [, pubkey, resource, perm] = args
if (!pubkey || !resource || !perm) {
console.error('Usage: cli.js check <pubkey> <resource> <perm>')
process.exit(1)
}
const ctx = await open()
const ok = await ctx.tm.check(pubkey, resource, perm)
console.log('check(%s, %s, %s) = %s', pubkey.slice(0, 16) + '...', resource, perm, ok ? 'YES' : 'NO')
await close(ctx)
return
}
if (cmd === 'list') {
const ctx = await open()
const all = await ctx.tm.list()
if (all.length === 0) {
console.log('(empty)')
} else {
console.log('Trust map (%d entries):', all.length)
for (const e of all) {
console.log(' %s %s %s', e.pubkey.slice(0, 16) + '...', e.resource.padEnd(20), e.perms.join(','))
}
}
await close(ctx)
return
}
if (cmd === 'for') {
const pubkey = args[1]
if (!pubkey) { console.error('Usage: cli.js for <pubkey>'); process.exit(1) }
const ctx = await open()
const entries = await ctx.tm.listForPubkey(pubkey)
if (entries.length === 0) {
console.log('No entries for %s', pubkey.slice(0, 16) + '...')
} else {
console.log('Entries for %s:', pubkey.slice(0, 16) + '...')
for (const e of entries) {
console.log(' %s: %s', e.resource, e.perms.join(','))
}
}
await close(ctx)
return
}
if (cmd === 'demo') {
const ctx = await open()
console.log('--- trust-map demo ---\n')
const alice = b4a.toString(crypto.randomBytes(32), 'hex')
const bob = b4a.toString(crypto.randomBytes(32), 'hex')
const ci = b4a.toString(crypto.randomBytes(32), 'hex')
console.log('Alice: %s', alice)
console.log('Bob: %s', bob)
console.log('CI bot: %s\n', ci)
console.log('1. Grant alice admin on repo-x')
await ctx.tm.grant(alice, 'repo-x', ['read', 'write', 'admin'])
console.log('2. Grant bob read+write on repo-x')
await ctx.tm.grant(bob, 'repo-x', ['read', 'write'])
console.log('3. Grant CI bot read on ALL repos (wildcard)')
await ctx.tm.grant(ci, '*', ['read'])
console.log()
console.log('Checks:')
console.log(' alice can admin repo-x: %s', await ctx.tm.check(alice, 'repo-x', 'admin'))
console.log(' bob can write repo-x: %s', await ctx.tm.check(bob, 'repo-x', 'write'))
console.log(' bob can admin repo-x: %s (should be NO)', await ctx.tm.check(bob, 'repo-x', 'admin'))
console.log(' ci can read repo-y: %s (via wildcard)', await ctx.tm.check(ci, 'repo-y', 'read'))
console.log(' ci can write repo-y: %s (should be NO)', await ctx.tm.check(ci, 'repo-y', 'write'))
console.log()
console.log('4. Revoke bob\'s write on repo-x')
await ctx.tm.revoke(bob, 'repo-x', ['write'])
console.log(' bob can write repo-x: %s (should be NO now)', await ctx.tm.check(bob, 'repo-x', 'write'))
console.log(' bob can read repo-x: %s (still YES)', await ctx.tm.check(bob, 'repo-x', 'read'))
await close(ctx)
return
}
console.log('trust-map example CLI')
console.log(' init Initialize storage')
console.log(' grant <pubkey> <resource> <perms,...> Grant permissions')
console.log(' revoke <pubkey> <resource> [perms,...] Revoke permissions')
console.log(' check <pubkey> <resource> <perm> Check a permission')
console.log(' list List all entries')
console.log(' for <pubkey> List entries for one pubkey')
console.log(' demo Run a full demo with random keypairs')
console.log('')
console.log('Storage: TRUST_MAP_STORE env var (default: ./.trust-map-cli)')
console.log('Note: this CLI does NOT share state with examples/web.js — they use')
console.log('different storage paths because RocksDB only allows one writer.')
}
main().catch((err) => { console.error(err.message); process.exit(1) })