#!/usr/bin/env node
/**
 * bot-relay-mcp unified CLI (v2.1 Phase 4h).
 *
 * Subcommands:
 *   relay doctor              Diagnostic sweep of local install state.
 *                             --remote <url> probes a remote hub instead (v2.1 Phase 7r).
 *   relay init [options]      First-run setup (interactive unless --yes).
 *   relay test                Fresh-install self-check (throwaway relay).
 *   relay generate-hooks      Emit agent hook config (Claude JSON default;
 *                             --codex for Codex config.toml; --all for both).
 *   relay send TO CONTENT     Send a relay message in one line (v2.17.1). Resolves
 *                             the sender's token (env → vault → --mint-if-missing)
 *                             and POSTs /api/send-message. --from NAME sets sender.
 *   relay watch AGENT         Sentinel (v2.18.0): autowake for a terminal agent not
 *                             in Tether. Prints a wake line when AGENT gets new mail
 *                             (event-driven with RELAY_FILESYSTEM_MARKERS). --once.
 *   relay backup [--output]   Snapshot the DB + config into a tar.gz archive.
 *   relay restore PATH        Restore from a tar.gz archive.
 *   relay recover NAME        Clear an agent's registration so it can re-bootstrap
 *                             (filesystem-gated, for lost-token recovery).
 *   relay mint-token NAME     Mint an agent token directly via filesystem access
 *                             (v2.6.0). For external CLI agents whose safety
 *                             monitors block register_agent → use-token in one
 *                             response. See docs/agents/external-cli-setup.md.
 *   relay purge-history NAME  Delete every message + task where the agent is
 *                             sender or recipient (v2.1.6, for reused names).
 *                             Preserves the agent row itself.
 *   relay purge-agents        Prune agent rows with last_seen > N days (v2.2.2;
 *     [--abandoned-since=N]   dry-run by default, --apply to commit). Messages
 *     [--apply]               + tasks are preserved (see purge-history).
 *   relay open [--url U]      Open the dashboard in the default browser
 *                             (v2.2.2; auto-detects host+port from config).
 *   relay list-instances      List all per-instance bot-relay setups
 *                             (v2.4.0). --json for machine-readable output.
 *   relay use-instance ID     Switch the active instance (kubectl-style)
 *                             (v2.4.0). Writes ~/.bot-relay/active-instance.
 *   relay re-encrypt          Rotate at-rest encryption keys across all encrypted
 *                             columns (v2.1 Phase 4b.3). See docs/key-rotation.md.
 *   relay pair HUB_URL        Register with a remote bot-relay-mcp hub + emit an
 *                             MCP client config snippet (v2.1 Phase 7r).
 *   relay help                Show this message.
 *
 * Phase 2c's bin/relay-backup + bin/relay-restore are absorbed into
 * `relay backup` / `relay restore`. Scripts that called the old names
 * should switch to the unified entry.
 */

const SUBCOMMANDS = {
  doctor: "../dist/cli/doctor.js",
  init: "../dist/cli/init.js",
  restart: "../dist/cli/restart.js",
  test: "../dist/cli/test.js",
  "generate-hooks": "../dist/cli/generate-hooks.js",
  "cli-profiles": "../dist/cli/cli-profiles.js",
  send: "../dist/cli/send.js",
  resolve: "../dist/cli/resolve.js",
  watch: "../dist/cli/watch.js",
  backup: "../dist/cli/backup.js",
  restore: "../dist/cli/restore.js",
  recover: "../dist/cli/recover.js",
  "release-binding": "../dist/cli/release-binding.js",
  "mint-token": "../dist/cli/mint-token.js",
  "purge-history": "../dist/cli/purge-history.js",
  "purge-agents": "../dist/cli/purge-agents.js",
  open: "../dist/cli/open.js",
  "list-instances": "../dist/cli/list-instances.js",
  "use-instance": "../dist/cli/use-instance.js",
  "re-encrypt": "../dist/cli/re-encrypt.js",
  pair: "../dist/cli/pair.js",
};

