morphit/apps/ops-cli
Morphit Team 7c1c3bb332
Some checks failed
morphit-ci / TypeScript typecheck (sweep all workspaces) (push) Has been cancelled
morphit-ci / apps/web svelte-check (svelte-kit sync + svelte-aware tsc) (push) Has been cancelled
morphit-ci / Integration tests (real Postgres 16) (push) Has been cancelled
morphit-ci / ansible-lint (playbook quality gate) (push) Has been cancelled
morphit-ci / Smoke suite (run-smokes.sh, triple-pulse) (push) Has been cancelled
morphit-release / Build + publish release tarball (push) Has been cancelled
v1.11.1 — registration self-heal + data-fs disk health + offline-install RPC softening + node-health unification (cp707-cp710)
2026-08-11 17:38:41 -07:00
..
bin cp653-659: offline install through all roles (docker images, mcp user, ipfs deferral) + Step-N-of-N wizard + password step + upgrade mirror rotation + wizard net-loss guard 2026-08-05 18:02:22 -07:00
scripts v1.11.1 — registration self-heal + data-fs disk health + offline-install RPC softening + node-health unification (cp707-cp710) 2026-08-11 17:38:41 -07:00
src v1.11.1 — registration self-heal + data-fs disk health + offline-install RPC softening + node-health unification (cp707-cp710) 2026-08-11 17:38:41 -07:00
test true-offline install, honest home bring-up, Tor/I2P serve the marketplace 2026-08-09 21:04:17 -07:00
.prettierignore cp653-659: offline install through all roles (docker images, mcp user, ipfs deferral) + Step-N-of-N wizard + password step + upgrade mirror rotation + wizard net-loss guard 2026-08-05 18:02:22 -07:00
.prettierrc cp653-659: offline install through all roles (docker images, mcp user, ipfs deferral) + Step-N-of-N wizard + password step + upgrade mirror rotation + wizard net-loss guard 2026-08-05 18:02:22 -07:00
package.json v1.11.1 — registration self-heal + data-fs disk health + offline-install RPC softening + node-health unification (cp707-cp710) 2026-08-11 17:38:41 -07:00
README.md cp653-659: offline install through all roles (docker images, mcp user, ipfs deferral) + Step-N-of-N wizard + password step + upgrade mirror rotation + wizard net-loss guard 2026-08-05 18:02:22 -07:00
tsconfig.json cp653-659: offline install through all roles (docker images, mcp user, ipfs deferral) + Step-N-of-N wizard + password step + upgrade mirror rotation + wizard net-loss guard 2026-08-05 18:02:22 -07:00
tsconfig.smoke-typecheck.json cp653-659: offline install through all roles (docker images, mcp user, ipfs deferral) + Step-N-of-N wizard + password step + upgrade mirror rotation + wizard net-loss guard 2026-08-05 18:02:22 -07:00

morphit-ops

Operator command-line tool for a Morphit instance. Read-mostly view into the indexer + relay shared database: status snapshot, drain queue depth, recent signups, abuse signals, moderation flags. Designed to be run on the same VPS that hosts the indexer and relay, over SSH.

The CLI does NOT mutate state. Everything it shows is sourced from queries against the same Postgres the indexer and relay write to; no admin endpoints are involved. This keeps the attack surface small (read-only Postgres connection is the only auth the CLI needs).

The init subcommand is the exception — it's a first-time setup wizard that writes morphit.config.env and a posting-key keystore to disk. See "First-time setup" below.

First-time setup

Run on a fresh checkout to generate your morphit.config.env and active-key keystore:

cd apps/ops-cli
npx tsx src/main.ts init

The wizard:

  1. Runs a system check (CPU, RAM, disk, OS version, Postgres reachability, outbound HTTPS). Catches issues before you commit time to interactive prompts.
  2. Walks you through 18 setup steps with ELI5 explanations.
  3. Validates each input (Blurt account names checked against the chain, database URL parsed, etc.).
  4. Shows a review and asks for confirmation.
  5. Writes morphit.config.env and apps/relay/keystore.{wif,json} with 0600 permissions.

If you only want to verify your hardware/OS meets the bar before committing time to setup:

