CLI Helpers

Shared shape for blamejs CLI subcommands AND for operators writing their own one-shot CLI scripts on top of the framework. Three patterns recur across every CLI command (migrate, seed, audit, vault, backup, api-key): bootstrap a headless b.createApp instance from --data-dir and shut it down cleanly; report success / error / usage with a consistent blamejs : prefix on stderr plus canonical exit codes (0 ok, 1 runtime failure, 2 arg error); resolve a passphrase from a -- or env var into the Buffer shape the underlying crypto primitives accept.

The reporter writes through whichever stream ctx.stdout / ctx.stderr point at — process.stdout / process.stderr in production, captured stream stubs in tests — so the same handler is testable without spawning a child process.

b.cliHelpers.makeReporter(ctx, prefix) #

stable0.8.0

Build a reporter bound to a CLI context (ctx.stdout / ctx.stderr) and a verb prefix. Every stderr message produced by .error / .usage gets the prefix. Methods return canonical Unix exit codes — 0 for ok / helpStdout, 1 for error (override via second arg), 2 for usage (argument-error convention).

var ctx = { stdout: process.stdout, stderr: process.stderr };
var report = b.cliHelpers.makeReporter(ctx, "blamejs vault seal");
var ok   = report.ok("sealed: /data/vault");        // → 0
var fail = report.error("decrypt failed");          // → 1
var arg  = report.error("missing --data-dir", 2);   // → 2
var help = report.usage("Usage: blamejs vault ..."); // → 2

b.cliHelpers.resolvePassphrase(args, ctx, opts) #

stable0.8.0
{
  flag:    string  (CLI flag name, e.g. "passphrase" reads args.flags.passphrase),
  envVar:  string  (env var fallback, e.g. "BLAMEJS_VAULT_PASSPHRASE"),
}

Resolve a passphrase from a CLI flag (preferred) or an env var (fallback) into a UTF-8 Buffer — the shape vault / crypto primitives accept. Returns null when neither source produced a non-empty string; the caller decides whether absence is a hard error (vault seal) or a soft default (plaintext-mode dev data dir).

var args = { flags: { passphrase: "hunter2" } };
var ctx  = { env: { BLAMEJS_VAULT_PASSPHRASE: "envval" } };
var pp = b.cliHelpers.resolvePassphrase(args, ctx, {
  flag:   "passphrase",
  envVar: "BLAMEJS_VAULT_PASSPHRASE",
});
pp.toString("utf8");     // → "hunter2"  (flag wins over env)

var none = b.cliHelpers.resolvePassphrase({ flags: {} }, { env: {} }, {
  flag: "passphrase", envVar: "BLAMEJS_VAULT_PASSPHRASE",
});
none;                    // → null

b.cliHelpers.bootApp(opts) #

stable0.8.0
{
  dataDir:    string   (filesystem path to the data dir; required),
  vaultMode:  "wrapped" | "plaintext"   (default "wrapped" — wrapped
              reads BLAMEJS_VAULT_PASSPHRASE from `opts.env`),
  dbAtRest:   "plain" | "encrypted"     (default "plain"),
  env:        object    (env-var bag; default process.env),
}

Boot a headless b.createApp instance from a data dir so a CLI script (framework subcommand or operator-written tool) can operate against the same vault + DB + audit chain the live app uses, with no HTTP listener attached. Returns { b, app } where b is the framework module and app is the headless instance — caller MUST await booted.app.shutdown() in a finally so SQLite file handles and the cluster lease release.

The default DB at-rest mode is plain because CLI runs are short-lived ops that never serve requests; encrypted-at-rest needs a tmpfs handle that wouldn't survive CLI exit anyway. Operators running against a production data dir whose DB is encrypted-at-rest pass dbAtRest: "encrypted" and ensure BLAMEJS_TMPDIR is set.

async function run() {
  var booted;
  try {
    booted = await b.cliHelpers.bootApp({
      dataDir:   "./data",
      vaultMode: "plaintext",
      env:       process.env,
    });
    var rows = await booted.app.db.all("SELECT count(*) AS n FROM _blamejs_audit_log");
    return rows[0].n;
  } finally {
    if (booted) await booted.app.shutdown();
  }
}

Last updated 2026-08-08T16:39:49.652Z by seeder.