#!/usr/bin/env node
// favela-markdown — the CLI. library.md §9.
//
// "Every projection defect so far has been diagnosed by reading daemon logs. This makes it a
// one-liner against a file, offline."
//
// ⚠ This file MAY use Node builtins; lib/ may not (§3 — the peer imports lib/ under Bare, and
// nothing imports bin/). Keep the boundary: all logic lives in lib/, this is argv and printing.
import { readFileSync } from 'node:fs'
import process from 'node:process'
import { strip } from '../lib/strip.js'
import { lint } from '../lib/lint.js'
import { parse } from '../lib/record.js'
const USAGE = `favela-markdown — the record codec, offline
favela-markdown strip <file> text + facets
favela-markdown lint <file> [--room KIND] frontmatter, reach, directives, mentions
favela-markdown project <file> --target T not implemented — needs a projection target
favela-markdown explain <file> not implemented — needs a projection target
--room KIND public | followers | members | dm
Without it, lint makes NO claim about reach: the room is what sets the
ceiling, and a file on disk does not say which room it belongs to.
`
const SEV = { error: '⛔', warn: '⚠ ', info: ' ·' }
function read (file) {
if (!file) { process.stderr.write('missing <file>\n\n' + USAGE); process.exit(2) }
try {
return readFileSync(file === '-' ? 0 : file, 'utf8')
} catch (err) {
process.stderr.write('cannot read ' + file + ': ' + err.message + '\n')
process.exit(2)
}
}
function flag (argv, name) {
const i = argv.indexOf(name)
return i >= 0 ? argv[i + 1] : null
}
const [cmd, ...rest] = process.argv.slice(2)
const file = rest.find((a) => !a.startsWith('--')) || null
switch (cmd) {
case 'strip': {
const rec = parse(read(file))
const { text, facets } = strip(rec.body)
process.stdout.write(JSON.stringify({ text, facets }, null, 2) + '\n')
break
}
case 'lint': {
const r = lint(read(file), { roomType: flag(rest, '--room') || undefined })
for (const i of r.issues) {
process.stdout.write(`${SEV[i.severity]} ${i.code} ${i.message}\n ${i.spec}\n`)
}
if (!r.issues.length) process.stdout.write('· clean\n')
process.stdout.write(
`\n${r.ok ? 'ok' : 'NOT OK'} · ${r.publishable ? 'publishable' : 'NOT publishable'}` +
(flag(rest, '--room') ? '' : ' · reach unchecked (pass --room to check it)') + '\n')
process.exit(r.ok ? 0 : 1)
}
case 'project':
case 'explain':
// Honest rather than a stub that prints nothing: these need a projection target, and
// project()/fromAS2()/fromBsky() are not written yet.
process.stderr.write(`'${cmd}' is not implemented — it needs project(), which needs a target.\n`)
process.exit(3)
break
default:
process.stdout.write(USAGE)
process.exit(cmd ? 2 : 0)
}