npx tsx src/main.ts init --check-only

init works on a fresh checkout where npm install hasn't been run yet — it has no third-party dependencies beyond what ships in Node.js.

Publish your instance to the federation

After your indexer + relay + frontend are up and serving correctly at your public origin, run:

# Source your wizard-generated env files so register can
# read them:
set -a; . ./morphit.env; . ./morphit.config.env; set +a

npx tsx apps/ops-cli/src/main.ts register

This posts a morphit_operator_register_v1 op on the Blurt chain. Within ~10 minutes every Morphit indexer (including morphit.io and your own) will see it via chain replay, probe your origin to verify it's serving correctly, and add your instance to their /instances directory.

You can verify your registration landed by visiting your own /instances page — you should appear with status good and a "You are here" badge.

The register subcommand requires npm install to have run (it dynamically loads @beblurt/dblurt for chain broadcasting).

Quick start (after init)

cd apps/ops-cli
npm install
export MORPHIT_OPS_DATABASE_URL=postgres://morphit:secret@localhost:5432/morphit
npx tsx src/main.ts status

You should see a multi-section dashboard summarizing indexer health, drain queue, today's signups, and 24h moderation flags.

Configuration

The CLI reads its database connection from environment variables. Only the database URL is required; everything else has a sensible default.

Variable Required Default Notes
MORPHIT_OPS_DATABASE_URL Yes (or alt below) Postgres connection string
MORPHIT_INDEXER_DATABASE_URL Alt Falls back to this if MORPHIT_OPS_DATABASE_URL is unset
DATABASE_URL Alt Final fallback
MORPHIT_OPS_RELAY_ACCOUNT No morphit-relay Matched against accounts.creator for signup queries
MORPHIT_OPS_FEES_ACCOUNT No morphit-fees Currently informational; used in future subcommands
MORPHIT_RELAY_SIGNUP_DAILY_CEILING No 50 Snapshot of relay's ceiling, used to compute "X / Y" on dashboard
MORPHIT_OPS_COLOR No auto auto (TTY-aware), always, or never. NO_COLOR env also honored

Threshold tunables

Each metric on the status dashboard maps to a status glyph ( / / ) by comparing its value to a warn/error threshold pair. Defaults follow the audit-recommended values. Override any of them via env:

Env variable Default Maps to
MORPHIT_OPS_THRESHOLD_RELAY_BALANCE_WARN 100 Warn below this many BLURT
MORPHIT_OPS_THRESHOLD_RELAY_BALANCE_ERROR 30 Error below this many BLURT
MORPHIT_OPS_THRESHOLD_DRAIN_AGE_WARN_SEC 300 (5m) Warn when oldest pending exceeds this
MORPHIT_OPS_THRESHOLD_DRAIN_AGE_ERROR_SEC 3600 (1h) Error when oldest pending exceeds this
MORPHIT_OPS_THRESHOLD_INDEXER_LAG_WARN_BLOCKS 5 Warn when indexer is N+ blocks behind
MORPHIT_OPS_THRESHOLD_INDEXER_LAG_ERROR_BLOCKS 30 Error when indexer is N+ blocks behind
MORPHIT_OPS_THRESHOLD_SIGNUPS_PCT_WARN 80 Warn at this % of daily ceiling
MORPHIT_OPS_THRESHOLD_SIGNUPS_PCT_ERROR 100 Error at this % of daily ceiling
MORPHIT_OPS_THRESHOLD_ABUSE_WARN 10 Warn at this many flags in 24h
MORPHIT_OPS_THRESHOLD_ABUSE_ERROR 50 Error at this many flags in 24h

Subcommands

Subcommand What it shows
init First-time setup wizard (run on a fresh install)
register Publish operator registration on-chain (Phase D.5)
status One-screen dashboard: indexer state, drain queue, signups today, moderation flags 24h
drain-queue [--age=DUR] Pending relay transfers, oldest first. --age=1h shows entries waiting >1h
signups [--since=DUR] Accounts created via this relay. Default window: 24h
abuse [--since=DUR] Combined view: persistent broadcast failures + new reciprocity/related-account flags
failed-broadcasts [--since=DUR] Relay broadcasts that errored, with error messages
loyalty [--since=DUR] Loyalty milestone delegations triggered. Default window: 7d
attestations Orders awaiting fee-attestation verification (BTC/XMR fee path)
flags [--type=reciprocity|related] [--since=DUR] Moderation flags drill-down

