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
| Command | Description |
|---|---|
bun hn | Interactive category and command picker |
bun hn setup ssh | Configure SSH access to HanseNexus infrastructure |
bun hn env generate | Generate .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 --all | Batch generate all .env.op files, no prompts |
bun hn --help | Print 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:
- Checks prerequisites — 1Password CLI auth, Tailscale connectivity, SSH Agent socket
- Exports public keys — fetches from 1Password vault (
Hubby) to~/.ssh/ - Configures SSH hosts — writes a managed block in
~/.ssh/configforhn-hub,hn-k3s,hn-runner,hn-mail - 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/configbefore 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:
| Host | Address | User | Key |
|---|---|---|---|
hn-hub | 100.82.96.102 (Tailscale) | clawdbot | 1p_hn_ssh.pub |
hn-k3s | 100.90.51.49 (Tailscale) | root | 1p_hn_ssh.pub |
hn-runner | 159.69.123.137 (Public) | root | 1p_hn_runner.pub |
hn-mail | 159.69.247.106 (Public) | root | 1p_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:
- Fetches item fields from the 1Password
k3vault as JSON - Generates
op://reference lines for each field matchingSCREAMING_SNAKE_CASE - Merges non-secret defaults from
.env.development(without overwriting op:// entries) - Writes the result to
.env.op(or.env.op.<env>for non-prod)
Prerequisites:
- 1Password CLI authenticated (via
OP_SERVICE_ACCOUNT_TOKENor interactiveop 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:
| App | 1Password Items | Environments |
|---|---|---|
calnexus | calnexus-{env}, convex-calnexus-{env} | prod |
lexilink | lexilink-{env}, convex-lexilink-{env} | prod, staging, preview, dev |
planex | planex-{env}, convex-planex-{env} | prod |
bgs-service | bgs-service-{env}, convex-bgs-service-{env} | prod |
nexus-lms | nexus-lms-{env} | prod |
portfolio | portfolio-{env} | prod |
qript | qript-{env} | prod |
elbe-akustik | elbe-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):
- Create
src/commands/setup/conductor.tsimplementingCommand - Add it to the
commandsarray insrc/commands/setup/index.ts - Done — available in interactive mode and as
bun hn setup conductor
Adding a New Category
To add a new category (e.g., hn deploy):
- Create
src/commands/deploy/index.tsexporting aCategory - Import and add it to the
CATEGORIESarray insrc/registry.ts - 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 Command | Original Script | Status |
|---|---|---|
bun hn setup ssh | scripts/setup-ssh.sh | Ported — original has deprecation notice |
bun hn env generate | scripts/generate-env-op.sh | Ported — original still used by bun env:generate |
Candidates for Future Porting
| Script | Suggested Command |
|---|---|
scripts/op-dev.sh | hn dev <app> [--env <env>] |
scripts/conductor-setup.sh | hn setup conductor |
scripts/migrate-secrets.sh | hn secrets migrate |