Skip to main content

CLI (hn)

Overview

The hn CLI (@hn-monorepo/cli) is the central entry point for monorepo tooling. It replaces scattered bash scripts with a single command that provides interactive menus, argument-based direct execution, and Clack-based visual output.

bun hn                           # Interactive mode — pick a category, then a command
bun hn setup ssh # Direct — run SSH setup
bun hn env generate lexilink # Direct — generate .env.op for lexilink
bun hn --help # Show all available commands

Quick Reference

CommandDescription
bun hnInteractive category and command picker
bun hn setup sshConfigure SSH access to HanseNexus infrastructure
bun hn env generateGenerate .env.op files (interactive app/env selection)
bun hn env generate <app>Generate .env.op for a specific app (prompts for environment)
bun hn env generate <app> --env <env>Generate .env.op for a specific app and environment
bun hn env generate --allBatch generate all .env.op files, no prompts
bun hn --helpPrint available categories and commands

Installation

The CLI is included in the monorepo as packages/cli/. After cloning and running bun install, it is available via:

bun hn

No global install is needed. The root package.json defines the hn script:

{
"scripts": {
"hn": "bun run packages/cli/src/cli.ts"
}
}

Commands

hn setup ssh

Configures SSH access to HanseNexus infrastructure servers. This is the TypeScript port of scripts/setup-ssh.sh with interactive Clack UI.

What it does:

  1. Checks prerequisites — 1Password CLI auth, Tailscale connectivity, SSH Agent socket
  2. Exports public keys — fetches from 1Password vault (Hubby) to ~/.ssh/
  3. Configures SSH hosts — writes a managed block in ~/.ssh/config for hn-hub, hn-k3s, hn-runner, hn-mail
  4. Tests connections — verifies SSH connectivity to each host

Prerequisites:

  • 1Password desktop app with SSH Agent enabled (or OP_SERVICE_ACCOUNT_TOKEN)
  • Tailscale connected to the HanseNexus network
  • Access to the "Hubby" vault in 1Password

Behavior:

  • Idempotent — safe to run multiple times. Uses marker comments (# --- hn-infra-start/end ---) to manage the config block
  • Non-destructive — backs up ~/.ssh/config before modifying
  • Detects manual config — if host entries exist outside the managed block, it warns instead of overwriting
bun hn setup ssh
┌  HanseNexus SSH Setup

◆ 1Password CLI — authenticated
◆ Tailscale — connected
◆ SSH Agent socket — found

◆ 1p_hn_ssh.pub — up to date
◆ 1p_hn_runner.pub — up to date

◆ Updated existing hn-infra block
● Backup → ~/.ssh/config.bak

◆ hn-hub → hn-hub
◆ hn-k3s → hn-k3s
◆ hn-runner → hn-runner
■ hn-mail — host key not in known_hosts

└ 3/4 connections successful

Configured hosts:

HostAddressUserKey
hn-hub100.82.96.102 (Tailscale)clawdbot1p_hn_ssh.pub
hn-k3s100.90.51.49 (Tailscale)root1p_hn_ssh.pub
hn-runner159.69.123.137 (Public)root1p_hn_runner.pub
hn-mail159.69.247.106 (Public)root1p_hn_ssh.pub

hn env generate

Generates .env.op files containing op:// references for 1Password secret resolution. This is the TypeScript port of scripts/generate-env-op.sh with interactive Clack UI.

What it does:

  1. Fetches item fields from the 1Password k3 vault as JSON
  2. Generates op:// reference lines for each field matching SCREAMING_SNAKE_CASE
  3. Merges non-secret defaults from .env.development (without overwriting op:// entries)
  4. Writes the result to .env.op (or .env.op.<env> for non-prod)

Prerequisites:

  • 1Password CLI authenticated (via OP_SERVICE_ACCOUNT_TOKEN or interactive op signin)

Usage modes:

# Interactive — prompts for app and environment
bun hn env generate

# Specify app — prompts for environment (if multiple available)
bun hn env generate lexilink

# Fully specified — no prompts
bun hn env generate lexilink --env staging

# Batch — generate all apps and environments
bun hn env generate --all
┌  Generate .env.op

● Using 1Password Service Account

◆ 26 fields from lexilink-staging
◆ 3 defaults merged from .env.development
◆ Written to apps/lexilink/.env.op.staging (29 vars)

└ Done

Supported apps:

App1Password ItemsEnvironments
calnexuscalnexus-{env}, convex-calnexus-{env}prod
lexilinklexilink-{env}, convex-lexilink-{env}prod, staging, preview, dev
planexplanex-{env}, convex-planex-{env}prod
bgs-servicebgs-service-{env}, convex-bgs-service-{env}prod
nexus-lmsnexus-lms-{env}prod
portfolioportfolio-{env}prod
qriptqript-{env}prod
elbe-akustikelbe-akustik-{env}prod

Output file convention:

  • Production: apps/<app>/.env.op
  • Other environments: apps/<app>/.env.op.<env> (e.g., .env.op.staging)

For more on how .env.op files are used at runtime, see Secrets Management.

Interactive Mode

When run without arguments (or with only a category), the CLI presents an interactive menu using @clack/prompts:

bun hn             # Pick category → pick command → run
bun hn setup # Pick command within "setup" → run
bun hn env # Pick command within "env" → run

The interactive menus support keyboard navigation and cancellation (Ctrl+C).

Architecture

The CLI lives at packages/cli/ and follows a registry pattern for extensibility:

packages/cli/
├── package.json # @hn-monorepo/cli
├── tsconfig.json
└── src/
├── cli.ts # Entry point — arg routing or interactive fallback
├── interactive.ts # Clack category → command picker
├── registry.ts # Categories and commands registry
├── ui.ts # Clack UI helpers (intro, outro, success, error)
├── shell.ts # Bun.spawn wrappers (spawn, commandExists, opItemGet)
└── commands/
├── types.ts # Command and Category interfaces
├── setup/
│ ├── index.ts # "Setup" category
│ └── ssh.ts # hn setup ssh
└── env/
├── index.ts # "Environment" category
└── generate.ts # hn env generate

Command Interface

Every command implements a simple interface:

interface Command {
name: string;
description: string;
run(args: string[]): Promise<void>;
}

interface Category {
id: string;
label: string;
description: string;
commands: Command[];
}

Adding a New Command

To add a command to an existing category (e.g., hn setup conductor):

  1. Create src/commands/setup/conductor.ts implementing Command
  2. Add it to the commands array in src/commands/setup/index.ts
  3. Done — available in interactive mode and as bun hn setup conductor

Adding a New Category

To add a new category (e.g., hn deploy):

  1. Create src/commands/deploy/index.ts exporting a Category
  2. Import and add it to the CATEGORIES array in src/registry.ts
  3. Done — available in interactive mode and as bun hn deploy <command>

Migrated Scripts

The CLI ports functionality from legacy bash scripts. The original scripts are kept as fallbacks for environments without Bun.

CLI CommandOriginal ScriptStatus
bun hn setup sshscripts/setup-ssh.shPorted — original has deprecation notice
bun hn env generatescripts/generate-env-op.shPorted — original still used by bun env:generate

Candidates for Future Porting

ScriptSuggested Command
scripts/op-dev.shhn dev <app> [--env <env>]
scripts/conductor-setup.shhn setup conductor
scripts/migrate-secrets.shhn secrets migrate