Global flags

  • --json — emit JSON instead of human-formatted output, suitable for piping to jq
  • --no-color — disable ANSI color (also honored: NO_COLOR env var)
  • --help, -h — show usage
  • --version, -v — show version

init-specific flags

  • --check-only — run the system check, print results, and exit (no prompts)
  • --out=PATH — write morphit.config.env to PATH instead of the repo root

Duration spec (DUR)

Number followed by unit: s (seconds), m (minutes), h (hours), d (days). Examples: 30s, 5m, 24h, 7d. Case-insensitive on the unit; whitespace between number and unit is tolerated (5 m works).

JSON output

Every subcommand accepts --json. Output is a single document to stdout, suitable for piping. Examples:

# How many failed broadcasts had errors > 5 minutes ago?
morphit-ops failed-broadcasts --json | jq '.entries | map(select(.error_count >= 5)) | length'

# Recent signups as a CSV-like list
morphit-ops signups --json | jq -r '.entries[] | [.name, .created_block_time] | @tsv'

# Alert if drain queue oldest age > 1 hour
oldest=$(morphit-ops status --json | jq '.drain_queue.oldest_age_sec // 0')
[ "$oldest" -gt 3600 ] && echo "ALERT: drain queue stuck"

Troubleshooting

"No database URL configured." Set MORPHIT_OPS_DATABASE_URL to the same connection string the indexer uses. See your operator config or systemd unit.

Status shows 0 signups but you know there were some. The CLI matches accounts.creator = MORPHIT_OPS_RELAY_ACCOUNT. If your relay's account name isn't morphit-relay, set MORPHIT_OPS_RELAY_ACCOUNT to the actual name.

Failed-broadcasts list is empty but the relay's logs show errors. The CLI sees only persisted DB state. In-flight or never-queued errors (e.g., the relay's active-key unlock failed at startup) don't appear here. Check the relay's structured logs for those.

Color is wrong / glyphs are blank. Some basic SSH sessions have spotty UTF-8 support. Pass --no-color for ASCII-only output ([OK], [WARN], [ERR]).

What's NOT in v1

The following are deliberately deferred until a follow-up phase:

  • Operations subcommands (drain-now, pause-signups, set-ceiling, top-up-balance). These would mutate live state and require either a relay HTTP admin endpoint with signed-challenge auth, or a coordination mechanism through Postgres. v1 is read-only by intent.
  • Operator monitoring web UI. We deliberately ship a CLI instead — fewer attack surfaces, scriptable, fits the operator's SSH-into-the-VPS workflow.
  • Multi-relay views. The CLI assumes one relay account per instance. Operators running multiple relays would invoke the CLI multiple times with different MORPHIT_OPS_RELAY_ACCOUNT values.

Source map

src/
├── main.ts                       Entry point + tiny arg parser
├── config.ts                     Env vars + threshold tunables
├── db.ts                         pg.Pool wrapper
├── render/
│   ├── term.ts                   ANSI colors, glyphs, sections
│   └── json.ts                   --json emitter
├── lib/
│   ├── ctx.ts                    Shared CommandCtx type
│   └── time.ts                   Duration/time formatting helpers
└── commands/
    ├── status.ts                 Dashboard
    ├── drainQueue.ts             Pending transfers
    ├── signups.ts                Recent signups
    ├── abuse.ts                  Combined abuse signals
    ├── failedBroadcasts.ts       Errored broadcasts
    ├── loyalty.ts                Milestones
    ├── attestations.ts           Pending fee attestations
    └── flags.ts                  Moderation flags

scripts/
└── ops-cli-smoke.ts              35-scenario smoke runner

test/
└── time.test.ts                  Vitest mirror of the smoke (for when vitest is installed)

License

AGPL-3.0-only — same as the rest of Morphit.