// STREAM DISCIPLINE: usage is DIAGNOSTIC unless it was explicitly requested.
//
// This shipped writing usage to STDOUT unconditionally, so a failed command
// substitution — RELAY_AGENT_TOKEN=$(relay mint-token NAME --json | sed ...) —
// captured 1549 bytes of help text instead of yielding empty. The agent then
// launched with a garbage token, every MCP call returned AUTH_FAILED, and it
// LOOKED HEALTHY throughout. Two real broken launches came from this today.
// A failure whose symptom is a plausible wrong value is the worst shape there
// is, because nothing downstream can tell it from a real one.
//
// `requested` = the operator typed `help`/`--help`/`-h`, so the text IS the
// requested data and belongs on stdout. Every other path is an error: usage
// goes to STDERR so a capture yields EMPTY, which fails loudly and visibly.
function printHelp(requested) {
  const write = requested
    ? (t) => process.stdout.write(t)
    : (t) => process.stderr.write(t);
  write(
    "bot-relay-mcp — unified CLI\n\n" +
      "Usage: relay <subcommand> [options]\n\n" +
      "Subcommands:\n" +
      "  doctor           Diagnostic sweep of the local install (or --remote URL).\n" +
      "  init             First-run setup (interactive; --yes for non-interactive).\n" +
      "  restart          Restart the local launchd daemon so a new package version takes effect (macOS).\n" +
      "  test             Fresh-install self-check against a throwaway relay.\n" +
      "  generate-hooks   Emit agent hook config (Claude JSON default; --codex Codex TOML; --all both).\n" +
      "  cli-profiles     Print the agent-CLI profile registry (--json for machine-readable).\n" +
      "  send TO CONTENT  Send a relay message in one line (resolves the sender's token).\n" +
      "  resolve ID...    Resolve (ack) your own inbox messages — the CLI path for MCP-mute sessions.\n" +
      "  watch AGENT      Sentinel: autowake — print a wake line when AGENT gets new mail.\n" +
      "  backup           Snapshot the DB + config into a tar.gz archive.\n" +
      "  restore PATH     Restore from a tar.gz archive.\n" +
      "  recover NAME     Clear an agent's registration so it can re-bootstrap.\n" +
      "  release-binding NAME  Clear a STALE binding (refuses on a live anchor) so the next launch re-binds; preserves identity.\n" +
      "  mint-token NAME  Mint an agent token via filesystem (external-CLI setup).\n" +
      "  purge-history NAME  Delete every message + task where the agent is sender or recipient.\n" +
      "  purge-agents     Prune agent rows with last_seen > N days (dry-run; --apply commits).\n" +
      "  open [--url U]   Open the dashboard in the default browser.\n" +
      "  list-instances   List all per-instance bot-relay setups (v2.4.0).\n" +
      "  use-instance ID  Switch the active instance (v2.4.0, kubectl-style).\n" +
      "  re-encrypt       Rotate at-rest encryption keys across all encrypted columns.\n" +
      "  pair HUB_URL     Register with a remote hub + emit MCP client config.\n" +
      "  help             Show this message.\n\n" +
      "Run `relay <subcommand> --help` for per-subcommand details.\n"
  );
}

async function main() {
  const argv = process.argv.slice(2);
  const askedForHelp =
    argv[0] === "help" || argv[0] === "--help" || argv[0] === "-h";
  if (askedForHelp) {
    printHelp(true); // requested → stdout, exit 0
    process.exit(0);
  }
  if (argv.length === 0) {
    // NO ARGS IS AN ERROR, not a help request. Exiting 0 here was half the
    // reason the poisoned-substitution failure was silent: `set -e` and `||`
    // guards both saw success.
    printHelp(false);
    process.exit(2);
  }

  const sub = argv[0];
  const rest = argv.slice(1);
  const modulePath = SUBCOMMANDS[sub];
  if (!modulePath) {
    process.stderr.write(`relay: unknown subcommand "${sub}"\n\n`);
    printHelp(false); // error path → stderr
    process.exit(1);
  }

  try {
    const mod = await import(modulePath);
    const code = await mod.run(rest);
    process.exit(typeof code === "number" ? code : 0);
  } catch (err) {
    process.stderr.write(`relay ${sub}: ${err instanceof Error ? err.message : String(err)}\n`);
    process.exit(1);
  }
}

main();
