@toon-protocol/townhouse 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-SXKZUTGE.js → chunk-IXG4IYTG.js} +119 -23
- package/dist/chunk-IXG4IYTG.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +16 -3
- package/dist/cli.js.map +1 -1
- package/dist/compose/townhouse-hs.yml +8 -8
- package/dist/image-manifest.json +10 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{manager-D9Y_iWHo.d.ts → manager-DSkD9Td1.d.ts} +14 -1
- package/package.json +2 -2
- package/dist/chunk-SXKZUTGE.js.map +0 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/cli/browser-opener.ts","../src/cli/onboarding-ribbon.ts","../src/cli/failure-copy.ts","../src/cli/password-prompt.ts","../src/cli/preflight-ports.ts","../src/cli/pull-narrator.ts","../src/cli/node-commands.ts","../src/cli/drill-commands.ts","../src/cli/status-earnings.ts","../src/credits/buy.ts","../src/wallet/turbo-signer.ts","../src/credits/units.ts","../src/credits/balance.ts","../src/tui/tty-detect.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * CLI entrypoint for `@toon-protocol/townhouse` (Story 21.1).\n *\n * Subcommands: init, up, down, status, --help\n *\n * Usage:\n * townhouse init [--force]\n * townhouse up\n * townhouse down\n * townhouse status\n */\n\nimport { parseArgs } from 'node:util';\nimport {\n mkdirSync,\n writeFileSync,\n existsSync,\n renameSync,\n rmSync,\n statSync,\n realpathSync,\n} from 'node:fs';\nimport { join, resolve, dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport { pathToFileURL } from 'node:url';\nimport { spawn } from 'node:child_process';\nimport { stringify } from 'yaml';\nimport Docker from 'dockerode';\nimport { nip19 } from 'nostr-tools';\n\nimport { getDefaultConfig } from './config/defaults.js';\nimport type { NetworkMode } from './config/schema.js';\nimport { loadConfig, saveConfig } from './config/loader.js';\nimport type { TownhouseConfig, ChainProviderEntry } from './config/schema.js';\nimport { DockerOrchestrator, OrchestratorError } from './docker/index.js';\nimport type { NodeType } from './docker/types.js';\nimport {\n ConnectorAdminClient,\n TransportProbe,\n DEFAULT_ATOR_PROXY,\n writeHsConnectorConfig,\n writeHsNodeEnvFile,\n} from './connector/index.js';\nimport { materializeComposeTemplate } from './compose-loader.js';\nimport type { ComposeLoaderOptions } from './compose-loader.js';\nimport { BootReconciler } from './reconciler.js';\nimport { createApiServer } from './api/server.js';\nimport { createWizardApiServer } from './api/wizard-server.js';\nimport type { ApiServer } from './api/index.js';\nimport { RealBrowserOpener } from './cli/browser-opener.js';\nimport type { BrowserOpener } from './cli/browser-opener.js';\nimport { OnboardingRibbon } from './cli/onboarding-ribbon.js';\nimport { renderFailure } from './cli/failure-copy.js';\nimport { promptPassword } from './cli/password-prompt.js';\nimport {\n checkHsPortCollisions,\n formatCollisionMessage,\n type PortCollision,\n} from './cli/preflight-ports.js';\nimport { PullNarrator } from './cli/pull-narrator.js';\nimport {\n readImageManifest,\n isSyntheticDigest,\n} from './state/image-manifest.js';\nimport {\n handleNodeAdd,\n handleNodeRemove,\n handleNodeList,\n NODE_HELP,\n NODE_ADD_HELP,\n NODE_REMOVE_HELP,\n NODE_LIST_HELP,\n} from './cli/node-commands.js';\nimport { dispatchDrillCommand } from './cli/drill-commands.js';\nimport {\n renderEarningsSection,\n resolveSatsRate,\n} from './cli/status-earnings.js';\nimport {\n aggregateEarnings,\n type AggregatedEarnings,\n} from './earnings/aggregator.js';\nimport { readNodesYaml } from './state/nodes-yaml.js';\nimport { PeerTypeResolver } from './registry/peer-type-resolver.js';\nimport { createDeltaComputer } from './earnings/snapshot-reader.js';\nimport {\n WalletManager,\n encryptWallet,\n decryptWallet,\n saveWallet,\n loadWallet,\n} from './wallet/index.js';\nimport type { NodeKeyInfo } from './wallet/index.js';\nimport type { TurboTokenId } from './wallet/turbo-signer.js';\nimport { buyCredits } from './credits/buy.js';\nimport { getCreditBalance } from './credits/balance.js';\nimport { formatTokenAmount, formatWincAsBytes } from './credits/units.js';\nimport { shouldRenderInk } from './tui/tty-detect.js';\n\n/**\n * Error thrown when `main()` is invoked with `--help`. Callers (tests) can\n * distinguish this from genuine failures; the top-level entrypoint catches\n * it and exits 0.\n */\nexport class CliHelpRequested extends Error {\n constructor() {\n super(HELP_TEXT);\n this.name = 'CliHelpRequested';\n }\n}\n\nconst HELP_TEXT = `townhouse — TOON node orchestrator\n\nUsage:\n townhouse setup [--no-browser] [--port <n>] [--config-dir <dir>] Run the first-run setup wizard\n townhouse init [--force] [--config-dir <dir>] [--password <pw>] [--preset <name>] [--network <mode>] [--yes] Initialize config + wallet\n townhouse up [--town] [--mill] [--dvm] [-c <path>] [--password <pw>] Start nodes\n townhouse down [-c <path>] Stop all nodes\n townhouse status [-c <path>] Show node status\n townhouse metrics [-c <path>] Show connector metrics\n townhouse wallet show [--json] [--hex] [--paths] [-c <path>] [--password <pw>] Show derived addresses\n townhouse wallet seed --confirm [-c <path>] [--password <pw>] Print the BIP-39 seed phrase (password-gated, requires --confirm)\n townhouse credits buy --token <id> --amount <decimal> [--fee-multiplier <n>] [--quote-only] [--yes] [-c <path>] [--password <pw>]\n Buy Arweave upload credits (token: eth|sol|pol|base-eth|base-usdc|usdc-eth|usdc-pol)\n townhouse credits balance --token <id> [-c <path>] [--password <pw>] Show Turbo credit balance for the funding address\n townhouse hs up [--password <pw>] [--skip-preflight] [-c <path>] Boot apex (connector + .anyone HS) (launches dashboard TUI in TTY mode)\n townhouse hs down [--rotate-keys] [-c <path>] Stop apex (--rotate-keys deletes .anyone keypair)\n townhouse node add [<type>] [--json] [-c <path>] Provision a child node (default: town)\n townhouse node remove <id> [--yes] [--json] [-c <path>] Deprovision a child node\n townhouse node list [--json] [-c <path>] List provisioned nodes\n townhouse chains list [--json] [-c <path>] List configured settlement chains (EVM/Solana/Mina)\n townhouse chains add --chain-type <evm|solana|mina> --chain-id <id> [fields] [-c <path>] Add/update a settlement chain\n townhouse chains remove <chainId> [-c <path>] Remove a settlement chain\n townhouse channels [--json] Show open payment channels\n townhouse logs <node-id> [-f|--follow] [--lines N] [--json] Tail logs for a node (Ctrl-C to stop)\n townhouse peer <id> [--json] Show per-peer detail card\n townhouse health [--json] Probe apex/api/nodes/.anyone health\n townhouse --help Show this help\n\nFlags:\n --town Start Town (Nostr relay) node\n --mill Start Mill (swap) node\n --dvm Start DVM (compute) node\n --password Wallet password (non-interactive mode)\n --rotate-keys Delete the .anyone keypair volume on hs down (produces a new address on next hs up)\n --skip-preflight Skip the port-collision preflight check on hs up (escape hatch)\n --no-browser Skip opening the browser automatically (setup command)\n --port Override the API port (setup command, default 9400)\n --preset Init from a named preset (init only). Supported: demo\n --network Chain network for apex + nodes (init only): mainnet (default), testnet, devnet, custom\n --yes Non-interactive (init only); with --preset=demo uses demo password if --password absent\n --json Machine-readable JSON output (node commands; NDJSON for \\`logs\\`)\n --lines Number of historical log lines to fetch on attach (logs command, default 50)\n -f|--follow Accepted for \\`tail -f\\` muscle memory on \\`logs\\` (no-op — follow is default)\n If no flags given, starts all enabled nodes from config.`;\n\n/**\n * Dependency-injection overrides for the `hs up` / `hs down` CLI path.\n * Used by unit tests to stub out Docker, file I/O, and admin client.\n */\nexport interface CliHsOverrides {\n /** Override materializeComposeTemplate (avoids disk writes in tests). */\n materializeComposeTemplate?: (\n profile: string,\n opts?: ComposeLoaderOptions\n ) => { composePath: string; manifestPath: string };\n /** Override the DockerOrchestrator constructor (avoids real Docker in tests). */\n createOrchestrator?: (\n docker: Docker,\n config: TownhouseConfig,\n walletManager: WalletManager | undefined,\n options: { profile: 'hs'; composePath: string }\n ) => {\n up: (profiles: NodeType[]) => Promise<void>;\n down: () => Promise<void>;\n on: (event: string, handler: (...args: unknown[]) => void) => unknown;\n /**\n * Pre-pull a single image ref (Epic 49 Followup D).\n * Optional on the stub interface — when omitted on a real orchestrator,\n * the cold-pull narration phase is skipped (silent degrade).\n */\n pullImage?: (image: string) => Promise<void>;\n };\n /** Override ConnectorAdminClient construction (avoids real HTTP in tests). */\n createAdminClient?: (\n baseUrl: string,\n timeoutMs: number\n ) => {\n getHsHostname: () => Promise<{\n hostname: string | null;\n publishedAt: string | null;\n }>;\n };\n /** Override `docker compose down -v` spawn for --rotate-keys (avoids real Docker). */\n runComposeDown?: (composePath: string, withVolumes: boolean) => Promise<void>;\n /**\n * Override BootReconciler construction (Story 46.1). Tests inject a stub\n * with a spied-on `reconcile()` to assert wiring without touching disk\n * or the connector. When omitted, the default constructs a real\n * `BootReconciler` against `~/.townhouse/{nodes.yaml,reconciler.log}`.\n */\n createReconciler?: (\n nodesYamlPath: string,\n reconcilerLogPath: string\n ) => { reconcile: () => Promise<void> };\n /**\n * Override the port-collision preflight check (Epic 49 Followup B).\n * Default invokes `checkHsPortCollisions(docker)` from\n * `./cli/preflight-ports.js`. Tests inject a stub that returns either\n * `[]` (happy path) or a fabricated PortCollision[] (collision path) so\n * the production socket-bind + Docker enrichment is not exercised in\n * unit tests.\n */\n checkPortCollisions?: (docker: Docker) => Promise<PortCollision[]>;\n}\n\n/**\n * Dependency-injection overrides for the `node add` / `node remove` / `node list` CLI path.\n * Used by unit tests to stub `fetch` and the interactive confirmation prompt.\n */\nexport interface CliNodeCommandOverrides {\n fetch?: (url: string, init?: RequestInit) => Promise<Response>;\n confirm?: (question: string) => Promise<boolean>;\n apiUrl?: string;\n}\n\nconst DEFAULT_CONFIG_DIR = join(homedir(), '.townhouse');\nconst DEFAULT_CONFIG_PATH = join(DEFAULT_CONFIG_DIR, 'config.yaml');\n\n/**\n * Print the \"what now\" call-to-action after init. The journey from `init` to a\n * running node is the moment a first-timer is most likely to stall, so init must\n * hand them the exact next command. When a non-default config dir is used, the\n * next command needs an explicit `-c <path>`.\n */\nfunction printInitNextStep(dir: string): void {\n const isDefaultDir = dir === resolve(DEFAULT_CONFIG_DIR);\n const cmd = isDefaultDir\n ? 'npx @toon-protocol/townhouse hs up'\n : `npx @toon-protocol/townhouse hs up -c ${join(dir, 'config.yaml')}`;\n console.log('');\n console.log('Next — start your node:');\n console.log(` ${cmd}`);\n console.log('');\n console.log(\n 'First run pulls container images and bootstraps a hidden service.'\n );\n console.log('It can take a few minutes; progress is shown throughout.');\n}\n\nasync function handleInit(\n force: boolean,\n configDir?: string,\n password?: string,\n preset?: 'demo',\n yes?: boolean,\n network?: NetworkMode\n): Promise<void> {\n const dir = resolve(configDir ?? DEFAULT_CONFIG_DIR);\n const configPath = join(dir, 'config.yaml');\n\n if (existsSync(configPath) && !force) {\n console.error(\n `Config already exists at ${configPath}. Use --force to overwrite.`\n );\n process.exitCode = 1;\n return;\n }\n\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // D2 — preset path takes precedence over default config for non-interactive\n // demo init. Preset writes the same TownhouseConfig shape, so the rest of\n // the init flow (wallet generation, etc.) is unaffected.\n let configToWrite;\n if (preset === 'demo') {\n const { buildDemoConfig, DEMO_DETERMINISTIC_PASSWORD } =\n await import('./presets/demo.js');\n configToWrite = buildDemoConfig({ walletPath: join(dir, 'wallet.enc') });\n // AC-D2-6: --yes without --password under --preset=demo gets the\n // deterministic demo password. Documented as DEMO ONLY.\n if (yes && !password) {\n password = DEMO_DETERMINISTIC_PASSWORD;\n console.log(\n '[demo preset] Using deterministic demo password (insecure — demo only).'\n );\n }\n } else {\n configToWrite = getDefaultConfig();\n // Override wallet path to use the config dir, not the default home-dir path.\n // getDefaultConfig() hardcodes ~/.townhouse/wallet.enc; tests and non-default\n // config dirs need the wallet collocated with config.yaml.\n configToWrite.wallet.encrypted_path = join(dir, 'wallet.enc');\n }\n // Persist the network mode (mainnet/testnet/devnet/custom). Drives chain/RPC\n // config for the apex connector and every node container (resolveNetworkProfile).\n if (network !== undefined) {\n configToWrite.network = network;\n }\n const yamlContent = stringify(configToWrite);\n writeFileSync(configPath, yamlContent, {\n encoding: 'utf-8',\n mode: 0o600,\n });\n\n console.log(`Config created at ${configPath}`);\n\n // Generate wallet — use config dir for wallet path (overrides default home dir path)\n const walletPath = join(dir, 'wallet.enc');\n if (existsSync(walletPath) && !force) {\n console.log('');\n console.log(\n `Wallet already exists at ${walletPath} — keeping your existing keys.`\n );\n console.log(\n 'Your seed phrase from the first run is still valid; nothing changed.'\n );\n console.log(\n '(Re-run with --force to regenerate, which REPLACES your keys.)'\n );\n printInitNextStep(dir);\n return;\n }\n\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (!walletPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const walletManager = new WalletManager({ encryptedPath: walletPath });\n const { mnemonic } = await walletManager.generate();\n\n // Display mnemonic ONCE for backup — this is the only place it ever appears\n console.log('');\n console.log('=== IMPORTANT: Back up your seed phrase ===');\n console.log('');\n console.log(` ${mnemonic}`);\n console.log('');\n console.log('This is the ONLY time your seed phrase will be shown.');\n console.log('Store it safely. You will need it to recover your node keys.');\n console.log('============================================');\n console.log('');\n\n // Encrypt and save — mnemonic reference is not stored beyond this block\n const encrypted = encryptWallet(mnemonic, walletPassword);\n await saveWallet(walletPath, encrypted);\n console.log(`Wallet saved to ${walletPath}`);\n\n // Display derived addresses\n console.log('');\n console.log('Derived Node Addresses:');\n console.log('-----------------------');\n const allKeys = walletManager.getAllKeys();\n for (const info of allKeys) {\n console.log(` ${info.nodeType.padEnd(6)} Nostr: ${info.nostrPubkey}`);\n console.log(` ${''.padEnd(6)} EVM: ${info.evmAddress}`);\n }\n\n // Zero key material\n walletManager.lock();\n\n printInitNextStep(dir);\n}\n\nasync function handleSetup(\n configDir: string | undefined,\n port: number,\n noBrowser: boolean,\n dockerInstance?: Docker,\n browserOpener?: BrowserOpener\n): Promise<void> {\n const dir = resolve(configDir ?? DEFAULT_CONFIG_DIR);\n const configPath = join(dir, 'config.yaml');\n const walletPath = join(dir, 'wallet.enc');\n\n // Short-circuit only when BOTH the config and the wallet exist — a config\n // without a wallet would land the operator in a circular dead-end (setup\n // says \"already initialized → run `townhouse up`\", up then errors that the\n // wallet is missing). Guide them to clean up and re-run setup instead.\n if (existsSync(configPath) && existsSync(walletPath)) {\n console.log('Already initialized — run `townhouse up` to start your nodes');\n return;\n }\n if (existsSync(configPath) && !existsSync(walletPath)) {\n console.error(\n `Found ${configPath} but no wallet at ${walletPath}.\\n` +\n `Delete the orphan config and re-run \\`townhouse setup\\`, or restore the wallet from backup.`\n );\n process.exitCode = 1;\n return;\n }\n\n const docker = dockerInstance ?? new Docker();\n const opener = browserOpener ?? new RealBrowserOpener();\n\n const wizardServer = await createWizardApiServer({\n configDir: dir,\n configPath,\n walletPath,\n port,\n docker,\n });\n\n const url = `http://127.0.0.1:${port}/wizard`;\n\n try {\n await wizardServer.app.listen({ host: '127.0.0.1', port });\n } catch (err: unknown) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === 'EADDRINUSE') {\n console.error(\n `Port ${port} is already in use. Pass \\`--port <n>\\` to choose a different port.`\n );\n process.exitCode = 1;\n try {\n await wizardServer.close();\n } catch {\n /* best-effort */\n }\n return;\n }\n throw err;\n }\n console.log(`Wizard ready at ${url}`);\n\n if (!noBrowser) {\n await opener.open(url);\n }\n\n // Wire signal handlers via process.once so they self-remove after firing\n // — prevents listener leaks when tests call main(['setup', ...]) repeatedly.\n // The Fastify server keeps the process alive after handleSetup returns;\n // signals trigger graceful close.\n let shuttingDown = false;\n const shutdown = async (sig: string) => {\n if (shuttingDown) return;\n shuttingDown = true;\n console.log(`\\nReceived ${sig}, shutting down...`);\n try {\n await wizardServer.close();\n } catch {\n /* best-effort */\n }\n process.exit(0);\n };\n process.once('SIGINT', () => {\n void shutdown('SIGINT');\n });\n process.once('SIGTERM', () => {\n void shutdown('SIGTERM');\n });\n}\n\n/**\n * Per-node role description shown above the address rows in the cards layout.\n * Mirrors Sally's round-4 wallet-show sketch.\n */\nconst NODE_ROLE_DESCRIPTIONS: Record<NodeType, string> = {\n town: 'Nostr relay — earns ILP fees per event relayed.',\n mill: 'Multi-chain swap peer — settles cross-chain swaps for fees.',\n dvm: 'Compute / DVM worker — collects job payments, signs Arweave uploads.',\n};\n\n/** Per-address purpose labels (one per key type per node). */\ninterface AddressRow {\n label: string; // e.g. \"Nostr\", \"EVM\", \"SOL\", \"Mina\", \"AR\"\n value: string; // e.g. npub1..., 0x..., base58..., AR address, or \"—\"\n purpose: string; // short trailing parenthetical\n hex?: string | undefined; // raw hex pubkey shown under npub when --hex\n path?: string | undefined; // derivation path shown when --paths\n}\n\n/**\n * Build the per-row address records for one node's card. Returns rows in\n * fixed presentation order (Nostr → EVM → chain rows). Optional rows render\n * as `—` when the underlying key is absent (e.g. Town has no SOL).\n */\nfunction buildNodeRows(\n info: NodeKeyInfo,\n options: { hex: boolean; paths: boolean }\n): AddressRow[] {\n const rows: AddressRow[] = [];\n\n // Nostr — always present. Encode as NIP-19 npub for the primary display.\n const npub = nip19.npubEncode(info.nostrPubkey);\n const nostrPurposeByNode: Record<NodeType, string> = {\n town: 'share this to be found',\n mill: 'announces swap quotes',\n dvm: 'offers DVM services',\n };\n rows.push({\n label: 'Nostr',\n value: npub,\n purpose: nostrPurposeByNode[info.nodeType],\n hex: options.hex ? info.nostrPubkey : undefined,\n path: options.paths ? info.nostrDerivationPath : undefined,\n });\n\n // EVM — always present.\n const evmPurposeByNode: Record<NodeType, string> = {\n town: 'receives ILP earnings',\n mill: 'settles EVM swaps',\n dvm: 'collects job payments',\n };\n rows.push({\n label: 'EVM',\n value: info.evmAddress,\n purpose: evmPurposeByNode[info.nodeType],\n path: options.paths ? info.evmDerivationPath : undefined,\n });\n\n // SOL — present for every node after Phase 1 (graceful fallback if absent).\n const solPurposeByNode: Record<NodeType, string> = {\n town: 'receives swap fills',\n mill: 'settles SOL swaps',\n dvm: 'spends Arweave credits',\n };\n rows.push({\n label: 'SOL',\n value: info.solanaAddress ?? '—',\n purpose: solPurposeByNode[info.nodeType],\n path: options.paths ? info.solanaDerivationPath : undefined,\n });\n\n // Mill-only: Mina row after SOL.\n if (info.nodeType === 'mill') {\n rows.push({\n label: 'Mina',\n value: info.minaAddress ?? '—',\n purpose: 'settles Mina swaps',\n // Mina derivation path is not currently surfaced through NodeKeyInfo.\n });\n }\n\n // DVM-only: Arweave row appended after SOL.\n if (info.nodeType === 'dvm') {\n rows.push({\n label: 'AR',\n value: info.arweaveAddress ?? '—',\n purpose: 'signs Arweave uploads',\n path: options.paths ? info.arweaveDerivationPath : undefined,\n });\n }\n\n return rows;\n}\n\n/**\n * Render one node card to stdout using box-drawing characters consistent\n * with the existing CLI aesthetic (see HELP_TEXT, status output).\n *\n * ┌─ TOWN ──── Nostr relay — earns ILP fees ────────┐\n * │ Nostr npub1abc... │\n * │ (share this to be found) │\n * │ EVM 0xAbC... │\n * │ (receives ILP earnings) │\n * └──────────────────────────────────────────────────┘\n *\n * Width is calculated from the widest row; the border auto-fits.\n */\nfunction renderNodeCard(info: NodeKeyInfo, rows: AddressRow[]): string {\n // Compose inner content lines (no border yet, no leading \"│ \").\n const role = NODE_ROLE_DESCRIPTIONS[info.nodeType];\n const labelWidth = Math.max(...rows.map((r) => r.label.length));\n const headerLine = `${info.nodeType.toUpperCase()} — ${role}`;\n\n const bodyLines: string[] = [];\n for (const row of rows) {\n bodyLines.push(`${row.label.padEnd(labelWidth)} ${row.value}`);\n bodyLines.push(`${' '.repeat(labelWidth)} (${row.purpose})`);\n if (row.hex) {\n bodyLines.push(`${' '.repeat(labelWidth)} hex: ${row.hex}`);\n }\n if (row.path) {\n bodyLines.push(`${' '.repeat(labelWidth)} path: ${row.path}`);\n }\n }\n\n const innerWidth = Math.max(\n headerLine.length,\n ...bodyLines.map((l) => l.length)\n );\n // Leave 1 char of padding on each side.\n const totalInner = innerWidth + 2;\n const horizontal = '─'.repeat(totalInner);\n const top = `┌${horizontal}┐`;\n const bottom = `└${horizontal}┘`;\n\n const lines: string[] = [];\n lines.push(top);\n lines.push(`│ ${headerLine.padEnd(innerWidth)} │`);\n // Separator under the header to set off the role text from rows.\n lines.push(`├${horizontal}┤`);\n for (const body of bodyLines) {\n lines.push(`│ ${body.padEnd(innerWidth)} │`);\n }\n lines.push(bottom);\n return lines.join('\\n');\n}\n\n/**\n * Build the structured JSON payload for `wallet show --json`. Schema is\n * documented in the plan; consumers like `jq` rely on the field names below.\n */\nfunction buildWalletJson(allKeys: NodeKeyInfo[]): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const info of allKeys) {\n const node: Record<string, unknown> = {\n nostr: {\n npub: nip19.npubEncode(info.nostrPubkey),\n hex: info.nostrPubkey,\n path: info.nostrDerivationPath,\n },\n evm: { address: info.evmAddress, path: info.evmDerivationPath },\n };\n if (info.solanaAddress) {\n node['sol'] = {\n address: info.solanaAddress,\n path: info.solanaDerivationPath,\n };\n }\n if (info.nodeType === 'mill' && info.minaAddress) {\n node['mina'] = { address: info.minaAddress };\n }\n if (info.nodeType === 'dvm' && info.arweaveAddress) {\n node['arweave'] = {\n address: info.arweaveAddress,\n path: info.arweaveDerivationPath,\n };\n }\n out[info.nodeType] = node;\n }\n return out;\n}\n\n/**\n * Extended `townhouse wallet show` (epic-49, Phase 3).\n *\n * Default output: cards layout (one card per node) using box-drawing\n * characters. Shows NIP-19 npub by default; hex hidden unless --hex.\n * Per Sally's round-4 sketch: role description line above the rows, plus\n * per-row purpose labels (e.g. \"receives ILP earnings\").\n *\n * Flags:\n * --json structured machine-readable output instead of cards\n * --hex append a hex pubkey line under each Nostr npub\n * --paths append a derivation-path line under each address\n *\n * Side effects: triggers `ensureArweaveKey('dvm')` once — RSA-4096 derivation\n * is 5–30s on first call per unlocked session, then cached. A \"deriving…\"\n * status line is printed to stderr so the operator knows why it's pausing.\n */\nasync function handleWalletShow(\n config: TownhouseConfig,\n password?: string,\n options: { json?: boolean; hex?: boolean; paths?: boolean } = {}\n): Promise<void> {\n const walletPath = config.wallet.encrypted_path;\n const result = await loadWallet(walletPath);\n\n if (!result) {\n console.error('No wallet found. Run `townhouse init` first.');\n process.exitCode = 1;\n return;\n }\n\n if (result.permissionsWarning) {\n console.error(result.permissionsWarning);\n }\n\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (!walletPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const walletManager = new WalletManager({ encryptedPath: walletPath });\n try {\n // Decrypt mnemonic in minimal scope — fromMnemonic derives keys then\n // the mnemonic string becomes unreachable (eligible for GC)\n await walletManager.fromMnemonic(\n decryptWallet(result.wallet, walletPassword)\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n try {\n // Derive DVM's Arweave key before rendering so the AR address row is\n // populated. RSA-4096 generation is 5–30s on first call per unlocked\n // session — emit a status line to stderr so the operator knows why\n // we're pausing. Subsequent calls in the same session are instant.\n // On failure (unsupported platform, etc.) we degrade gracefully and\n // render the AR row as `—` rather than aborting `wallet show`.\n const arStartMs = Date.now();\n // Spinner-style status — only worth emitting if the call actually pauses.\n // Cache hit returns in <100ms; cold derivation pays the 5–30s. We can't\n // know up front which path will run, so we set a 200ms timer that prints\n // the status only when needed, and clear it on completion.\n const arStatusTimer = setTimeout(() => {\n process.stderr.write('deriving Arweave key (first run, ~15s)...\\n');\n }, 200);\n try {\n await walletManager.ensureArweaveKey('dvm', walletPassword);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(\n `Warning: Arweave key derivation failed (${msg}). AR address will display as '—'.`\n );\n } finally {\n clearTimeout(arStatusTimer);\n void arStartMs; // keep var alive for potential timing log\n }\n\n const allKeys = walletManager.getAllKeys();\n\n if (options.json) {\n console.log(JSON.stringify(buildWalletJson(allKeys), null, 2));\n return;\n }\n\n const renderOpts = {\n hex: options.hex === true,\n paths: options.paths === true,\n };\n for (const info of allKeys) {\n const rows = buildNodeRows(info, renderOpts);\n console.log(renderNodeCard(info, rows));\n console.log(''); // blank line between cards\n }\n\n // Trailing tips block — guides the operator to scripting and the\n // related credit-funding command added in Phase 2.\n console.log('Tip: townhouse wallet show --json for scripting');\n console.log(' townhouse wallet show --hex to see raw hex pubkeys');\n console.log(' townhouse wallet show --paths to see derivation paths');\n console.log(\n ' townhouse credits buy --token sol --amount <n> to fund Arweave uploads'\n );\n } finally {\n // Zero key material immediately after display\n walletManager.lock();\n }\n}\n\n/**\n * `townhouse wallet seed --confirm` (epic-49, Phase 3).\n *\n * Reveals the BIP-39 mnemonic for recovery. Requires --confirm so it's\n * impossible to leak the seed by typing the wrong subcommand at a public\n * terminal. Same password-sourcing chain as the rest of the wallet commands.\n *\n * Closes the \"I scrolled past the init banner in tmux\" footgun without\n * inventing a new backup channel (clipboard, QR, encrypted USB, etc.).\n */\nasync function handleWalletSeed(\n config: TownhouseConfig,\n password: string | undefined,\n confirm: boolean\n): Promise<void> {\n if (!confirm) {\n console.error(\n 'This command will print your seed phrase to your terminal. Re-run with --confirm to acknowledge.'\n );\n process.exitCode = 1;\n return;\n }\n\n const walletPath = config.wallet.encrypted_path;\n const result = await loadWallet(walletPath);\n if (!result) {\n console.error('No wallet found. Run `townhouse init` first.');\n process.exitCode = 1;\n return;\n }\n if (result.permissionsWarning) {\n console.error(result.permissionsWarning);\n }\n\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (!walletPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const walletManager = new WalletManager({ encryptedPath: walletPath });\n try {\n await walletManager.fromMnemonic(\n decryptWallet(result.wallet, walletPassword)\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n try {\n const mnemonic = walletManager.getMnemonic();\n if (!mnemonic) {\n // Shouldn't happen — fromMnemonic just succeeded — but defend against\n // races with concurrent lock() calls in long-running test processes.\n console.error('Internal error: mnemonic unavailable after unlock.');\n process.exitCode = 1;\n return;\n }\n\n // ASCII-only warning banner — CLAUDE.md forbids emojis unless requested.\n console.log(\n '============================================================='\n );\n console.log(' [!] Anyone who sees this seed owns your townhouse identity.');\n console.log(' [!] Anyone who records this terminal owns your earnings.');\n console.log(\n ' [!] Shoulder-surf, screen-shares, and tmux logs are vectors.'\n );\n console.log(\n '============================================================='\n );\n console.log('');\n console.log('');\n console.log(` ${mnemonic}`);\n console.log('');\n console.log('');\n console.log(\n 'This is the same 12 words shown at `townhouse init`. Storing them elsewhere is your responsibility.'\n );\n } finally {\n walletManager.lock();\n }\n}\n\n// ── Credits commands (epic-49, Phase 2) ────────────────────────────────────\n\n/**\n * Set of valid `--token` values for credits commands. Must stay in sync with\n * `TurboTokenId` in wallet/turbo-signer.ts.\n */\nconst VALID_TURBO_TOKENS: ReadonlySet<TurboTokenId> = new Set([\n 'eth',\n 'pol',\n 'base-eth',\n 'base-usdc',\n 'usdc-eth',\n 'usdc-pol',\n 'sol',\n 'ar',\n]);\n\nfunction isTurboTokenId(value: string): value is TurboTokenId {\n return VALID_TURBO_TOKENS.has(value as TurboTokenId);\n}\n\n/**\n * Resolve the wallet password from --password → env var → TTY prompt → error.\n * Returns the resolved password string OR null when no source is available\n * (caller should set exitCode=1 and return).\n */\nasync function resolveWalletPassword(\n flagPassword: string | undefined\n): Promise<string | null> {\n const envPassword = process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (flagPassword) return flagPassword;\n if (envPassword) return envPassword;\n if (process.stdin.isTTY) {\n return await promptPassword('Wallet password: ');\n }\n return null;\n}\n\n/**\n * Read a single y/N answer from stdin, defaulting to N on empty input.\n * Mirrors the existing readline-based prompt in `hs down --rotate-keys`.\n */\nasync function promptYesNo(question: string): Promise<boolean> {\n const { createInterface } = await import('node:readline');\n const answer = await new Promise<string>((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n rl.question(question, (ans) => {\n rl.close();\n resolve(ans);\n });\n });\n return ['y', 'yes'].includes(answer.trim().toLowerCase());\n}\n\n/**\n * Handle `townhouse credits buy --token <id> --amount <decimal> [...]`.\n *\n * Flow: parse argv → resolve password → unlock wallet → fetch quote →\n * (unless --yes) ask for confirmation → submit topUpWithTokens → stream\n * status to stdout.\n *\n * For the testable contract: returns void, sets process.exitCode on failure\n * paths, writes status to stdout. Pure infrastructure happens in\n * `credits/buy.ts`.\n */\nasync function handleCreditsBuy(\n config: TownhouseConfig,\n values: Record<string, unknown>,\n nodeType: NodeType = 'dvm'\n): Promise<void> {\n // ── 1. Argv validation ──\n const tokenRaw = values['token'] as string | undefined;\n const amountRaw = values['amount'] as string | undefined;\n if (!tokenRaw || !amountRaw) {\n console.error(\n 'Usage: townhouse credits buy --token <id> --amount <decimal> [--fee-multiplier <n>] [--credit-destination <addr>] [--quote-only] [--yes]'\n );\n process.exitCode = 1;\n return;\n }\n if (!isTurboTokenId(tokenRaw)) {\n console.error(\n `Unknown token '${tokenRaw}'. Supported: ${Array.from(VALID_TURBO_TOKENS).join(', ')}`\n );\n process.exitCode = 1;\n return;\n }\n const token: TurboTokenId = tokenRaw;\n\n let feeMultiplier: number | undefined;\n const feeRaw = values['fee-multiplier'] as string | undefined;\n if (feeRaw !== undefined) {\n const parsed = Number(feeRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n console.error(\n `--fee-multiplier must be a positive number, got '${feeRaw}'`\n );\n process.exitCode = 1;\n return;\n }\n feeMultiplier = parsed;\n }\n const quoteOnly = values['quote-only'] === true;\n const skipConfirm = values['yes'] === true;\n const destinationOverride = values['credit-destination'] as\n | string\n | undefined;\n\n // ── 2. Wallet unlock ──\n const walletPath = config.wallet.encrypted_path;\n const loaded = await loadWallet(walletPath);\n if (!loaded) {\n console.error(\n `No wallet found at ${walletPath}. Run \\`townhouse init\\` first.`\n );\n process.exitCode = 1;\n return;\n }\n if (loaded.permissionsWarning) console.error(loaded.permissionsWarning);\n\n const resolvedPassword = await resolveWalletPassword(\n values['password'] as string | undefined\n );\n if (!resolvedPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const wallet = new WalletManager({ encryptedPath: walletPath });\n try {\n await wallet.fromMnemonic(decryptWallet(loaded.wallet, resolvedPassword));\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n try {\n // ── 3. Resolve credit destination ──\n // Funding from EVM/SOL must route credits to the DVM's Arweave address so\n // the DVM container's ArweaveSigner can spend them. Funding from `ar`\n // already targets that address natively (signer === destination). An\n // explicit --credit-destination flag overrides both behaviors.\n let destinationAddress: string | undefined;\n if (destinationOverride) {\n destinationAddress = destinationOverride;\n } else if (token !== 'ar' && nodeType === 'dvm') {\n process.stdout.write(\n `Resolving DVM Arweave credit address (first run, ~10s)...\\n`\n );\n await wallet.ensureArweaveKey('dvm', resolvedPassword);\n const dvmKeys = wallet.getNodeKeys('dvm');\n if (!dvmKeys.arweaveAddress) {\n throw new Error(\n 'DVM Arweave address not populated after ensureArweaveKey'\n );\n }\n destinationAddress = dvmKeys.arweaveAddress;\n }\n\n // ── 4. Quote step ──\n process.stdout.write(\n `Quoting ${amountRaw} ${token} for ${nodeType}'s credit address...\\n`\n );\n const quote = await buyCredits({\n wallet,\n nodeType,\n token,\n amount: amountRaw,\n quoteOnly: true,\n ...(destinationAddress ? { destinationAddress } : {}),\n });\n if (quote.kind !== 'quote') {\n throw new Error('Internal error: quoteOnly returned non-quote result');\n }\n const quotedDisplay = `${quote.winc.toString()} winc (${formatWincAsBytes(quote.winc)})`;\n process.stdout.write(\n `Quote: ${formatTokenAmount(token, quote.baseAmount)} → ${quotedDisplay}\\n`\n );\n process.stdout.write(`Source address (${token}): ${quote.fromAddress}\\n`);\n process.stdout.write(`Credit recipient: ${quote.creditAddress}\\n`);\n\n if (quoteOnly) {\n process.stdout.write('Quote-only; no on-chain transaction submitted.\\n');\n return;\n }\n\n // ── 5. Confirmation ──\n if (!skipConfirm) {\n const ok = await promptYesNo('Proceed? [y/N] ');\n if (!ok) {\n process.stdout.write('Aborted. No transaction submitted.\\n');\n process.exitCode = 1;\n return;\n }\n }\n\n // ── 6. Submit ──\n process.stdout.write('Submitting on-chain transaction...\\n');\n const result = await buyCredits({\n wallet,\n nodeType,\n token,\n amount: amountRaw,\n ...(feeMultiplier !== undefined ? { feeMultiplier } : {}),\n ...(destinationAddress ? { destinationAddress } : {}),\n });\n if (result.kind !== 'submit') {\n throw new Error('Internal error: submit path returned non-submit result');\n }\n process.stdout.write(`Transaction submitted: ${result.id}\\n`);\n process.stdout.write(`Status: ${result.status}\\n`);\n process.stdout.write(\n `Credited: ${result.winc.toString()} winc (${formatWincAsBytes(result.winc)})\\n`\n );\n if (result.block !== undefined) {\n process.stdout.write(`Block: ${result.block}\\n`);\n }\n process.stdout.write('Done.\\n');\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`credits buy failed: ${msg}`);\n process.exitCode = 1;\n } finally {\n wallet.lock();\n }\n}\n\n/**\n * Handle `townhouse credits balance --token <id>`.\n *\n * Per AC#6 (plan): require --token explicitly. The funding identity differs\n * per token family (EVM vs SOL vs AR), so there is no sensible default.\n */\nasync function handleCreditsBalance(\n config: TownhouseConfig,\n values: Record<string, unknown>,\n nodeType: NodeType = 'dvm'\n): Promise<void> {\n const tokenRaw = values['token'] as string | undefined;\n if (!tokenRaw) {\n console.error(\n 'Usage: townhouse credits balance --token <id> [-c <path>] [--password <pw>]'\n );\n process.exitCode = 1;\n return;\n }\n if (!isTurboTokenId(tokenRaw)) {\n console.error(\n `Unknown token '${tokenRaw}'. Supported: ${Array.from(VALID_TURBO_TOKENS).join(', ')}`\n );\n process.exitCode = 1;\n return;\n }\n const token: TurboTokenId = tokenRaw;\n\n const walletPath = config.wallet.encrypted_path;\n const loaded = await loadWallet(walletPath);\n if (!loaded) {\n console.error(\n `No wallet found at ${walletPath}. Run \\`townhouse init\\` first.`\n );\n process.exitCode = 1;\n return;\n }\n if (loaded.permissionsWarning) console.error(loaded.permissionsWarning);\n\n const resolvedPassword = await resolveWalletPassword(\n values['password'] as string | undefined\n );\n if (!resolvedPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const wallet = new WalletManager({ encryptedPath: walletPath });\n try {\n await wallet.fromMnemonic(decryptWallet(loaded.wallet, resolvedPassword));\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n try {\n const balance = await getCreditBalance({ wallet, nodeType, token });\n process.stdout.write(`Address (${token}): ${balance.address}\\n`);\n process.stdout.write(\n `Balance: ${balance.winc.toString()} winc (${formatWincAsBytes(balance.winc)})\\n`\n );\n if (balance.effectiveBalance !== balance.winc) {\n process.stdout.write(\n `Effective (incl. received approvals): ${balance.effectiveBalance.toString()} winc (${formatWincAsBytes(balance.effectiveBalance)})\\n`\n );\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`credits balance failed: ${msg}`);\n process.exitCode = 1;\n } finally {\n wallet.lock();\n }\n}\n\nasync function resolveEarnings(\n adminUrl: string,\n configPath: string\n): Promise<AggregatedEarnings> {\n const base = dirname(configPath);\n try {\n const yaml = await readNodesYaml(join(base, 'nodes.yaml'));\n return await aggregateEarnings({\n connectorAdmin: new ConnectorAdminClient(adminUrl),\n peerTypeResolver: new PeerTypeResolver(yaml),\n deltaComputer: createDeltaComputer({\n snapshotPath: join(base, 'earnings-snapshots.jsonl'),\n }),\n });\n } catch (err) {\n // Distinguish local config/snapshot errors from connector outage:\n // aggregateEarnings handles connector failures internally, so anything\n // surfaced here is a nodes.yaml / snapshot-file / resolver problem.\n console.error(`Earnings unavailable: ${formatLocalEarningsError(err)}`);\n return {\n status: 'connector_unavailable',\n apex: { routingFees: {} },\n peers: [],\n recentClaims: [],\n eventsRelayed: 0,\n uptimeSeconds: 0,\n };\n }\n}\n\n// Render a one-line breadcrumb from a thrown error, collapsing Zod issue\n// lists (multi-line JSON) into `path: message` segments joined by `; `.\nfunction formatLocalEarningsError(err: unknown): string {\n if (\n err !== null &&\n typeof err === 'object' &&\n 'issues' in err &&\n Array.isArray((err as { issues: unknown }).issues)\n ) {\n const issues = (err as { issues: { path?: unknown; message?: unknown }[] })\n .issues;\n const parts = issues\n .map((i) => {\n const path =\n Array.isArray(i.path) && i.path.length > 0\n ? i.path.join('.')\n : '<root>';\n const msg = typeof i.message === 'string' ? i.message : 'invalid';\n return `${path}: ${msg}`;\n })\n .join('; ');\n if (parts) return parts;\n }\n return err instanceof Error ? err.message : String(err);\n}\n\nasync function handleStatus(\n docker: Docker,\n config: TownhouseConfig,\n opts: { units: 'usdc' | 'sats'; satsPerUsdc?: number; configPath: string } = {\n units: 'usdc',\n configPath: DEFAULT_CONFIG_PATH,\n }\n): Promise<void> {\n const orchestrator = new DockerOrchestrator(docker, config, undefined, {\n profile: 'dev',\n });\n const statuses = await orchestrator.status();\n\n console.log('Node Status:');\n console.log('------------');\n for (const s of statuses) {\n const health = s.health ? ` (${s.health})` : '';\n console.log(` ${s.name.padEnd(12)} ${s.state}${health}`);\n }\n\n const connectorHs = config.transport.hiddenService;\n const relayHs = config.transport.relayHiddenService;\n if (\n config.transport.mode === 'ator' ||\n connectorHs?.externalUrl ||\n relayHs?.externalUrl ||\n config.transport.externalUrl\n ) {\n console.log('');\n console.log('Hidden Services:');\n console.log('----------------');\n const connectorUrl =\n connectorHs?.externalUrl ?? config.transport.externalUrl;\n if (connectorUrl) {\n console.log(` Connector (BTP): ${connectorUrl}`);\n }\n if (relayHs?.externalUrl) {\n console.log(` Relay (Nostr): ${relayHs.externalUrl}`);\n }\n if (!connectorUrl && !relayHs?.externalUrl) {\n console.log(' (ator mode set but no externalUrl configured)');\n }\n }\n\n // Try to include connector metrics (graceful degradation)\n try {\n const adminClient = new ConnectorAdminClient(\n `http://127.0.0.1:${config.connector.adminPort}`\n );\n const metrics = await adminClient.getMetrics();\n const peers = await adminClient.getPeers();\n const activePeers = peers.filter((p) => p.connected).length;\n\n console.log('');\n console.log('Connector Metrics:');\n console.log('------------------');\n console.log(` Packets forwarded: ${metrics.aggregate.packetsForwarded}`);\n console.log(` Active peers: ${activePeers}/${peers.length}`);\n } catch {\n console.log('');\n console.log('Connector Metrics: unavailable');\n }\n\n if (opts.units === 'sats' && opts.satsPerUsdc === undefined) return;\n const earnings = await resolveEarnings(\n `http://127.0.0.1:${config.connector.adminPort}`,\n opts.configPath\n );\n for (const line of renderEarningsSection({\n earnings,\n units: opts.units,\n satsPerUsdc: opts.satsPerUsdc,\n }))\n console.log(line);\n}\n\n// handleMetrics moved to cli/drill-commands.ts (Story 48.5)\n\n/**\n * Determine which node profiles to start based on CLI flags and config.\n * If explicit flags (--town, --mill, --dvm) are provided, use those.\n * Otherwise fall back to all enabled nodes from config.\n */\nfunction resolveProfiles(\n values: Record<string, unknown>,\n config: TownhouseConfig\n): NodeType[] {\n const explicitFlags: NodeType[] = [];\n if (values['town']) explicitFlags.push('town');\n if (values['mill']) explicitFlags.push('mill');\n if (values['dvm']) explicitFlags.push('dvm');\n\n if (explicitFlags.length > 0) {\n return explicitFlags;\n }\n\n // No explicit flags — start all enabled nodes from config\n const enabled: NodeType[] = [];\n if (config.nodes.town.enabled) enabled.push('town');\n if (config.nodes.mill.enabled) enabled.push('mill');\n if (config.nodes.dvm.enabled) enabled.push('dvm');\n return enabled;\n}\n\nasync function handleUp(\n configPath: string,\n config: TownhouseConfig,\n profiles: NodeType[],\n docker: Docker,\n password?: string,\n dryRun = false\n): Promise<void> {\n if (profiles.length === 0) {\n console.log(\n 'No nodes enabled in config. Enable nodes in config.yaml first.'\n );\n return;\n }\n\n // Initialize wallet (Round-2 Decision D1:c).\n // The API's GET /wallet depends on an unlocked wallet. If a wallet file\n // exists on disk it MUST be unlockable (fail-fast on bad password). If no\n // wallet file exists, we log a warning and skip API startup entirely so\n // orchestration-only callers (CI, tooling, smoke tests) still work.\n const walletPath = config.wallet.encrypted_path;\n let walletManager: WalletManager | undefined;\n if (!existsSync(walletPath)) {\n console.error(\n `Wallet not found at ${walletPath}. Run \\`townhouse setup\\` first (or restore your wallet backup).`\n );\n process.exitCode = 1;\n return;\n } else {\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (!walletPassword) {\n throw new Error(\n 'Wallet password required to start the API. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n }\n const loaded = await loadWallet(walletPath);\n if (!loaded) {\n throw new Error(`Wallet at ${walletPath} could not be read.`);\n }\n if (loaded.permissionsWarning) {\n console.error(loaded.permissionsWarning);\n }\n walletManager = new WalletManager({ encryptedPath: walletPath });\n try {\n await walletManager.fromMnemonic(\n decryptWallet(loaded.wallet, walletPassword)\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to decrypt wallet: ${msg}`);\n }\n\n // Pre-warm AR cache when DVM is in the boot set. The orchestrator's later\n // ensureArweaveKey('dvm') call (without password) would otherwise pay the\n // full 5–30s RSA cost AND not write back to disk. Calling here with the\n // password populates both the in-memory + on-disk caches once and lets\n // every subsequent invocation be sub-second (epic-49 Followup A).\n if (profiles.includes('dvm')) {\n try {\n await walletManager.ensureArweaveKey('dvm', walletPassword);\n } catch (err: unknown) {\n // Non-fatal: orchestrator's own ensureArweaveKey call will retry.\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(\n `[townhouse up] AR pre-warm failed (non-fatal, orchestrator will retry): ${msg}`\n );\n }\n }\n }\n\n const orchestrator = new DockerOrchestrator(docker, config, walletManager, {\n profile: 'dev',\n });\n\n // Wire up progress reporting\n orchestrator.on(\n 'containerState',\n (event: { name: string; state: string }) => {\n console.log(` ${event.name}: ${event.state}`);\n }\n );\n orchestrator.on(\n 'pullProgress',\n (event: { image: string; status: string; progress?: string }) => {\n const progress = event.progress ? ` ${event.progress}` : '';\n console.log(` [pull] ${event.image}: ${event.status}${progress}`);\n }\n );\n\n // API server reference for graceful shutdown\n let apiServer: ApiServer | undefined;\n\n // Register SIGINT handler for graceful shutdown\n const sigintHandler = async () => {\n console.log('\\nReceived SIGINT, shutting down gracefully...');\n\n // Close API server first\n if (apiServer) {\n try {\n await apiServer.close();\n } catch {\n // Best-effort\n }\n }\n\n // Then stop containers\n try {\n await orchestrator.down();\n } catch {\n // Best-effort cleanup\n }\n process.exit(0);\n };\n process.on('SIGINT', sigintHandler);\n\n // For SIGTERM\n const sigtermHandler = async () => {\n console.log('\\nReceived SIGTERM, shutting down gracefully...');\n\n if (apiServer) {\n try {\n await apiServer.close();\n } catch {\n // Best-effort\n }\n }\n\n try {\n await orchestrator.down();\n } catch {\n // Best-effort cleanup\n }\n process.exit(0);\n };\n process.on('SIGTERM', sigtermHandler);\n\n // Track if the server started successfully (handlers stay registered if true)\n let serverStarted = false;\n\n if (\n profiles.includes('dvm') &&\n config.nodes.dvm.enabled &&\n !process.env['TURBO_TOKEN']\n ) {\n console.warn(\n '[townhouse] WARN: TURBO_TOKEN is not set — Arweave DVM (kind:5094) uploads will fail at first job.'\n );\n console.warn(\n '[townhouse] Export TURBO_TOKEN=<arweave-jwk-json> before `townhouse up` to enable uploads.'\n );\n }\n\n try {\n console.log(`Starting nodes: ${profiles.join(', ')}...`);\n if (!dryRun) {\n await orchestrator.up(profiles);\n console.log('All nodes started successfully.');\n } else {\n console.log('[dry-run] Skipped orchestrator.up()');\n }\n\n // Start API server after nodes are up\n if (walletManager) {\n const connectorAdmin = new ConnectorAdminClient(\n `http://127.0.0.1:${config.connector.adminPort}`\n );\n\n const transportProbe = new TransportProbe({\n proxyUrl:\n config.transport.mode === 'ator'\n ? (config.transport.socksProxy ?? DEFAULT_ATOR_PROXY)\n : '',\n });\n if (config.transport.mode === 'ator') {\n transportProbe.start();\n }\n\n const apiDeps = {\n configPath,\n config,\n orchestrator,\n wallet: walletManager,\n connectorAdmin,\n transportProbe,\n };\n\n apiServer = await createApiServer(apiDeps);\n\n const { host, port } = config.api;\n if (!dryRun) {\n await apiServer.app.listen({\n host: host ?? '127.0.0.1',\n port: port ?? 9400,\n });\n serverStarted = true;\n\n console.log(\n `\\n[Townhouse API] listening on http://${host ?? '127.0.0.1'}:${port ?? 9400}`\n );\n console.log(\n ' GET /nodes, GET /nodes/:type, PATCH /nodes/:type/config, GET /wallet, WS /metrics'\n );\n } else {\n // Log a structured summary for the dry-run smoke test (Task 8.3).\n console.log(\n `[dry-run] API factory invoked: configPath=${configPath} host=${host ?? '127.0.0.1'} port=${port ?? 9400} connectorAdmin=http://127.0.0.1:${config.connector.adminPort} wallet=WalletManager`\n );\n await apiServer.close();\n apiServer = undefined;\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n if (\n msg.includes('Docker is not running') ||\n msg.includes('ENOENT') ||\n msg.includes('ECONNREFUSED') ||\n msg.includes('socket')\n ) {\n throw new Error(\n `Docker is not available. Please ensure Docker is running and try again. (${msg})`\n );\n }\n throw error;\n } finally {\n // Only remove signal handlers if server never started\n // If server is running, handlers enable graceful shutdown on SIGTERM/SIGINT\n if (!serverStarted) {\n process.removeListener('SIGINT', sigintHandler);\n process.removeListener('SIGTERM', sigtermHandler);\n }\n }\n}\n\nasync function handleDown(\n config: TownhouseConfig,\n docker: Docker\n): Promise<void> {\n const orchestrator = new DockerOrchestrator(docker, config, undefined, {\n profile: 'dev',\n });\n\n orchestrator.on(\n 'containerState',\n (event: { name: string; state: string }) => {\n console.log(` ${event.name}: ${event.state}`);\n }\n );\n\n console.log('Stopping nodes...');\n await orchestrator.down();\n console.log('All nodes stopped.');\n}\n\n/** Connector admin URL for HS mode. */\nconst HS_CONNECTOR_ADMIN_URL = 'http://127.0.0.1:9401';\n/** Townhouse API URL for HS mode (inside the townhouse-api container). */\nconst HS_TOWNHOUSE_API_URL = 'http://127.0.0.1:28090';\n\n/**\n * Run `reconciler.reconcile()` with a brief retry budget for cold-boot\n * transients. The connector container may not have bound its admin port\n * by the time `orchestrator.up()` resolves; treat ECONNREFUSED / timeout\n * on early attempts as \"still warming\" and retry. Persistent failures\n * surface to the caller and end up in the non-fatal stderr log.\n */\nasync function reconcileWithBriefRetry(\n reconciler: { reconcile: () => Promise<unknown> },\n budgetMs: number\n): Promise<void> {\n const deadline = Date.now() + budgetMs;\n for (;;) {\n try {\n await reconciler.reconcile();\n return;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n const transient =\n msg.includes('ECONNREFUSED') ||\n msg.includes('connection refused') ||\n msg.includes('request timeout');\n if (!transient || Date.now() >= deadline) {\n throw err;\n }\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n}\n\n/**\n * Boot the apex (connector + townhouse-api) via `townhouse hs up`.\n * Idempotent: if the apex is already running, re-prints the hostname and exits 0.\n * After the apex is live, writes `~/.townhouse/host.json` and prints the final line.\n */\n/**\n * Collect the apex image refs (digest-pinned) that `hs up` should pre-pull\n * before invoking `docker compose up -d`.\n *\n * Apex always-on services (Story 45.2 / 45.4) are connector + townhouse-api.\n * Profile-gated services (town/mill/dvm) are lazy-provisioned via\n * `POST /api/nodes` and excluded here.\n *\n * Returns `[]` (and never throws) if:\n * - `image-manifest.json` is absent under `<configDir>` (local-dev tree\n * without a CI-produced manifest), OR\n * - the manifest exists but cannot be parsed (corrupt file). The caller\n * treats `[]` as \"skip pre-pull narration\" and lets compose handle it.\n */\nasync function collectApexImageRefs(configDir: string): Promise<string[]> {\n const manifestPath = join(configDir, 'image-manifest.json');\n if (!existsSync(manifestPath)) return [];\n try {\n const manifest = await readImageManifest(manifestPath);\n // Skip pre-pull if the manifest is synthetic (smoke-workflow sentinel).\n // Attempting to pull sha256:dead000… from the registry would fail with a\n // cryptic 404; the compose step will handle image resolution correctly.\n if (\n isSyntheticDigest(manifest.images.connector.digest) ||\n isSyntheticDigest(manifest.images['townhouse-api'].digest)\n ) {\n return [];\n }\n return [\n `${manifest.images.connector.name}@${manifest.images.connector.digest}`,\n `${manifest.images['townhouse-api'].name}@${manifest.images['townhouse-api'].digest}`,\n ];\n } catch {\n return [];\n }\n}\n\n/**\n * Returns true when an OrchestratorError is caused by the ATOR anon SDK's\n * hardcoded 60s bootstrap timeout (@anyone-protocol/anyone-client@1.1.x\n * `setupTimeoutHandler`). The connector container goes unhealthy and compose\n * exits 1 — the retry loop in handleHsUp restarts the stack on this signal.\n */\nfunction isAnonBootstrapTimeout(err: unknown): boolean {\n if (!(err instanceof OrchestratorError)) return false;\n const text = `${err.message}\\n${err.stderr ?? ''}`;\n return /connector.*unhealthy|dependency.*connector.*fail/i.test(text);\n}\n\n/**\n * Foreground the Ink dashboard for an already-live apex when stdout is a TTY.\n * Used by both the cold-boot path and the idempotent re-run path, so that\n * re-running `townhouse hs up` against a running node re-attaches the dashboard\n * instead of just printing the hostname and exiting.\n *\n * A TUI mount/runtime failure must NOT be treated as a boot failure — apex is\n * already live — so it is caught and reported as a display issue. No-op on\n * non-TTY stdout (the process then exits naturally after printing the address).\n */\nasync function attachDashboard(hostname: string): Promise<void> {\n if (!shouldRenderInk()) return;\n try {\n const { mountTui } = await import('./tui/index.js');\n // P27 (D1): thread HS_TOWNHOUSE_API_URL env override into the TUI so\n // operators on a non-default Fastify port don't see eternal fetch_failed.\n const apiUrlOverride = process.env['HS_TOWNHOUSE_API_URL'];\n const mountOpts =\n apiUrlOverride !== undefined ? { apiUrl: apiUrlOverride } : {};\n const instance = mountTui(mountOpts);\n await instance.waitUntilExit();\n } catch (tuiErr: unknown) {\n const detail = tuiErr instanceof Error ? tuiErr.message : String(tuiErr);\n console.error('');\n console.error(`Your node is live at ${hostname}.`);\n console.error(\n `The live dashboard could not open (${detail}) — this is a display ` +\n 'issue, not a node issue. Your node keeps running.'\n );\n console.error(\n 'Stop it anytime with: npx @toon-protocol/townhouse hs down'\n );\n // Leave process.exitCode at success — the node is live.\n }\n}\n\nasync function handleHsUp(\n _configPath: string,\n configDir: string,\n config: TownhouseConfig,\n docker: Docker,\n options: {\n password?: string;\n force?: boolean;\n skipPreflight?: boolean;\n hsOverrides?: CliHsOverrides;\n }\n): Promise<void> {\n const { password, force, skipPreflight, hsOverrides } = options;\n\n // ── Idempotency probe (AC #7) — BEFORE the preflight ────────────────────────\n // If our apex is already live, this is a re-run: re-print the address, refresh\n // host.json, and (in a TTY) re-attach the dashboard, then return. This MUST run\n // before the port preflight: the preflight would otherwise flag our OWN apex's\n // canonical ports as a collision and refuse, making an idempotent re-run (and\n // re-attaching the dashboard) impossible. Skipped under --force (cold rebuild).\n if (!force) {\n const adminClientFactory =\n hsOverrides?.createAdminClient ??\n ((url: string, t: number) => new ConnectorAdminClient(url, t));\n const probe = adminClientFactory(HS_CONNECTOR_ADMIN_URL, 3_000);\n try {\n const existing = await probe.getHsHostname();\n if (existing.hostname !== null) {\n // hostname from the connector already includes the .anyone suffix.\n console.log(`Apex live at ${existing.hostname}`);\n _writeHostJson(configDir, {\n hostname: existing.hostname,\n publishedAt: existing.publishedAt ?? new Date().toISOString(),\n writtenAt: new Date().toISOString(),\n });\n await attachDashboard(existing.hostname);\n return;\n }\n // hostname null → apex started but HS not ready → treat as cold-start.\n } catch (probeErr: unknown) {\n const msg =\n probeErr instanceof Error ? probeErr.message : String(probeErr);\n if (msg.includes('anon-disabled')) {\n // Apex running but anon is disabled — render failure copy and exit.\n const { exitCode } = renderFailure(probeErr);\n process.exitCode = exitCode;\n return;\n }\n // ECONNREFUSED / timeout → not running → fall through to preflight + boot.\n }\n }\n\n // ── Preflight: port-collision check (Epic 49 Followup B) ────────────────────\n // Only reached on a true cold start (no apex already live). Catches the most\n // common operator footgun (contributor dev stack still up, another hs up\n // instance, or an unrelated process bound to the canonical ports) and\n // surfaces an actionable error instead of a cryptic mid-boot EADDRINUSE.\n //\n // `--skip-preflight` bypasses the check (escape hatch for operators who\n // know what they're doing — e.g. running two HS stacks on different\n // network interfaces; rare but harmless).\n if (!skipPreflight) {\n const preflight =\n hsOverrides?.checkPortCollisions ??\n ((d: Docker) => checkHsPortCollisions(d));\n try {\n const collisions = await preflight(docker);\n if (collisions.length > 0) {\n const msg = formatCollisionMessage(collisions);\n // Write directly to stderr (multi-line message — console.error adds\n // an extra newline per call which would shred the formatting).\n process.stderr.write(msg);\n process.exitCode = 1;\n return;\n }\n } catch (preflightErr: unknown) {\n // Preflight itself failed unexpectedly (e.g. kernel out of fds). Log\n // and continue rather than block boot — the existing Docker-level\n // EADDRINUSE handler is still there as a fallback.\n const detail =\n preflightErr instanceof Error\n ? preflightErr.message\n : String(preflightErr);\n console.error(\n `[townhouse hs up] port preflight skipped (non-fatal): ${detail}`\n );\n }\n }\n\n // Resolve wallet password (AC #10): --password → env var → interactive prompt → reject\n const walletPath = config.wallet.encrypted_path;\n if (!existsSync(walletPath)) {\n console.error(\n `Wallet not found at ${walletPath}. Run \\`townhouse init\\` first.`\n );\n process.exitCode = 1;\n return;\n }\n\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n\n let resolvedPassword: string;\n if (walletPassword) {\n resolvedPassword = walletPassword;\n } else if (process.stdin.isTTY) {\n resolvedPassword = await promptPassword('Wallet password: ');\n } else {\n // No interactive terminal (CI, SSH without a TTY, piped stdin). Make the\n // reason explicit so the user knows why no prompt appeared and what to do.\n console.error(\n 'Wallet password required, but no interactive terminal is available to prompt.\\n' +\n 'Pass --password <pw> or set TOWNHOUSE_WALLET_PASSWORD.'\n );\n process.exitCode = 1;\n return;\n }\n\n const loaded = await loadWallet(walletPath);\n if (!loaded) {\n console.error(`Wallet at ${walletPath} could not be read.`);\n process.exitCode = 1;\n return;\n }\n\n let walletManager: WalletManager | undefined;\n try {\n walletManager = new WalletManager({ encryptedPath: walletPath });\n await walletManager.fromMnemonic(\n decryptWallet(loaded.wallet, resolvedPassword)\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n const ribbon = new OnboardingRibbon();\n\n try {\n // Cold-boot path. (The idempotency probe runs earlier — above the preflight —\n // so an already-live apex re-attaches instead of failing the port check.)\n\n // Step 1: write connector.yaml with anon.enabled: true (AC #3).\n writeHsConnectorConfig(configDir, config, { force });\n\n // Step 2: materialize compose template.\n const materialize =\n hsOverrides?.materializeComposeTemplate ?? materializeComposeTemplate;\n const { composePath } = materialize('hs', { townhouseHome: configDir });\n\n // Step 2b: write compose/.env from the `network` mode so the compose\n // template's ${EVM_CHAIN}/${EVM_RPC_URL}/${SOLANA_*} interpolations resolve\n // to real public endpoints for the chosen tier (apex + children share the\n // same network profile). Must run after materialize (which creates compose/).\n writeHsNodeEnvFile(configDir, config);\n\n // Step 3: start the ribbon (phase 1 — pulling).\n ribbon.start('pull');\n\n // Step 4: construct orchestrator and wire ribbon events.\n const orchestratorFactory =\n hsOverrides?.createOrchestrator ??\n ((\n d: Docker,\n cfg: TownhouseConfig,\n wm: WalletManager | undefined,\n opts: { profile: 'hs'; composePath: string }\n ) => new DockerOrchestrator(d, cfg, wm, opts));\n\n const orch = orchestratorFactory(docker, config, walletManager, {\n profile: 'hs',\n composePath,\n });\n\n // ── pullProgress narration (Epic 49 Followup D) ───────────────────────\n // Subscribe BEFORE any pulls so we never miss the first events. Uses\n // the throttled narrator to dedupe Downloading/Extracting noise.\n const narrator = new PullNarrator();\n orch.on('pullProgress', (event: unknown) => {\n const ev = event as {\n image?: string;\n status?: string;\n id?: string;\n progress?: string;\n };\n if (!ev.image || !ev.status) return;\n const line = narrator.format({\n image: ev.image,\n status: ev.status,\n id: ev.id,\n progress: ev.progress,\n });\n if (line !== null) console.log(line);\n });\n\n // Transition ribbon to bootstrap phase when a container starts creating.\n let bootstrapStarted = false;\n orch.on('containerState', (event: unknown) => {\n const ev = event as { name?: string; state?: string; detail?: string };\n if (\n !bootstrapStarted &&\n (ev.state === 'creating' || ev.state === 'starting')\n ) {\n bootstrapStarted = true;\n ribbon.start('bootstrap');\n }\n });\n\n // Stop the pull-phase spinner before the pre-pull narration below. The\n // spinner rewrites its line in place (cursor-up + clear), which smears into\n // duplicated \"Pulling apex image…\" lines when the per-image/layer progress\n // prints interleaved underneath it. The narration IS the progress indicator\n // for this phase; the spinner resumes cleanly for the quiet bootstrap wait.\n ribbon.stop();\n\n // ── Cold-pull pre-warm (Epic 49 Followup D) ───────────────────────────\n // Compose's `up -d` with inheritStdio=true is essentially silent on a\n // cold image cache — operators experience a 5-minute black hole. We\n // pre-pull apex images via dockerode (which emits pullProgress) so the\n // narrator above can render layer-state transitions to stdout. The\n // subsequent `docker compose up -d` finds the images cached and proceeds\n // without re-pulling.\n //\n // Image list comes from the materialized image-manifest.json. If the\n // manifest is missing (local dev tree, not an npm install) or pullImage\n // is absent (stale stub), we silently skip the pre-pull and let compose's\n // own pull behaviour stand. The \"Apex live at <hostname>\" success message\n // remains the canonical completion signal.\n if (typeof orch.pullImage === 'function') {\n try {\n const apexImages = await collectApexImageRefs(configDir);\n if (apexImages.length > 0) {\n console.log(\n `Pulling ${apexImages.length} apex ${apexImages.length === 1 ? 'image' : 'images'}...`\n );\n let pulled = 0;\n for (const ref of apexImages) {\n pulled++;\n console.log(` [${pulled}/${apexImages.length}] ${ref}`);\n await orch.pullImage(ref);\n }\n } else {\n // No pinned image manifest (e.g. local dev tree). Compose will pull\n // images on demand during `up` — which can be a multi-minute period\n // with little output. Tell the user so the wait isn't a silent void.\n console.log(\n 'No pinned image manifest found — Docker will pull images on demand.'\n );\n console.log(\n 'First start can take several minutes with limited progress output.'\n );\n }\n } catch (pullErr: unknown) {\n // Non-fatal: compose up will retry the pull and surface a real error if\n // it fails permanently. Narrate it calmly rather than as an alarm.\n const detail =\n pullErr instanceof Error ? pullErr.message : String(pullErr);\n console.log(\n `Could not pre-pull images (${detail}). Docker will pull them during ` +\n 'startup — this is normal and may take a few minutes.'\n );\n }\n }\n\n // Step 5: up (always-on services only — empty profile array).\n // Inject env vars that Docker Compose interpolates in townhouse-hs.yml:\n // TOWNHOUSE_HOME — operator's config dir; replaces hardcoded `~/.townhouse`\n // bind-mount sources so a custom --config-dir (or test tmpDir) actually\n // reaches the containers. Docker does NOT expand `~` in bind-mount\n // sources, so the template must use an explicit interpolation variable.\n // TOWNHOUSE_WALLET_PASSWORD — required by townhouse-api service\n // TOWNHOUSE_UID — run townhouse-api as the host user so bind-mounted\n // ~/.townhouse files (rw------- 600) are readable inside the container\n // TOWNHOUSE_DOCKER_GID — host docker socket group (typically root:docker\n // mode 660 on Linux); added as supplementary group so the non-root\n // container user can read/write /var/run/docker.sock for the\n // `pull-image` step of POST /api/nodes. Without this, dockerode calls\n // from townhouse-api fail with `connect EACCES /var/run/docker.sock`.\n let dockerSockGid = 0;\n try {\n dockerSockGid = statSync('/var/run/docker.sock').gid;\n } catch {\n // Socket missing — operator will see a clearer error at compose-up time.\n // Fallback 0 keeps Compose interpolation valid; the container just won't\n // gain extra group access (matches pre-fix behaviour for that case).\n }\n const prevTownhouseHome = process.env['TOWNHOUSE_HOME'];\n const prevWalletPassword = process.env['TOWNHOUSE_WALLET_PASSWORD'];\n const prevTownhouseUid = process.env['TOWNHOUSE_UID'];\n const prevWalletDir = process.env['TOWNHOUSE_WALLET_DIR'];\n const prevDockerGid = process.env['TOWNHOUSE_DOCKER_GID'];\n process.env['TOWNHOUSE_HOME'] = configDir;\n process.env['TOWNHOUSE_WALLET_PASSWORD'] = resolvedPassword;\n process.env['TOWNHOUSE_UID'] = String(process.getuid?.() ?? 1000);\n // Inject the wallet dir as an absolute host path so the townhouse-api\n // container can find the wallet at the same path as config.wallet.encrypted_path.\n process.env['TOWNHOUSE_WALLET_DIR'] = dirname(\n resolve(config.wallet.encrypted_path)\n );\n process.env['TOWNHOUSE_DOCKER_GID'] = String(dockerSockGid);\n\n // Guarantee the bootstrap phase is narrated before the up-to-90s hostname\n // wait. The containerState event above may never fire with a matching state\n // (observed in plain/non-TTY boots: a silent ~20s gap here), so trigger the\n // phase explicitly. The event handler no-ops once bootstrapStarted is set.\n if (!bootstrapStarted) {\n bootstrapStarted = true;\n ribbon.start('bootstrap');\n }\n\n // Retry up to 3×: the ATOR anon SDK (@anyone-protocol/anyone-client@1.1.x)\n // has a hardcoded 60s bootstrap timeout that fires when relay descriptor\n // loading is slow. `downHs` omits --volumes so the keypair is preserved.\n const MAX_ANON_RETRIES = 3;\n try {\n for (let attempt = 1; attempt <= MAX_ANON_RETRIES; attempt++) {\n try {\n await orch.up([]);\n break;\n } catch (err: unknown) {\n if (isAnonBootstrapTimeout(err) && attempt < MAX_ANON_RETRIES) {\n console.error(\n `[townhouse hs up] ATOR bootstrap timed out (attempt ${attempt}/${MAX_ANON_RETRIES}) — retrying...`\n );\n await orch.down().catch(() => undefined);\n continue;\n }\n throw err;\n }\n }\n } finally {\n if (prevTownhouseHome === undefined) {\n delete process.env['TOWNHOUSE_HOME'];\n } else {\n process.env['TOWNHOUSE_HOME'] = prevTownhouseHome;\n }\n if (prevWalletPassword === undefined) {\n delete process.env['TOWNHOUSE_WALLET_PASSWORD'];\n } else {\n process.env['TOWNHOUSE_WALLET_PASSWORD'] = prevWalletPassword;\n }\n if (prevTownhouseUid === undefined) {\n delete process.env['TOWNHOUSE_UID'];\n } else {\n process.env['TOWNHOUSE_UID'] = prevTownhouseUid;\n }\n if (prevWalletDir === undefined) {\n delete process.env['TOWNHOUSE_WALLET_DIR'];\n } else {\n process.env['TOWNHOUSE_WALLET_DIR'] = prevWalletDir;\n }\n if (prevDockerGid === undefined) {\n delete process.env['TOWNHOUSE_DOCKER_GID'];\n } else {\n process.env['TOWNHOUSE_DOCKER_GID'] = prevDockerGid;\n }\n }\n\n // Step 5b: reconcile connector peer state to nodes.yaml (Story 46.1).\n // Runs after orchestrator.up([]) but BEFORE host.json is written and the\n // hostname is printed. Reconciler divergences are non-fatal — the\n // failure is logged to stderr but does not block apex boot.\n const nodesYamlPath = join(configDir, 'nodes.yaml');\n const reconcilerLogPath = join(configDir, 'reconciler.log');\n const reconcilerFactory =\n hsOverrides?.createReconciler ??\n ((nodesPath: string, logPath: string) => {\n const reconcilerAdminClient = new ConnectorAdminClient(\n HS_CONNECTOR_ADMIN_URL,\n 5_000\n );\n return new BootReconciler(reconcilerAdminClient, nodesPath, logPath);\n });\n const reconciler = reconcilerFactory(nodesYamlPath, reconcilerLogPath);\n // Brief retry on cold-boot transient errors — orchestrator.up() returns\n // once Docker accepts the create call, not when the connector inside\n // the container has bound its admin port. A short retry budget keeps\n // cold-boot stderr quiet on the common \"connector still warming\" case\n // while still surfacing genuine connector-down failures via the final\n // non-fatal log below.\n try {\n await reconcileWithBriefRetry(reconciler, 5_000);\n } catch (reconcilerErr: unknown) {\n const detail =\n reconcilerErr instanceof Error\n ? (reconcilerErr.stack ?? reconcilerErr.message)\n : String(reconcilerErr);\n console.error(\n `[townhouse hs up] reconciler error (non-fatal): ${detail}`\n );\n }\n\n // Step 6: fetch published hostname and publishedAt for host.json (AC #6).\n const adminClientFactory2 =\n hsOverrides?.createAdminClient ??\n ((url: string, t: number) => new ConnectorAdminClient(url, t));\n const adminClient = adminClientFactory2(HS_CONNECTOR_ADMIN_URL, 5_000);\n const hsInfo = await adminClient.getHsHostname();\n\n const hostname = hsInfo.hostname ?? '';\n const publishedAt = hsInfo.publishedAt ?? new Date().toISOString();\n\n // Step 7: write host.json atomically (AC #6).\n _writeHostJson(configDir, {\n hostname,\n publishedAt,\n writtenAt: new Date().toISOString(),\n });\n\n // Step 8: ribbon phase 3 + final stdout line (AC #5).\n // hostname from the connector already includes the .anyone suffix.\n // ribbon.start('live', hostname) prints: \"Apex live at <hostname>\" as the FINAL stdout line.\n ribbon.start('live', hostname);\n\n // Story 48.1: foreground Ink TUI when stdout is a TTY. Apex is already live\n // here (host.json written, \"Apex live at …\" printed above); attachDashboard\n // isolates any TUI failure so it is never reported as a boot failure.\n await attachDashboard(hostname);\n } catch (err: unknown) {\n const { exitCode } = renderFailure(err);\n process.exitCode = exitCode;\n } finally {\n ribbon.stop();\n if (walletManager) {\n walletManager.lock();\n }\n }\n}\n\n/** Atomically write ~/.townhouse/host.json (AC #6). */\nfunction _writeHostJson(\n configDir: string,\n data: { hostname: string; publishedAt: string; writtenAt: string }\n): void {\n const hostJsonPath = join(configDir, 'host.json');\n const tmpPath = `${hostJsonPath}.tmp`;\n // hostname from the connector already includes the .anyone suffix (e.g. \"abc123.anyone\").\n const content = JSON.stringify(\n {\n hostname: data.hostname,\n publishedAt: data.publishedAt,\n connectorAdminUrl: HS_CONNECTOR_ADMIN_URL,\n townhouseApiUrl: HS_TOWNHOUSE_API_URL,\n writtenAt: data.writtenAt,\n },\n null,\n 2\n );\n writeFileSync(tmpPath, content, { mode: 0o600, encoding: 'utf-8' });\n renameSync(tmpPath, hostJsonPath);\n}\n\n/**\n * Stop the apex via `townhouse hs down`.\n * Default: preserves the townhouse-hs-anon volume (stable .anyone address).\n * --rotate-keys: removes the volume (new address on next hs up).\n */\nasync function handleHsDown(\n configDir: string,\n config: TownhouseConfig,\n docker: Docker,\n options: {\n rotateKeys?: boolean;\n hsOverrides?: CliHsOverrides;\n }\n): Promise<void> {\n const { rotateKeys, hsOverrides } = options;\n\n // Materialize compose template to get the composePath (idempotent re-write).\n const materialize =\n hsOverrides?.materializeComposeTemplate ?? materializeComposeTemplate;\n const { composePath } = materialize('hs', { townhouseHome: configDir });\n\n // Export every env var the compose template interpolates, so `docker compose\n // down` parses the same YAML that `up` parsed. Compose's `${VAR:-default}`\n // fallbacks for the wallet dir use a literal `~/.townhouse` which Docker\n // doesn't expand — they only work when handleHsDown explicitly sets the\n // var. TOWNHOUSE_WALLET_PASSWORD has a `${...:?...}` mandatory-error\n // fallback (Finding J — fixed in the same PR by switching to `:-`), so set\n // an empty string here to bypass it if Compose still requires it. Mirrors\n // the env-export pattern in handleHsUp. Discovered by Story 46.4 live gate\n // run (Finding I, 2026-05-11; supersedes PR #51's TOWNHOUSE_HOME-only fix).\n const prevTownhouseHome = process.env['TOWNHOUSE_HOME'];\n const prevTownhouseUid = process.env['TOWNHOUSE_UID'];\n const prevWalletDir = process.env['TOWNHOUSE_WALLET_DIR'];\n const prevDockerGid = process.env['TOWNHOUSE_DOCKER_GID'];\n const prevWalletPassword = process.env['TOWNHOUSE_WALLET_PASSWORD'];\n process.env['TOWNHOUSE_HOME'] = configDir;\n process.env['TOWNHOUSE_UID'] = String(process.getuid?.() ?? 1000);\n process.env['TOWNHOUSE_WALLET_DIR'] = dirname(\n resolve(config.wallet.encrypted_path)\n );\n let dockerSockGid = 0;\n try {\n dockerSockGid = statSync('/var/run/docker.sock').gid;\n } catch {\n /* Docker socket missing — fall back to 0; compose down won't actually use it */\n }\n process.env['TOWNHOUSE_DOCKER_GID'] = String(dockerSockGid);\n // Empty string keeps Compose interpolation valid even if the template still\n // has a mandatory-error fallback. The container side checks the password\n // itself; compose-down doesn't actually need it.\n if (prevWalletPassword === undefined) {\n process.env['TOWNHOUSE_WALLET_PASSWORD'] = '';\n }\n const restoreTownhouseHome = (): void => {\n if (prevTownhouseHome === undefined) {\n delete process.env['TOWNHOUSE_HOME'];\n } else {\n process.env['TOWNHOUSE_HOME'] = prevTownhouseHome;\n }\n if (prevTownhouseUid === undefined) {\n delete process.env['TOWNHOUSE_UID'];\n } else {\n process.env['TOWNHOUSE_UID'] = prevTownhouseUid;\n }\n if (prevWalletDir === undefined) {\n delete process.env['TOWNHOUSE_WALLET_DIR'];\n } else {\n process.env['TOWNHOUSE_WALLET_DIR'] = prevWalletDir;\n }\n if (prevDockerGid === undefined) {\n delete process.env['TOWNHOUSE_DOCKER_GID'];\n } else {\n process.env['TOWNHOUSE_DOCKER_GID'] = prevDockerGid;\n }\n if (prevWalletPassword === undefined) {\n delete process.env['TOWNHOUSE_WALLET_PASSWORD'];\n }\n };\n\n if (rotateKeys) {\n // Confirmation prompt when TTY is available.\n if (process.stdin.isTTY) {\n // Read the existing hostname from host.json for the warning message.\n let existingHostname = '(unknown)';\n const hostJsonPath = join(configDir, 'host.json');\n if (existsSync(hostJsonPath)) {\n try {\n const { readFileSync } = await import('node:fs');\n const json = JSON.parse(readFileSync(hostJsonPath, 'utf-8')) as {\n hostname?: string;\n };\n existingHostname = json.hostname ?? existingHostname;\n } catch {\n // best-effort\n }\n }\n // Use readline for the yes/no confirmation prompt.\n const { createInterface } = await import('node:readline');\n const answer = await new Promise<string>((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n rl.question(\n `WARNING: --rotate-keys will permanently delete your current .anyone address (${existingHostname}). The next 'hs up' will publish a new address. Continue? [y/N] `,\n (ans) => {\n rl.close();\n resolve(ans);\n }\n );\n });\n if (!['y', 'yes'].includes(answer.trim().toLowerCase())) {\n console.log('Cancelled.');\n return;\n }\n }\n\n // Run `docker compose down -v` to remove volumes (including townhouse-hs-anon).\n const runDown = hsOverrides?.runComposeDown ?? _runDockerComposeDown;\n try {\n await runDown(composePath, true);\n } catch (err: unknown) {\n const { exitCode } = renderFailure(err);\n process.exitCode = exitCode;\n restoreTownhouseHome();\n return;\n }\n\n // Delete host.json so the stale hostname doesn't outlive the keypair (AC #9).\n rmSync(join(configDir, 'host.json'), { force: true });\n\n console.log(\n \"Apex stopped. Volumes deleted — your next 'hs up' will publish a NEW .anyone address.\"\n );\n restoreTownhouseHome();\n return;\n }\n\n // Default: preserve volumes (townhouse-hs-anon survives → same hostname next hs up).\n const orchestratorFactory =\n hsOverrides?.createOrchestrator ??\n ((\n d: Docker,\n cfg: TownhouseConfig,\n wm: WalletManager | undefined,\n opts: { profile: 'hs'; composePath: string }\n ) => new DockerOrchestrator(d, cfg, wm, opts));\n\n const orch = orchestratorFactory(docker, config, undefined, {\n profile: 'hs',\n composePath,\n });\n\n try {\n await orch.down();\n } catch (err: unknown) {\n const { exitCode } = renderFailure(err);\n process.exitCode = exitCode;\n restoreTownhouseHome();\n return;\n }\n restoreTownhouseHome();\n\n console.log(\n 'Apex stopped. Volumes preserved — your .anyone address is stable.'\n );\n}\n\n/**\n * Run `docker compose -f <composePath> down [-v]` as a subprocess.\n * Used by handleHsDown's --rotate-keys path (AC #9).\n */\nfunction _runDockerComposeDown(\n composePath: string,\n withVolumes: boolean\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const args = ['compose', '-f', composePath, 'down'];\n if (withVolumes) args.push('-v');\n const child = spawn('docker', args, {\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`docker compose down exited with code ${code}`));\n }\n });\n });\n}\n\n/**\n * Main CLI entry — exported for testability (same pattern as Mill CLI).\n * Accepts optional dockerode instance for dependency injection in tests.\n * The optional `hsOverrides` bag is used by unit tests to stub out Docker,\n * file I/O, and admin-client calls in the `hs up` / `hs down` path.\n */\nconst CHAINS_HELP = `townhouse chains — configure settlement chains (connector chainProviders)\n\nThe connector settles ILP payment claims on these chains. Changes take effect\non the next 'townhouse hs down && townhouse hs up'.\n\nUsage:\n townhouse chains list [--json] [-c <path>]\n townhouse chains add --chain-type <evm|solana|mina> --chain-id <id> [fields] [-c <path>]\n townhouse chains remove <chainId> [-c <path>]\n\nFields by chain type:\n evm: --rpc-url <url> --registry <0x..> --token-address <0x..> --key-id <0x..>\n solana: --rpc-url <url> --program-id <addr> --key-id <id> [--ws-url <url>] [--token-mint <addr>]\n mina: --graphql-url <url> --zkapp <addr> [--key-id <id>]`;\n\ninterface ChainsFlags {\n chainType?: string;\n chainId?: string;\n rpcUrl?: string;\n wsUrl?: string;\n registry?: string;\n tokenAddress?: string;\n tokenMint?: string;\n programId?: string;\n graphqlUrl?: string;\n zkapp?: string;\n keyId?: string;\n}\n\n/** Build a typed ChainProviderEntry from CLI flags. Throws on missing fields. */\nfunction buildChainProviderFromFlags(f: ChainsFlags): ChainProviderEntry {\n const { chainType, chainId } = f;\n if (chainType !== 'evm' && chainType !== 'solana' && chainType !== 'mina') {\n throw new Error('--chain-type must be one of: evm, solana, mina');\n }\n if (!chainId) throw new Error('--chain-id is required');\n\n const require = (flag: string, val: string | undefined): string => {\n if (!val) throw new Error(`${flag} is required for ${chainType} chains`);\n return val;\n };\n\n if (chainType === 'evm') {\n return {\n chainType: 'evm',\n chainId,\n rpcUrl: require('--rpc-url', f.rpcUrl),\n registryAddress: require('--registry', f.registry),\n tokenAddress: require('--token-address', f.tokenAddress),\n keyId: require('--key-id', f.keyId),\n };\n }\n if (chainType === 'solana') {\n return {\n chainType: 'solana',\n chainId,\n rpcUrl: require('--rpc-url', f.rpcUrl),\n ...(f.wsUrl ? { wsUrl: f.wsUrl } : {}),\n programId: require('--program-id', f.programId),\n ...(f.tokenMint ? { tokenMint: f.tokenMint } : {}),\n keyId: require('--key-id', f.keyId),\n };\n }\n // mina\n return {\n chainType: 'mina',\n chainId,\n graphqlUrl: require('--graphql-url', f.graphqlUrl),\n zkAppAddress: require('--zkapp', f.zkapp),\n ...(f.keyId ? { keyId: f.keyId } : {}),\n };\n}\n\n/**\n * `townhouse chains <list|add|remove>` — edit the connector settlement chains\n * (config.chainProviders) for EVM / Solana / Mina without hand-editing YAML.\n */\nasync function handleChains(\n action: string | undefined,\n chainIdArg: string | undefined,\n flags: ChainsFlags,\n configPath: string,\n jsonMode: boolean\n): Promise<void> {\n if (!action) {\n console.log(CHAINS_HELP);\n throw new CliHelpRequested();\n }\n\n const config = loadConfig(configPath);\n const providers: ChainProviderEntry[] = config.chainProviders ?? [];\n\n switch (action) {\n case 'list': {\n if (jsonMode) {\n console.log(JSON.stringify(providers, null, 2));\n return;\n }\n if (providers.length === 0) {\n console.log(\n 'No settlement chains configured — the connector uses a built-in dev-Anvil EVM placeholder.'\n );\n console.log(\n 'Add one with: townhouse chains add --chain-type evm --chain-id evm:base:8453 ...'\n );\n return;\n }\n console.log('Configured settlement chains:');\n for (const p of providers) {\n console.log(` ${p.chainType.padEnd(6)} ${p.chainId}`);\n }\n return;\n }\n case 'add': {\n let entry: ChainProviderEntry;\n try {\n entry = buildChainProviderFromFlags(flags);\n } catch (err: unknown) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n return;\n }\n // Idempotent upsert: replace any existing entry with the same chainId.\n const next = providers.filter((p) => p.chainId !== entry.chainId);\n next.push(entry);\n try {\n saveConfig(configPath, { ...config, chainProviders: next });\n } catch (err: unknown) {\n console.error(\n `Invalid chain config: ${err instanceof Error ? err.message : String(err)}`\n );\n process.exitCode = 1;\n return;\n }\n console.log(\n `Added ${entry.chainType} settlement chain '${entry.chainId}'.`\n );\n console.log('Apply with: townhouse hs down && townhouse hs up');\n return;\n }\n case 'remove': {\n if (!chainIdArg) {\n console.error('Usage: townhouse chains remove <chainId>');\n process.exitCode = 1;\n return;\n }\n const next = providers.filter((p) => p.chainId !== chainIdArg);\n if (next.length === providers.length) {\n console.error(\n `No settlement chain with chainId '${chainIdArg}' found.`\n );\n process.exitCode = 1;\n return;\n }\n saveConfig(configPath, {\n ...config,\n chainProviders: next.length > 0 ? next : undefined,\n });\n console.log(`Removed settlement chain '${chainIdArg}'.`);\n console.log('Apply with: townhouse hs down && townhouse hs up');\n return;\n }\n default: {\n // eslint-disable-next-line no-control-regex\n const safe = action.replace(/[\\x00-\\x1f\\x7f]/g, '');\n console.error(`Unknown chains subcommand: ${safe}`);\n console.log(CHAINS_HELP);\n process.exitCode = 1;\n }\n }\n}\n\nexport async function main(\n argv: string[],\n dockerInstance?: Docker,\n browserOpener?: BrowserOpener,\n hsOverrides?: CliHsOverrides,\n nodeCommandOverrides?: CliNodeCommandOverrides\n): Promise<void> {\n const { values, positionals } = parseArgs({\n args: argv,\n options: {\n help: { type: 'boolean' },\n force: { type: 'boolean' },\n config: { type: 'string', short: 'c' },\n 'config-dir': { type: 'string' },\n town: { type: 'boolean' },\n mill: { type: 'boolean' },\n dvm: { type: 'boolean' },\n password: { type: 'string' },\n 'dry-run': { type: 'boolean' },\n 'no-browser': { type: 'boolean' },\n port: { type: 'string' },\n preset: { type: 'string' },\n network: { type: 'string' },\n yes: { type: 'boolean' },\n 'rotate-keys': { type: 'boolean' },\n 'skip-preflight': { type: 'boolean' },\n json: { type: 'boolean' },\n 'json-compact': { type: 'boolean' },\n lines: { type: 'string' },\n follow: { type: 'boolean', short: 'f' },\n units: { type: 'string' },\n rate: { type: 'string' },\n // credits buy / credits balance (epic-49, Phase 2)\n token: { type: 'string' },\n amount: { type: 'string' },\n 'fee-multiplier': { type: 'string' },\n 'quote-only': { type: 'boolean' },\n 'credit-destination': { type: 'string' },\n // wallet show / wallet seed (epic-49, Phase 3)\n hex: { type: 'boolean' },\n paths: { type: 'boolean' },\n confirm: { type: 'boolean' },\n // chains add (multi-chain settlement config)\n 'chain-type': { type: 'string' },\n 'chain-id': { type: 'string' },\n 'rpc-url': { type: 'string' },\n 'ws-url': { type: 'string' },\n registry: { type: 'string' },\n 'token-address': { type: 'string' },\n 'token-mint': { type: 'string' },\n 'program-id': { type: 'string' },\n 'graphql-url': { type: 'string' },\n zkapp: { type: 'string' },\n 'key-id': { type: 'string' },\n },\n strict: false,\n allowPositionals: true,\n });\n\n const command = positionals[0];\n\n // Handle `townhouse node <verb> --help` before the global --help check so\n // node sub-help takes priority over the global HELP_TEXT.\n if (command === 'node' && values.help) {\n const action = positionals[1];\n const subHelp =\n action === 'add'\n ? NODE_ADD_HELP\n : action === 'remove'\n ? NODE_REMOVE_HELP\n : action === 'list'\n ? NODE_LIST_HELP\n : NODE_HELP;\n console.log(subHelp);\n throw new CliHelpRequested();\n }\n\n if (values.help) {\n console.log(HELP_TEXT);\n throw new CliHelpRequested();\n }\n\n if (!command) {\n console.log(HELP_TEXT);\n throw new CliHelpRequested();\n }\n\n switch (command) {\n case 'setup': {\n const portStr = values['port'] as string | undefined;\n // Reject trailing junk like \"9400foo\" (parseInt would silently accept).\n const port = portStr ? Number(portStr) : 9400;\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n console.error('--port must be an integer between 1 and 65535');\n process.exitCode = 1;\n break;\n }\n await handleSetup(\n values['config-dir'] as string | undefined,\n port,\n values['no-browser'] === true,\n dockerInstance,\n browserOpener\n );\n break;\n }\n case 'init': {\n const presetVal = values.preset as string | undefined;\n if (presetVal !== undefined && presetVal !== 'demo') {\n console.error(`Unknown preset: ${presetVal}. Supported: demo`);\n process.exitCode = 1;\n break;\n }\n const networkVal = values.network as string | undefined;\n if (\n networkVal !== undefined &&\n !['mainnet', 'testnet', 'devnet', 'custom'].includes(networkVal)\n ) {\n console.error(\n `Unknown network: ${networkVal}. Supported: mainnet, testnet, devnet, custom`\n );\n process.exitCode = 1;\n break;\n }\n await handleInit(\n values.force === true,\n values['config-dir'] as string | undefined,\n values.password as string | undefined,\n presetVal,\n values.yes === true,\n networkVal as NetworkMode | undefined\n );\n break;\n }\n case 'wallet': {\n const subCommand = positionals[1];\n if (subCommand === 'show') {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n await handleWalletShow(config, values.password as string | undefined, {\n json: values.json === true,\n hex: values.hex === true,\n paths: values.paths === true,\n });\n } else if (subCommand === 'seed') {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n await handleWalletSeed(\n config,\n values.password as string | undefined,\n values.confirm === true\n );\n } else {\n console.error(\n 'Usage:\\n' +\n ' townhouse wallet show [--json] [--hex] [--paths] [-c <path>] [--password <pw>]\\n' +\n ' townhouse wallet seed --confirm [-c <path>] [--password <pw>]'\n );\n process.exitCode = 1;\n }\n break;\n }\n case 'credits': {\n const subCommand = positionals[1];\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n if (subCommand === 'buy') {\n await handleCreditsBuy(config, values as Record<string, unknown>);\n } else if (subCommand === 'balance') {\n await handleCreditsBalance(config, values as Record<string, unknown>);\n } else {\n console.error(\n 'Usage:\\n' +\n ' townhouse credits buy --token <id> --amount <decimal> [--fee-multiplier <n>] [--quote-only] [--yes] [-c <path>] [--password <pw>]\\n' +\n ' townhouse credits balance --token <id> [-c <path>] [--password <pw>]'\n );\n process.exitCode = 1;\n }\n break;\n }\n case 'status': {\n const configPath = (values['config'] as string) ?? DEFAULT_CONFIG_PATH;\n const rawUnits = (values['units'] as string | undefined) ?? 'usdc';\n if (rawUnits !== 'usdc' && rawUnits !== 'sats') {\n console.error(`--units must be 'usdc' or 'sats'`);\n process.exitCode = 1;\n break;\n }\n let satsPerUsdc: number | undefined;\n if (rawUnits === 'sats') {\n const r = resolveSatsRate(\n values as Record<string, unknown>,\n process.env\n );\n if ('error' in r) {\n console.error(r.error);\n process.exitCode = 1;\n } else {\n satsPerUsdc = r.rate;\n }\n }\n const units = rawUnits as 'usdc' | 'sats';\n await handleStatus(\n dockerInstance ?? new Docker(),\n loadConfig(configPath),\n { units, satsPerUsdc, configPath }\n );\n break;\n }\n case 'up': {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n const docker = dockerInstance ?? new Docker();\n const profiles = resolveProfiles(values, config);\n await handleUp(\n configPath,\n config,\n profiles,\n docker,\n values.password as string | undefined,\n values['dry-run'] === true\n );\n break;\n }\n case 'down': {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n const docker = dockerInstance ?? new Docker();\n await handleDown(config, docker);\n break;\n }\n case 'channels':\n case 'metrics':\n case 'logs':\n case 'peer':\n case 'health': {\n await dispatchDrillCommand(command, {\n adminUrl: HS_CONNECTOR_ADMIN_URL,\n apiUrl: HS_TOWNHOUSE_API_URL,\n values: values as Record<string, unknown>,\n positionals,\n docker: dockerInstance,\n });\n break;\n }\n case 'hs': {\n const action = positionals[1];\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n const docker = dockerInstance ?? new Docker();\n const configDir = dirname(configPath);\n if (action === 'up') {\n await handleHsUp(configPath, configDir, config, docker, {\n password: values.password as string | undefined,\n force: values.force === true,\n skipPreflight: values['skip-preflight'] === true,\n hsOverrides,\n });\n } else if (action === 'down') {\n await handleHsDown(configDir, config, docker, {\n rotateKeys: values['rotate-keys'] === true,\n hsOverrides,\n });\n } else {\n console.error(\n 'Usage: townhouse hs <up|down> [--rotate-keys] [--password <pw>] [-c <path>]'\n );\n process.exitCode = 1;\n }\n break;\n }\n case 'node': {\n const action = positionals[1];\n const jsonMode = values.json === true;\n const yesMode = values.yes === true;\n const nodeApiUrl = nodeCommandOverrides?.apiUrl ?? HS_TOWNHOUSE_API_URL;\n\n if (!action) {\n console.log(NODE_HELP);\n throw new CliHelpRequested();\n }\n\n switch (action) {\n case 'add': {\n const typeArg = positionals[2] ?? 'town';\n await handleNodeAdd(typeArg, {\n json: jsonMode,\n apiUrl: nodeApiUrl,\n fetch: nodeCommandOverrides?.fetch,\n confirm: nodeCommandOverrides?.confirm,\n });\n break;\n }\n case 'remove': {\n const idArg = positionals[2] ?? '';\n await handleNodeRemove(idArg, {\n yes: yesMode,\n json: jsonMode,\n apiUrl: nodeApiUrl,\n fetch: nodeCommandOverrides?.fetch,\n confirm: nodeCommandOverrides?.confirm,\n });\n break;\n }\n case 'list': {\n await handleNodeList({\n json: jsonMode,\n apiUrl: nodeApiUrl,\n fetch: nodeCommandOverrides?.fetch,\n });\n break;\n }\n default: {\n // Sanitize to prevent log injection\n // eslint-disable-next-line no-control-regex\n const safeAction = action.replace(/[\\x00-\\x1f\\x7f]/g, '');\n console.error(`Unknown node subcommand: ${safeAction}`);\n console.log(NODE_HELP);\n process.exitCode = 1;\n }\n }\n break;\n }\n case 'chains': {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const action = positionals[1];\n const chainIdArg = positionals[2];\n const flags: ChainsFlags = {\n chainType: values['chain-type'] as string | undefined,\n chainId: values['chain-id'] as string | undefined,\n rpcUrl: values['rpc-url'] as string | undefined,\n wsUrl: values['ws-url'] as string | undefined,\n registry: values['registry'] as string | undefined,\n tokenAddress: values['token-address'] as string | undefined,\n tokenMint: values['token-mint'] as string | undefined,\n programId: values['program-id'] as string | undefined,\n graphqlUrl: values['graphql-url'] as string | undefined,\n zkapp: values['zkapp'] as string | undefined,\n keyId: values['key-id'] as string | undefined,\n };\n await handleChains(\n action,\n chainIdArg,\n flags,\n configPath,\n values.json === true\n );\n break;\n }\n default: {\n // Sanitize user input to prevent log injection (CWE-117)\n // eslint-disable-next-line no-control-regex\n const sanitized = command.replace(/[\\x00-\\x1f\\x7f]/g, '');\n console.error(`Unknown command: ${sanitized}`);\n console.log(HELP_TEXT);\n process.exitCode = 1;\n }\n }\n}\n\n// Self-invoke when run as entrypoint.\n// process.argv[1] can be a symlink (e.g. node_modules/.bin/townhouse created by\n// npm/npx), while import.meta.url is the realpath of dist/cli.js. Comparing them\n// directly makes the guard false under npx/installed-bin, so main() never runs and\n// every command silently no-ops. Resolve symlinks before comparing.\nconst invokedFile = process.argv[1];\nlet invokedDirectly = false;\nif (typeof invokedFile === 'string') {\n try {\n invokedDirectly =\n import.meta.url === pathToFileURL(realpathSync(invokedFile)).href;\n } catch {\n invokedDirectly = import.meta.url === pathToFileURL(invokedFile).href;\n }\n}\n\nif (invokedDirectly) {\n main(process.argv.slice(2)).catch((error: unknown) => {\n if (error instanceof CliHelpRequested) {\n process.exit(0);\n }\n console.error('[Townhouse] Error:', error);\n process.exit(1);\n });\n}\n","/**\n * Cross-platform browser opener for the wizard CLI command.\n * Uses platform-native launchers; errors are non-fatal.\n */\n\nimport { spawn } from 'node:child_process';\n\nexport interface BrowserOpener {\n open(url: string): Promise<void>;\n}\n\nexport class RealBrowserOpener implements BrowserOpener {\n async open(url: string): Promise<void> {\n let cmd: string;\n let args: string[];\n\n switch (process.platform) {\n case 'darwin':\n cmd = 'open';\n args = [url];\n break;\n case 'win32':\n cmd = 'cmd';\n args = ['/c', 'start', '', url];\n break;\n default:\n cmd = 'xdg-open';\n args = [url];\n break;\n }\n\n return new Promise<void>((resolve) => {\n let settled = false;\n const settle = () => {\n if (settled) return;\n settled = true;\n resolve();\n };\n\n try {\n const child = spawn(cmd, args, {\n stdio: ['ignore', 'ignore', 'ignore'],\n detached: true,\n });\n\n // ENOENT (e.g. xdg-open not on PATH on a minimal container/WSL2 env)\n // surfaces asynchronously via the 'error' event, NOT a synchronous\n // throw from spawn. Subscribe so we don't crash with an unhandled\n // 'error' event and so the user gets a hint about why no browser opened.\n child.once('error', (err: Error) => {\n console.warn(\n `[Townhouse] Could not open browser via ${cmd}: ${err.message}`\n );\n settle();\n });\n child.once('spawn', () => {\n child.unref();\n settle();\n });\n } catch (err: unknown) {\n // Synchronous spawn error path (rare)\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[Townhouse] Could not open browser: ${msg}`);\n settle();\n }\n });\n }\n}\n\nexport class NoopBrowserOpener implements BrowserOpener {\n public readonly calls: string[] = [];\n\n async open(url: string): Promise<void> {\n this.calls.push(url);\n }\n}\n","/**\n * UX-DR4: Three-phase onboarding ribbon for `townhouse hs up` (Story 45.4).\n *\n * Phases: pull → bootstrap → live\n * TTY + unicode: in-place ANSI cursor-up + line-clear rewrite.\n * Fallback (non-TTY, NO_COLOR, CI, dumb terminal): plain lines + ASCII spinner.\n */\n\nconst PHASES = {\n pull: 'Pulling apex image…',\n bootstrap: 'Bootstrapping hidden service (this takes 30–90s)…',\n} as const;\n\nconst SPINNER_FRAMES = ['|', '/', '-', '\\\\'];\n\nfunction isTty(): boolean {\n return process.stdout.isTTY === true;\n}\n\nfunction supportsUnicode(): boolean {\n const term = process.env['TERM'] ?? '';\n if (term === 'dumb') return false;\n if (/xterm|screen|tmux/i.test(term)) return true;\n if (process.env['COLORTERM'] !== undefined) return true;\n return false;\n}\n\nfunction isAnimationDisabled(): boolean {\n if (process.env['NO_COLOR'] !== undefined && process.env['NO_COLOR'] !== '')\n return true;\n if (process.env['CI'] === 'true') return true;\n return false;\n}\n\nfunction useAnsiRewrite(): boolean {\n return isTty() && supportsUnicode() && !isAnimationDisabled();\n}\n\nexport type RibbonPhase = 'pull' | 'bootstrap' | 'live';\n\nexport class OnboardingRibbon {\n private currentPhase: RibbonPhase | null = null;\n private spinnerTimer: ReturnType<typeof setInterval> | null = null;\n private spinnerFrame = 0;\n private hasWrittenLine = false;\n\n start(phase: RibbonPhase, detail?: string): void {\n this._stopSpinner();\n\n if (phase === 'live') {\n const line = detail ? `Apex live at ${detail}` : 'Apex live.';\n this._writeLine(line);\n this.currentPhase = 'live';\n return;\n }\n\n const text = PHASES[phase];\n\n if (useAnsiRewrite() && this.hasWrittenLine) {\n // Move cursor up one line and clear it before writing the new phase.\n process.stdout.write('\\x1b[1A\\x1b[2K');\n }\n\n if (isAnimationDisabled() || !isTty()) {\n this._writeLine(text);\n } else {\n // Start a spinner for the bootstrap/pull phases.\n this._writeLine(`${text} ${SPINNER_FRAMES[0]}`);\n this.spinnerFrame = 1;\n this.spinnerTimer = setInterval(() => {\n const idx = this.spinnerFrame % SPINNER_FRAMES.length;\n const frame = SPINNER_FRAMES[idx] ?? '|';\n this.spinnerFrame++;\n if (useAnsiRewrite()) {\n process.stdout.write('\\x1b[1A\\x1b[2K');\n process.stdout.write(`${text} ${frame}\\n`);\n } else {\n process.stdout.write(`${text} ${frame}\\n`);\n }\n }, 100);\n }\n\n this.currentPhase = phase;\n }\n\n stop(): void {\n this._stopSpinner();\n }\n\n private _stopSpinner(): void {\n if (this.spinnerTimer !== null) {\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n }\n\n private _writeLine(text: string): void {\n process.stdout.write(`${text}\\n`);\n this.hasWrittenLine = true;\n }\n}\n","/**\n * Sally's failure-state copy library — apex-side error classes (UX-DR5 partial, Story 45.4).\n * Covers: anon-timeout, anon-disabled, image-pull-failure, port-collision,\n * missing-docker-sock, and generic fallback.\n */\n\nimport { OrchestratorError } from '../docker/orchestrator.js';\n\ninterface FailureCopyEntry {\n headline: string;\n explanation: string;\n nextStep: string;\n}\n\nconst FAILURE_COPY: Readonly<Record<string, FailureCopyEntry>> = {\n 'anon-timeout': {\n headline: \"Hidden service didn't publish in time.\",\n explanation:\n 'The .anyone descriptor did not publish within the allotted time.',\n nextStep: 'Re-run with DEBUG=townhouse:* for verbose anon logs.',\n },\n 'anon-disabled': {\n headline: 'Connector is anon-disabled.',\n explanation: 'The connector config has anon.enabled: false.',\n nextStep: 'Edit ~/.townhouse/connector.yaml and set anon.enabled: true.',\n },\n 'image-pull-failure': {\n headline: 'Image pull failed.',\n explanation: 'Docker could not pull the required townhouse images.',\n nextStep: 'Check your network and try again.',\n },\n 'port-collision': {\n headline: 'Port already in use.',\n explanation: 'A required host port is already bound by another process.',\n nextStep:\n 'Stop the conflicting service or override the port via --connector-admin-port.',\n },\n 'missing-docker-sock': {\n headline: 'Docker daemon unreachable.',\n explanation:\n 'The Docker socket is not accessible or Docker is not running.',\n nextStep: 'Start Docker and re-run `townhouse hs up`.',\n },\n generic: {\n headline: 'Apex boot failed.',\n explanation: '',\n nextStep: 'Run with DEBUG=townhouse:* for verbose logs.',\n },\n};\n\nfunction supportsUnicode(): boolean {\n const term = process.env['TERM'] ?? '';\n if (term === 'dumb') return false;\n if (/xterm|screen|tmux/i.test(term)) return true;\n if (process.env['COLORTERM'] !== undefined) return true;\n return false;\n}\n\nexport function useAscii(): boolean {\n if (process.env['NO_COLOR'] !== undefined && process.env['NO_COLOR'] !== '')\n return true;\n return !supportsUnicode();\n}\n\ntype ErrorClass =\n | 'anon-timeout'\n | 'anon-disabled'\n | 'image-pull-failure'\n | 'port-collision'\n | 'missing-docker-sock'\n | 'generic';\n\nfunction classify(error: unknown): { key: ErrorClass; explanation: string } {\n const msg = error instanceof Error ? error.message : String(error);\n const isOrchError = error instanceof OrchestratorError;\n const stderr = isOrchError ? (error.stderr ?? '') : '';\n\n // Anon timeout: orchestrator exhausted the 120s polling budget.\n if (msg.includes('HS hostname publication timeout')) {\n return { key: 'anon-timeout', explanation: msg };\n }\n\n // Anon disabled: the orchestrator caught a 503 early-exit during polling\n // (OrchestratorError wrapping the anon-disabled signal) → treat as anon-timeout\n // so the operator gets actionable next steps (verbose logs to diagnose).\n if (isOrchError && msg.includes('anon-disabled')) {\n return { key: 'anon-timeout', explanation: msg };\n }\n\n // Anon disabled: direct 503 from the idempotency probe (plain Error, not OrchestratorError).\n if (!isOrchError && msg.includes('anon-disabled')) {\n return { key: 'anon-disabled', explanation: msg };\n }\n\n // Image pull failure: Docker couldn't fetch the image.\n if (\n stderr.includes('failed to pull') ||\n stderr.includes('pull access denied') ||\n msg.includes('failed to pull') ||\n msg.includes('pull access denied')\n ) {\n return { key: 'image-pull-failure', explanation: msg };\n }\n\n // Port collision: a required port is already in use.\n if (\n stderr.includes('address already in use') ||\n stderr.includes('port is already allocated') ||\n msg.includes('address already in use') ||\n msg.includes('port is already allocated')\n ) {\n return { key: 'port-collision', explanation: msg };\n }\n\n // Missing Docker: daemon not running or docker CLI not found.\n if (\n stderr.includes('Cannot connect to the Docker daemon') ||\n msg.includes('Cannot connect to the Docker daemon') ||\n msg.includes('docker CLI not found on PATH')\n ) {\n return { key: 'missing-docker-sock', explanation: msg };\n }\n\n return { key: 'generic', explanation: msg };\n}\n\n/**\n * Classify `error` and write Sally's three-line failure copy to stderr.\n * Returns `{ exitCode: 1 }` for the caller to propagate via `process.exitCode`.\n */\nexport function renderFailure(error: unknown): { exitCode: number } {\n const ascii = useAscii();\n const { key, explanation } = classify(error);\n\n const entry = FAILURE_COPY[key];\n if (!entry) {\n const xMark = ascii ? '[X]' : '✕';\n const arrow = ascii ? '->' : '→';\n process.stderr.write(`${xMark} Apex boot failed.\\n`);\n process.stderr.write(` ${explanation}\\n`);\n process.stderr.write(\n ` ${arrow} Run with DEBUG=townhouse:* for verbose logs.\\n`\n );\n return { exitCode: 1 };\n }\n\n const xMark = ascii ? '[X]' : '✕';\n const arrow = ascii ? '->' : '→';\n\n const explanationText = key === 'generic' ? explanation : entry.explanation;\n\n process.stderr.write(`${xMark} ${entry.headline}\\n`);\n process.stderr.write(` ${explanationText}\\n`);\n process.stderr.write(` ${arrow} ${entry.nextStep}\\n`);\n\n return { exitCode: 1 };\n}\n","/**\n * Interactive password prompt using `node:readline` with character masking.\n * Used by `townhouse hs up` when neither --password flag nor\n * TOWNHOUSE_WALLET_PASSWORD env var is set and stdin is a TTY (Story 45.4).\n *\n * No external dependencies — uses only Node built-ins per architecture rules.\n */\n\nimport { createInterface } from 'node:readline';\n\n/**\n * Prompt for a password interactively. Masks each typed character with '*'.\n * The mute trick overrides `_writeToOutput` on the Interface instance so\n * every character echo is replaced with `*`.\n *\n * @returns The entered password string (without trailing newline).\n * @throws Never — on I/O errors the returned promise rejects.\n */\nexport function promptPassword(prompt = 'Wallet password: '): Promise<string> {\n return new Promise((resolve, reject) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true,\n });\n\n // Intercept all output from readline and replace with '*' per character.\n // Casting needed: _writeToOutput is a protected internal implementation\n // detail, not in the public Node.js type definitions.\n const iface = rl as unknown as {\n _writeToOutput: (str: string) => void;\n output: NodeJS.WritableStream;\n };\n\n const origWrite = iface._writeToOutput.bind(iface);\n iface._writeToOutput = (str: string) => {\n // Pass through control sequences (cursor movement, newlines) but mask\n // printable characters.\n if (str === '\\r\\n' || str === '\\n' || str === '\\r') {\n // Let the newline through so the cursor advances.\n origWrite(str);\n } else if (/^[\\x20-\\x7e-]/.test(str)) {\n // Printable character — mask with '*'.\n origWrite('*'.repeat(str.length));\n } else {\n // Control sequence — pass through unchanged.\n origWrite(str);\n }\n };\n\n rl.question(prompt, (answer) => {\n // Restore original writer before closing so subsequent console.log\n // calls are not masked.\n iface._writeToOutput = origWrite;\n // Emit a newline so the terminal cursor lands on the next line.\n process.stdout.write('\\n');\n rl.close();\n resolve(answer);\n });\n\n rl.once('error', (err) => {\n iface._writeToOutput = origWrite;\n rl.close();\n reject(err);\n });\n\n rl.once('close', () => {\n // No-op: resolved above or rejected above.\n });\n });\n}\n","/**\n * Port-collision preflight for `townhouse hs up` (Epic 49 Followup B).\n *\n * Detects host-port conflicts BEFORE handing off to Docker, so operators get\n * an actionable error message instead of a cryptic mid-boot EADDRINUSE.\n *\n * Detection strategy (defense in depth):\n * 1. Bind a transient TCP socket to 127.0.0.1:<port> and immediately close.\n * If bind throws EADDRINUSE, the port is occupied. Pure Node, no deps,\n * works on Linux/Mac/WSL — this is the source of truth.\n * 2. If (1) flags a collision, ask Docker for the offending container's\n * name + compose project so the message can name a culprit. Best-effort\n * enrichment — Docker may be unreachable, in which case we still report\n * the port and suggest `lsof` for non-Docker processes.\n */\n\nimport { createServer } from 'node:net';\nimport type { AddressInfo } from 'node:net';\nimport type Docker from 'dockerode';\nimport type { ContainerInfo } from 'dockerode';\n\n/**\n * Canonical HS-mode host ports — sourced from the compose template at\n * `packages/townhouse/compose/townhouse-hs.yml`. If that template is edited\n * to bind a new port (or unbind an existing one), update this list.\n *\n * HS-mode is single-tenant by design (per packages/townhouse/README.md), so\n * these ports cannot be remapped — collisions MUST be cleared before boot.\n *\n * 9401 : connector admin\n * 28090 : townhouse-api Fastify\n * 7100 : town relay WebSocket (profile: town, lazy-provisioned)\n * 3100 : town BLS health (profile: town, lazy-provisioned)\n * 3200 : mill BLS health (profile: mill, lazy-provisioned)\n * 3400 : dvm BLS health (profile: dvm, lazy-provisioned)\n *\n * Profile-gated peer ports are checked too: even though `townhouse hs up`\n * boots only connector + townhouse-api at apex install, the SAME compose\n * file is re-parsed when `townhouse node add <type>` lazy-provisions a peer.\n * Catching all six up-front means `hs up` succeeds AND the operator's\n * subsequent `node add town` won't fail with the same EADDRINUSE error.\n */\nexport const HS_CANONICAL_PORTS: readonly number[] = [\n 9401, 28090, 7100, 3100, 3200, 3400,\n];\n\nexport interface PortCollision {\n /** The host port that is already bound. */\n port: number;\n /** Name of the Docker container holding the port (when known). */\n containerName?: string;\n /** Compose project the container belongs to (when known). */\n composeProject?: string;\n /** Container status string e.g. \"Up 5 hours\" (when known). */\n status?: string;\n}\n\n/**\n * Probe one port: bind a transient TCP server to 127.0.0.1:<port> and close\n * it immediately. Returns true if the bind fails with EADDRINUSE, false if\n * the port is free, throws for other errors (kernel issues, permissions).\n */\nexport async function isPortInUse(port: number): Promise<boolean> {\n return new Promise<boolean>((resolve, reject) => {\n const server = createServer();\n let settled = false;\n\n const finalize = (result: boolean | Error): void => {\n if (settled) return;\n settled = true;\n // Drop listeners so `close` doesn't re-trigger them.\n server.removeAllListeners('error');\n server.removeAllListeners('listening');\n try {\n server.close();\n } catch {\n /* best-effort */\n }\n if (result instanceof Error) reject(result);\n else resolve(result);\n };\n\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n finalize(true);\n } else {\n finalize(err);\n }\n });\n\n server.once('listening', () => {\n // Capture the actual port to confirm the bind succeeded; then close.\n const addr = server.address() as AddressInfo | null;\n void addr; // touched for clarity; not used further\n finalize(false);\n });\n\n try {\n // exclusive:true ensures we don't share a socket with another listener.\n server.listen({ port, host: '127.0.0.1', exclusive: true });\n } catch (err) {\n finalize(err as Error);\n }\n });\n}\n\n/**\n * Walk a dockerode `ContainerInfo.Ports[]` for an entry that maps host port\n * `port` on 127.0.0.1 or 0.0.0.0. Returns the container's display name and\n * compose project label if found, undefined otherwise.\n */\nfunction findDockerCulprit(\n containers: readonly ContainerInfo[],\n port: number\n):\n | Pick<PortCollision, 'containerName' | 'composeProject' | 'status'>\n | undefined {\n for (const c of containers) {\n const ports = c.Ports ?? [];\n for (const p of ports) {\n // PublicPort is the host-side port; IP \"\" or \"0.0.0.0\" or \"127.0.0.1\" all bind to loopback's view.\n if (p.PublicPort === port) {\n // Names come back as [\"/foo\"] — strip the leading slash.\n const rawName = c.Names?.[0] ?? '';\n const name = rawName.startsWith('/') ? rawName.slice(1) : rawName;\n const project = c.Labels?.['com.docker.compose.project'];\n return {\n containerName: name || undefined,\n composeProject: project,\n status: c.Status,\n };\n }\n }\n }\n return undefined;\n}\n\n/**\n * Preflight check for `townhouse hs up`. Probes each canonical HS port for\n * a collision and (if Docker is reachable) enriches each collision with the\n * offending container's name + compose project.\n *\n * Pure logic: never throws on Docker failures, never calls process.exit, never\n * writes to stdout/stderr. Returns `[]` when all ports are free.\n *\n * @param docker - Dockerode instance for enrichment. If undefined or\n * unreachable, collisions are still reported (port-only).\n * @param ports - List of ports to probe. Defaults to HS_CANONICAL_PORTS.\n */\nexport async function checkHsPortCollisions(\n docker?: Pick<Docker, 'listContainers'>,\n ports: readonly number[] = HS_CANONICAL_PORTS\n): Promise<PortCollision[]> {\n // 1) Socket-bind probe in parallel. Fast — sub-millisecond per port.\n const probes = await Promise.all(\n ports.map(async (port) => {\n try {\n const inUse = await isPortInUse(port);\n return { port, inUse, probeError: undefined as Error | undefined };\n } catch (err) {\n // Unexpected bind error (EACCES on privileged port, EMFILE, etc.).\n // Treat as \"unknown\" — surface as a collision so the operator\n // investigates rather than as a silent pass.\n return {\n port,\n inUse: true,\n probeError: err instanceof Error ? err : new Error(String(err)),\n };\n }\n })\n );\n\n const taken = probes.filter((p) => p.inUse);\n if (taken.length === 0) return [];\n\n // 2) Enrich with Docker info (best effort).\n let containers: readonly ContainerInfo[] = [];\n if (docker) {\n try {\n containers = await docker.listContainers({ all: false });\n } catch {\n // Docker unreachable or daemon down — fall through with empty list.\n containers = [];\n }\n }\n\n return taken.map((t) => {\n const culprit = findDockerCulprit(containers, t.port);\n return {\n port: t.port,\n ...(culprit ?? {}),\n };\n });\n}\n\n/**\n * Format port collisions into a multi-line operator-facing error message.\n * Designed to be written to stderr; ends with a trailing newline.\n *\n * Shape (matches the spec):\n *\n * townhouse hs up: cannot start — host ports already in use:\n *\n * 127.0.0.1:9401 in use by container 'townhouse-hs-connector'\n * (compose project 'compose', Up 5 hours)\n * 127.0.0.1:3100 port in use (no Docker container found — try `sudo lsof -iTCP:3100 -sTCP:LISTEN`)\n *\n * The HS template needs canonical ports — it cannot remap.\n * Stop the conflicting project to free them:\n *\n * docker compose -p <project> down\n *\n * Or, if the conflicting process is NOT a townhouse stack, identify it with:\n *\n * sudo lsof -iTCP:<port> -sTCP:LISTEN\n *\n * Re-run with --skip-preflight to bypass this check.\n */\nexport function formatCollisionMessage(\n collisions: readonly PortCollision[]\n): string {\n if (collisions.length === 0) return '';\n\n const lines: string[] = [];\n lines.push('townhouse hs up: cannot start — host ports already in use:');\n lines.push('');\n\n for (const c of collisions) {\n const portLabel = `127.0.0.1:${c.port}`.padEnd(18);\n if (c.containerName) {\n lines.push(` ${portLabel}in use by container '${c.containerName}'`);\n const project = c.composeProject ?? '<no compose project>';\n const status = c.status ? `, ${c.status}` : '';\n lines.push(` ${' '.repeat(18)}(compose project '${project}'${status})`);\n } else {\n lines.push(\n ` ${portLabel}port in use (no Docker container found — try \\`sudo lsof -iTCP:${c.port} -sTCP:LISTEN\\`)`\n );\n }\n }\n\n lines.push('');\n lines.push('The HS template needs canonical ports — it cannot remap.');\n\n // Suggest a `docker compose -p <project> down` for the most-common project\n // (typically `compose` or `townhouse-hs`). Dedupe to avoid spamming.\n const projects = new Set<string>();\n for (const c of collisions) {\n if (c.composeProject) projects.add(c.composeProject);\n }\n if (projects.size > 0) {\n lines.push('Stop the conflicting project to free them:');\n lines.push('');\n for (const project of projects) {\n lines.push(` docker compose -p ${project} down`);\n }\n lines.push('');\n lines.push(\n 'Or, if the conflicting process is NOT a townhouse stack, identify it with:'\n );\n } else {\n lines.push('Identify the conflicting processes with:');\n }\n\n lines.push('');\n // Pick the first collision's port for the example lsof command — keeps the\n // message concrete rather than dropping `<port>` placeholder text.\n const examplePort = collisions[0]?.port ?? 9401;\n lines.push(` sudo lsof -iTCP:${examplePort} -sTCP:LISTEN`);\n lines.push('');\n lines.push('Re-run with --skip-preflight to bypass this check.');\n\n return lines.join('\\n') + '\\n';\n}\n","/**\n * Pull-progress narrator (Epic 49 Followup D).\n *\n * Dockerode's `pullProgress` events fire many times per second per layer\n * (e.g., a single 80 MB layer emits one event per a few KB downloaded). If we\n * naively forwarded every event to stdout the operator would see ~thousands of\n * lines per image — exactly the noise we are trying to fix.\n *\n * This narrator:\n * 1. Always prints layer-state TRANSITIONS (`Pulling fs layer` →\n * `Downloading` → `Extracting` → `Pull complete`). Operators care about\n * these because each one tells them the pull is still alive.\n * 2. Throttles repeated `Downloading`/`Extracting` updates to at most one\n * line per second PER IMAGE so a slow download still narrates progress\n * without flooding.\n * 3. Always passes through final image-level statuses\n * (`Status: Downloaded newer image for ...`,\n * `Status: Image is up to date for ...`,\n * `Pull complete`, `Already exists`) — the operator's signal that the\n * image is done.\n *\n * It is a pure stateful object — no I/O — so unit tests can drive a sequence\n * of events and assert the rendered lines deterministically.\n */\n\n/** Subset of the dockerode pull-progress event shape we consume. */\nexport interface PullProgressEvent {\n image: string;\n status: string;\n id?: string | undefined;\n progress?: string | undefined;\n}\n\n/** Per-image throttle bookkeeping. */\ninterface ImageState {\n /** Last status string we PRINTED for this image (any layer). */\n lastStatus: string | undefined;\n /** Wall-clock ms of the last printed `Downloading`/`Extracting` line. */\n lastThrottledAtMs: number;\n}\n\n/**\n * Status strings that are noisy (\"happens many times per second per layer\")\n * and therefore subject to the per-image 1Hz throttle.\n *\n * Everything NOT in this set is treated as a transition and printed verbatim.\n */\nconst THROTTLED_STATUSES = new Set(['Downloading', 'Extracting']);\n\nexport interface PullNarratorOptions {\n /** Wall-clock source. Defaults to `Date.now`. Tests inject a fake clock. */\n now?: () => number;\n /** Throttle interval for noisy statuses (ms). Default 1000. */\n throttleMs?: number;\n}\n\n/**\n * Throttling state machine for pull-progress events. Stateful but I/O-free.\n *\n * Usage:\n * ```\n * const narrator = new PullNarrator();\n * orch.on('pullProgress', (event) => {\n * const line = narrator.format(event);\n * if (line !== null) console.log(line);\n * });\n * ```\n */\nexport class PullNarrator {\n private readonly now: () => number;\n private readonly throttleMs: number;\n private readonly perImage = new Map<string, ImageState>();\n\n constructor(options: PullNarratorOptions = {}) {\n this.now = options.now ?? Date.now;\n this.throttleMs = options.throttleMs ?? 1000;\n }\n\n /**\n * Render an event to a stdout-ready line, or `null` if it should be\n * suppressed by the throttle.\n */\n format(event: PullProgressEvent): string | null {\n const status = event.status;\n // Drop empty-status events (dockerode occasionally emits them on errored\n // streams). Nothing useful to narrate.\n if (!status) {\n return null;\n }\n const state = this.perImage.get(event.image) ?? {\n lastStatus: undefined,\n lastThrottledAtMs: 0,\n };\n\n const isThrottled = THROTTLED_STATUSES.has(status);\n const isTransition = state.lastStatus !== status;\n\n if (isThrottled && !isTransition) {\n // Same noisy status as last print — apply the per-image 1Hz throttle.\n const elapsed = this.now() - state.lastThrottledAtMs;\n if (elapsed < this.throttleMs) {\n return null;\n }\n }\n\n // Print this event. Update bookkeeping.\n state.lastStatus = status;\n if (isThrottled) {\n state.lastThrottledAtMs = this.now();\n }\n this.perImage.set(event.image, state);\n\n const progress = event.progress ? ` ${event.progress}` : '';\n return ` [pull] ${event.image}: ${status}${progress}`;\n }\n\n /**\n * Reset the narrator's per-image state. Useful between separate pull\n * batches in the same process.\n */\n reset(): void {\n this.perImage.clear();\n }\n}\n","/**\n * CLI node lifecycle handlers: `townhouse node add` / `remove` / `list`.\n *\n * D4 DECISION (Story 46.3):\n * In HS mode, `nodes.yaml` is the single source of truth for provisioned nodes.\n * `townhouse node add <type>` ignores `config.nodes[type].enabled` entirely —\n * the static flag is the source of truth for the `dev` profile (`townhouse up\n * --town`) only. Epic 46 lazy provisioning and the static dev-profile config\n * are orthogonal: lifecycle-managed nodes answer to nodes.yaml, not the flag.\n */\n\nimport * as readline from 'node:readline';\nimport { renderFailure, useAscii } from './failure-copy.js';\n\n// Default Townhouse host API URL for HS mode.\nconst DEFAULT_HS_API_URL = 'http://127.0.0.1:28090';\n\n// Maps server-side step identifiers to the user-visible stage labels.\nconst STEP_TO_STAGE: Record<string, string> = {\n preflight: 'Preflight',\n 'derive-key': 'Deriving wallet',\n 'pull-image': 'Pulling image',\n 'write-yaml': 'Deriving wallet', // same disk-class bucket from operator POV\n 'write-mill-config': 'Deriving wallet', // same disk-class bucket as write-yaml\n 'start-container': 'Registering with apex',\n healthcheck: 'Registering with apex',\n 'register-peer': 'Live',\n};\n\nconst STAGE_LABELS = [\n 'Pulling image',\n 'Deriving wallet',\n 'Registering with apex',\n 'Live',\n];\n\n/** Sub-help text printed by `townhouse node <verb> --help`. */\nexport const NODE_ADD_HELP = `townhouse node add — Provision a child node\n\nUsage:\n townhouse node add [<type>] [--json] [-c <path>]\n\nArguments:\n <type> Node type to provision: town, mill, dvm (default: town)\n\nFlags:\n --json Machine-readable JSON output\n -c Path to config file\n\nExamples:\n townhouse node add # provision a Town relay (default)\n townhouse node add town # same as above\n townhouse node add mill # earn from chain swaps (5x earnings unlock)\n townhouse node add dvm # add a DVM compute node`;\n\nexport const NODE_REMOVE_HELP = `townhouse node remove — Deprovision a child node\n\nUsage:\n townhouse node remove <id> [--yes] [--json] [-c <path>]\n\nArguments:\n <id> Node ID to remove (use 'townhouse node list' to find IDs)\n\nFlags:\n --yes Skip confirmation prompt (required in non-interactive mode)\n --json Machine-readable JSON output; implies non-interactive (no prompt)\n -c Path to config file`;\n\nexport const NODE_LIST_HELP = `townhouse node list — List provisioned nodes\n\nUsage:\n townhouse node list [--json] [-c <path>]\n\nFlags:\n --json Machine-readable JSON output (emits API response verbatim)\n -c Path to config file`;\n\nexport const NODE_HELP = `townhouse node — Manage child nodes\n\nUsage:\n townhouse node add [<type>] [--json] [-c <path>] Provision a child node (default: town)\n townhouse node remove <id> [--yes] [--json] [-c <path>] Deprovision a child node\n townhouse node list [--json] [-c <path>] List provisioned nodes\n\nRun 'townhouse node <verb> --help' for details on each verb.\n\nTip:\n townhouse node add mill # earn from chain swaps (5x earnings unlock)`;\n\n// ── Shared helpers ─────────────────────────────────────────────────────────────\n\ntype FetchFn = (url: string, init?: RequestInit) => Promise<Response>;\n\nfunction resolveApiUrl(apiUrl?: string): string {\n return apiUrl ?? DEFAULT_HS_API_URL;\n}\n\nfunction formatRelativeTime(iso: string): string {\n const ts = new Date(iso).getTime();\n if (Number.isNaN(ts)) return '—';\n const diffMs = Date.now() - ts;\n if (diffMs < 0) return 'just now';\n const secs = Math.floor(diffMs / 1000);\n if (secs < 60) return `${secs}s ago`;\n const mins = Math.floor(secs / 60);\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.floor(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n return `${Math.floor(hours / 24)}d ago`;\n}\n\nasync function confirmInteractive(question: string): Promise<boolean> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n try {\n const answer = await new Promise<string>((resolve) =>\n rl.question(question, resolve)\n );\n return /^y(es)?$/i.test(answer.trim());\n } finally {\n rl.close();\n }\n}\n\nfunction emitJsonError(obj: Record<string, unknown>, exitCode = 1): void {\n process.stdout.write(JSON.stringify(obj) + '\\n');\n process.exitCode = exitCode;\n}\n\n// ── handleNodeAdd ──────────────────────────────────────────────────────────────\n\nexport interface NodeAddOptions {\n json: boolean;\n apiUrl?: string;\n fetch?: FetchFn;\n confirm?: (question: string) => Promise<boolean>;\n}\n\nexport async function handleNodeAdd(\n type: string,\n options: NodeAddOptions\n): Promise<void> {\n const ascii = useAscii();\n const check = ascii ? '[OK]' : '✓';\n const xMark = ascii ? '[X]' : '✕';\n const dot = ascii ? '.' : '·';\n\n if (type !== 'town' && type !== 'mill' && type !== 'dvm') {\n const msg = `Unknown type: '${type}'. Supported: town, mill, dvm`;\n if (options.json) {\n emitJsonError({ ok: false, error: 'invalid_type', message: msg });\n } else {\n process.stderr.write(`${xMark} ${msg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n\n const url = resolveApiUrl(options.apiUrl);\n const fetchImpl = options.fetch ?? fetch;\n\n if (!options.json) {\n // Print all stages dim while waiting for the blocking POST to return.\n process.stdout.write(\n ` ${STAGE_LABELS.map((s) => `${dot} ${s}`).join(' · ')}\\n`\n );\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 120_000);\n\n let response: Response;\n try {\n response = await fetchImpl(`${url}/api/nodes`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ type }),\n signal: controller.signal,\n });\n } catch (err: unknown) {\n clearTimeout(timer);\n const isAborted = err instanceof Error && err.name === 'AbortError';\n const errMsg = isAborted\n ? 'Request timed out after 120 seconds.'\n : \"townhouse hs up isn't running. Run 'townhouse hs up' first.\";\n if (options.json) {\n emitJsonError({\n ok: false,\n error: isAborted ? 'timeout' : 'econnrefused',\n message: errMsg,\n });\n } else {\n process.stderr.write(`${xMark} ${errMsg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n clearTimeout(timer);\n\n if (response.status === 201) {\n const body = (await response.json().catch(() => ({}))) as {\n id?: string;\n type?: string;\n peerId?: string;\n ilpAddress?: string;\n hsRoute?: string;\n healthCheckUrl?: string;\n };\n if (options.json) {\n process.stdout.write(JSON.stringify({ ok: true, ...body }) + '\\n');\n } else {\n // Re-print all stages green.\n process.stdout.write(\n ` ${STAGE_LABELS.map((s) => `${check} ${s}`).join(' · ')}\\n`\n );\n const addedId = body.id ?? type;\n const addedPeer = body.peerId ? ` (${body.peerId})` : '';\n const addedAddr = body.ilpAddress ? ` at ${body.ilpAddress}` : '';\n process.stdout.write(` Added ${addedId}${addedPeer}${addedAddr}\\n`);\n }\n return;\n }\n\n // Error path — surface the step field from the response body.\n const body = (await response.json().catch(() => ({}))) as {\n step?: string;\n err?: string;\n rollbackError?: string;\n error?: string;\n type?: string;\n existingId?: string;\n };\n\n if (options.json) {\n emitJsonError({ ok: false, ...body });\n return;\n }\n\n // 409 node_type_in_use\n if (response.status === 409 && body.error === 'node_type_in_use') {\n // The desired node already exists — only one node per type is supported\n // (v1 constraint). Point the operator at how to inspect it and how to\n // recreate it, rather than the unhelpful \"use a different type\" (there are\n // only three types, and they likely already have the ones they want).\n const existingId = body.existingId ?? body.type ?? type;\n process.stderr.write(\n `${xMark} A '${body.type}' node already exists (id '${existingId}'). ` +\n `Only one node per type is supported.\\n` +\n ` See your nodes: townhouse node list\\n` +\n ` Recreate it: townhouse node remove ${existingId} && townhouse node add ${body.type}\\n`\n );\n process.exitCode = 1;\n return;\n }\n\n // 409 node_lifecycle_in_flight\n if (response.status === 409 && body.error === 'node_lifecycle_in_flight') {\n process.stderr.write(\n `${xMark} Another node operation is in flight. Try again in a moment.\\n`\n );\n process.exitCode = 1;\n return;\n }\n\n const step = body.step ?? 'unknown';\n const errText = body.err ?? '';\n\n // pull-image → use renderFailure with synthetic error matching classifier.\n if (step === 'pull-image') {\n const syntheticErr = new Error(`failed to pull: ${errText}`);\n renderFailure(syntheticErr);\n } else if (\n step === 'start-container' &&\n (errText.includes('port is already allocated') ||\n errText.includes('Cannot connect to the Docker daemon'))\n ) {\n renderFailure(new Error(errText));\n } else if (step === 'preflight') {\n // Preflight failures are configuration errors — the stack is healthy and\n // must NOT be torn down. Give targeted advice instead of the generic\n // \"hs down && hs up\" which would destroy a working stack for no reason.\n const arrow = ascii ? '->' : '→';\n process.stderr.write(`${xMark} ${errText}\\n`);\n process.stderr.write(\n ` ${arrow} Fix the configuration above, then retry 'townhouse node add'.\\n`\n );\n } else {\n // Generic 3-line failure for all other steps.\n const stageName = STEP_TO_STAGE[step] ?? step;\n const arrow = ascii ? '->' : '→';\n process.stderr.write(\n `${xMark} Step ${step} failed (stage: ${stageName}): ${errText}\\n`\n );\n process.stderr.write(\n ` ${arrow} Run 'townhouse hs down && townhouse hs up' to reset state, then retry.\\n`\n );\n }\n\n if (body.rollbackError) {\n process.stderr.write(` Rollback error: ${body.rollbackError}\\n`);\n }\n\n process.exitCode = 1;\n}\n\n// ── handleNodeRemove ───────────────────────────────────────────────────────────\n\nexport interface NodeRemoveOptions {\n yes: boolean;\n json: boolean;\n apiUrl?: string;\n fetch?: FetchFn;\n confirm?: (question: string) => Promise<boolean>;\n}\n\nconst NODE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\n\nexport async function handleNodeRemove(\n id: string,\n options: NodeRemoveOptions\n): Promise<void> {\n const ascii = useAscii();\n const check = ascii ? '[OK]' : '✓';\n const xMark = ascii ? '[X]' : '✕';\n\n if (!id) {\n const msg = 'Usage: townhouse node remove <id> [--yes] [--json]';\n if (options.json) {\n emitJsonError({ ok: false, error: 'missing_id', message: msg });\n } else {\n process.stderr.write(`${msg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n\n // Sanitize id against the route pattern before sending (fail fast, no round-trip).\n if (!NODE_ID_PATTERN.test(id)) {\n const msg = `Invalid node id '${id}'. IDs must match ^[a-z][a-z0-9-]*$ (lowercase, no leading hyphens or underscores).`;\n if (options.json) {\n emitJsonError({ ok: false, error: 'invalid_id', message: msg });\n } else {\n process.stderr.write(`${xMark} ${msg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n\n // Confirmation gate.\n const skipPrompt = options.yes || options.json;\n if (!skipPrompt) {\n if (!process.stdin.isTTY) {\n const msg =\n '--yes required when stdin is not a TTY (use --yes for non-interactive removal).';\n process.stderr.write(`${xMark} ${msg}\\n`);\n process.exitCode = 1;\n return;\n }\n const confirmFn = options.confirm ?? confirmInteractive;\n const confirmed = await confirmFn(\n `Remove node '${id}'? This deprovisions the container and deregisters the peer. [y/N] `\n );\n if (!confirmed) {\n process.stdout.write('Cancelled.\\n');\n return;\n }\n }\n\n const url = resolveApiUrl(options.apiUrl);\n const fetchImpl = options.fetch ?? fetch;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 60_000);\n\n let response: Response;\n try {\n response = await fetchImpl(`${url}/api/nodes/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n signal: controller.signal,\n });\n } catch (err: unknown) {\n clearTimeout(timer);\n const isAborted = err instanceof Error && err.name === 'AbortError';\n const errMsg = isAborted\n ? 'Request timed out.'\n : \"townhouse hs up isn't running. Run 'townhouse hs up' first.\";\n if (options.json) {\n emitJsonError({\n ok: false,\n error: isAborted ? 'timeout' : 'econnrefused',\n message: errMsg,\n });\n } else {\n process.stderr.write(`${xMark} ${errMsg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n clearTimeout(timer);\n\n if (response.status === 200) {\n const body = (await response.json().catch(() => ({}))) as {\n id?: string;\n type?: string;\n };\n const removedId = body.id ?? id;\n if (options.json) {\n process.stdout.write(\n JSON.stringify({ ok: true, id: removedId, type: body.type }) + '\\n'\n );\n } else {\n process.stdout.write(`${check} Removed ${removedId}\\n`);\n }\n return;\n }\n\n const body = (await response.json().catch(() => ({}))) as {\n error?: string;\n step?: string;\n err?: string;\n id?: string;\n };\n\n if (options.json) {\n emitJsonError({ ok: false, ...body });\n return;\n }\n\n if (response.status === 404) {\n process.stderr.write(`${xMark} No node with id '${id}'\\n`);\n } else if (\n response.status === 409 &&\n body.error === 'node_lifecycle_in_flight'\n ) {\n process.stderr.write(\n `${xMark} Another node operation is in flight. Try again in a moment.\\n`\n );\n } else {\n const step = body.step ?? 'unknown';\n process.stderr.write(`${xMark} Step ${step} failed: ${body.err ?? ''}\\n`);\n }\n process.exitCode = 1;\n}\n\n// ── handleNodeList ─────────────────────────────────────────────────────────────\n\nexport interface NodeListOptions {\n json: boolean;\n apiUrl?: string;\n fetch?: FetchFn;\n}\n\ninterface NodeEntry {\n id: string;\n type: string;\n peerId: string;\n ilpAddress: string;\n status: 'connected' | 'disconnected' | 'unknown';\n enabledAt: string;\n lastSeenAt: string | null;\n}\n\nexport async function handleNodeList(options: NodeListOptions): Promise<void> {\n const ascii = useAscii();\n const xMark = ascii ? '[X]' : '✕';\n const emDash = ascii ? '-' : '—';\n\n const url = resolveApiUrl(options.apiUrl);\n const fetchImpl = options.fetch ?? fetch;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 30_000);\n\n let response: Response;\n try {\n response = await fetchImpl(`${url}/api/nodes`, {\n method: 'GET',\n signal: controller.signal,\n });\n } catch (err: unknown) {\n clearTimeout(timer);\n const isAborted = err instanceof Error && err.name === 'AbortError';\n const errMsg = isAborted\n ? 'Request timed out.'\n : \"townhouse hs up isn't running. Run 'townhouse hs up' first.\";\n if (options.json) {\n emitJsonError({\n ok: false,\n error: isAborted ? 'timeout' : 'econnrefused',\n message: errMsg,\n });\n } else {\n process.stderr.write(`${xMark} ${errMsg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n clearTimeout(timer);\n\n if (response.status !== 200) {\n const body = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n if (options.json) {\n emitJsonError({ ok: false, ...body });\n } else {\n process.stderr.write(\n `${xMark} Failed to fetch nodes (HTTP ${response.status})\\n`\n );\n process.exitCode = 1;\n }\n return;\n }\n\n const body = (await response.json().catch(() => ({ nodes: [] }))) as {\n nodes?: NodeEntry[];\n };\n const nodes = body.nodes ?? [];\n\n if (options.json) {\n // Emit the API response body verbatim (no `ok` envelope) — consistent with kubectl -o json.\n process.stdout.write(JSON.stringify({ nodes }) + '\\n');\n return;\n }\n\n if (nodes.length === 0) {\n process.stdout.write(\n \"No nodes provisioned. Run 'townhouse node add town' to add one.\\n\"\n );\n return;\n }\n\n // Print 4-column table. Header labels follow AC #5 literal (`peer · type ·\n // status · last claim`). Column widths grow to fit the longest value so long\n // IDs / peer handles are never silently sliced.\n const rows = nodes.map((node) => ({\n peer: node.id,\n type: node.type,\n status: node.status,\n lastClaim:\n node.lastSeenAt !== null ? formatRelativeTime(node.lastSeenAt) : emDash,\n }));\n\n const HEADERS = {\n peer: 'peer',\n type: 'type',\n status: 'status',\n lastClaim: 'last claim',\n };\n const widths = {\n peer: Math.max(HEADERS.peer.length, ...rows.map((r) => r.peer.length)),\n type: Math.max(HEADERS.type.length, ...rows.map((r) => r.type.length)),\n status: Math.max(\n HEADERS.status.length,\n ...rows.map((r) => r.status.length)\n ),\n lastClaim: Math.max(\n HEADERS.lastClaim.length,\n ...rows.map((r) => r.lastClaim.length)\n ),\n };\n\n function pad(s: string, width: number): string {\n return s.length >= width ? s : s + ' '.repeat(width - s.length);\n }\n\n const divider = ascii ? '-' : '─';\n process.stdout.write(\n `${pad(HEADERS.peer, widths.peer)} ${pad(HEADERS.type, widths.type)} ${pad(HEADERS.status, widths.status)} ${HEADERS.lastClaim}\\n`\n );\n process.stdout.write(\n `${divider.repeat(widths.peer)} ${divider.repeat(widths.type)} ${divider.repeat(widths.status)} ${divider.repeat(widths.lastClaim)}\\n`\n );\n\n for (const row of rows) {\n process.stdout.write(\n `${pad(row.peer, widths.peer)} ${pad(row.type, widths.type)} ${pad(row.status, widths.status)} ${row.lastClaim}\\n`\n );\n }\n}\n","/**\n * Drill-subcommand handlers: channels, metrics (moved from cli.ts), logs, peer, health.\n *\n * Boundary: imports ONLY from ../connector/, ../docker/log-tail.js,\n * ../tui/format.js, ../constants.js, dockerode, and node:* stdlib.\n * No imports from ../api/, ../tui/components/, ../earnings/, or ../docker/orchestrator.js.\n */\n\nimport Docker from 'dockerode';\nimport { CONTAINER_PREFIX } from '../constants.js';\nimport { ConnectorAdminClient } from '../connector/admin-client.js';\nimport type {\n ChannelSummary,\n MetricsResponse,\n PeerStatus,\n PeerEarnings,\n} from '../connector/types.js';\nimport {\n tailContainerLogs,\n serviceFromContainerName,\n LOG_SERVICES,\n type LogService,\n} from '../docker/log-tail.js';\nimport { formatRelativeTime } from '../tui/format.js';\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport interface DrillOptions {\n json: boolean;\n jsonCompact: boolean;\n now?: Date;\n adminClient?: ConnectorAdminClient;\n apiUrl?: string;\n fetch?: typeof fetch;\n docker?: Docker;\n}\n\nexport interface ProbeResult {\n source: string;\n status:\n | 'healthy'\n | 'unhealthy'\n | 'unreachable'\n | 'unknown'\n | 'starting'\n | 'n/a'\n | 'degraded';\n error?: string;\n uptime?: number;\n peersConnected?: number;\n totalPeers?: number;\n startedAt?: string;\n version?: string;\n hostname?: string;\n publishedAt?: string;\n message?: string;\n}\n\n// ── Help text constants ────────────────────────────────────────────────────────\n\nexport const CHANNELS_HELP = ` townhouse channels [--json] [-c <path>] Show open payment channels`;\nexport const LOGS_HELP = ` townhouse logs <node-id> [--lines N] [--json] [-c <path>] Tail logs for a node (Ctrl-C to stop)`;\nexport const PEER_HELP = ` townhouse peer <id> [--json] [-c <path>] Show per-peer detail card`;\nexport const HEALTH_HELP = ` townhouse health [--json] [-c <path>] Probe apex/api/nodes/.anyone health`;\n\n// ── Shared helpers ─────────────────────────────────────────────────────────────\n\nfunction truncate16(s: string): string {\n return s.length > 16 ? s.slice(0, 16) + '…' : s;\n}\n\nfunction emitJson(payload: unknown, opts: { compact: boolean }): void {\n process.stdout.write(\n JSON.stringify(payload, null, opts.compact ? 0 : 2) + '\\n'\n );\n}\n\nexport function emitJsonError(\n message: string,\n code: string,\n opts: { compact: boolean }\n): void {\n process.stdout.write(\n JSON.stringify({ error: message, code }, null, opts.compact ? 0 : 2) + '\\n'\n );\n process.exitCode = 1;\n}\n\n// ── handleChannels ──────────────────────────────────────────────────────────────\n\nexport async function handleChannels(\n adminClient: ConnectorAdminClient,\n opts: DrillOptions\n): Promise<void> {\n let channels: ChannelSummary[];\n try {\n channels = await adminClient.getChannels();\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n if (opts.json) {\n emitJsonError(\n `Failed to fetch connector channels: ${msg}`,\n 'unreachable',\n opts\n );\n } else {\n console.error(`Failed to fetch connector channels: ${msg}`);\n process.exitCode = 1;\n }\n return;\n }\n\n if (opts.json) {\n emitJson(channels, opts);\n return;\n }\n\n if (channels.length === 0) {\n console.log('No channels open');\n return;\n }\n\n const now = opts.now ?? new Date();\n const HEADERS = {\n channel: 'CHANNEL',\n peer: 'PEER',\n chain: 'CHAIN',\n status: 'STATUS',\n deposit: 'DEPOSIT',\n lastActivity: 'LAST ACTIVITY',\n };\n\n const rows = channels.map((c) => ({\n channel: truncate16(c.channelId),\n peer: truncate16(c.peerId),\n chain: c.chain,\n status: c.status,\n deposit: c.deposit,\n lastActivity: formatRelativeTime(c.lastActivity, now),\n }));\n\n const widths = {\n channel: Math.max(\n HEADERS.channel.length,\n ...rows.map((r) => r.channel.length)\n ),\n peer: Math.max(HEADERS.peer.length, ...rows.map((r) => r.peer.length)),\n chain: Math.max(HEADERS.chain.length, ...rows.map((r) => r.chain.length)),\n status: Math.max(\n HEADERS.status.length,\n ...rows.map((r) => r.status.length)\n ),\n deposit: Math.max(\n HEADERS.deposit.length,\n ...rows.map((r) => r.deposit.length)\n ),\n lastActivity: Math.max(\n HEADERS.lastActivity.length,\n ...rows.map((r) => r.lastActivity.length)\n ),\n };\n\n const header =\n HEADERS.channel.padEnd(widths.channel) +\n ' ' +\n HEADERS.peer.padEnd(widths.peer) +\n ' ' +\n HEADERS.chain.padEnd(widths.chain) +\n ' ' +\n HEADERS.status.padEnd(widths.status) +\n ' ' +\n HEADERS.deposit.padEnd(widths.deposit) +\n ' ' +\n HEADERS.lastActivity;\n console.log(header);\n console.log('-'.repeat(header.length));\n\n for (const row of rows) {\n console.log(\n row.channel.padEnd(widths.channel) +\n ' ' +\n row.peer.padEnd(widths.peer) +\n ' ' +\n row.chain.padEnd(widths.chain) +\n ' ' +\n row.status.padEnd(widths.status) +\n ' ' +\n row.deposit.padEnd(widths.deposit) +\n ' ' +\n row.lastActivity\n );\n }\n}\n\n// ── handleMetrics (moved from cli.ts) ──────────────────────────────────────────\n\nexport async function handleMetrics(\n adminClient: ConnectorAdminClient,\n opts: DrillOptions\n): Promise<void> {\n try {\n const metrics: MetricsResponse = await adminClient.getMetrics();\n const peers: PeerStatus[] = await adminClient.getPeers();\n\n const peerMetrics = new Map(metrics.peers.map((p) => [p.peerId, p]));\n\n if (opts.json) {\n emitJson(\n {\n aggregate: metrics.aggregate,\n peers: metrics.peers,\n peersDetail: peers,\n uptimeSeconds: metrics.uptimeSeconds,\n timestamp: metrics.timestamp,\n },\n opts\n );\n return;\n }\n\n const now = opts.now ?? new Date();\n\n console.log('Connector Metrics:');\n console.log('------------------');\n console.log(` Packets forwarded: ${metrics.aggregate.packetsForwarded}`);\n console.log(` Packets rejected: ${metrics.aggregate.packetsRejected}`);\n console.log(` Bytes sent: ${metrics.aggregate.bytesSent}`);\n console.log('');\n console.log('Peers:');\n console.log('------');\n if (peers.length === 0) {\n console.log(' No peers connected');\n } else {\n const HEADERS = {\n peer: 'PEER',\n connected: 'STATUS',\n packetsForwarded: 'PACKETS FWD',\n packetsRejected: 'PACKETS REJ',\n bytesSent: 'BYTES SENT',\n lastPacket: 'LAST PACKET',\n };\n\n const rows = peers.map((peer) => {\n const pm = peerMetrics.get(peer.id);\n return {\n peer: peer.id,\n connected: peer.connected ? 'connected' : 'disconnected',\n packetsForwarded: String(pm?.packetsForwarded ?? 0),\n packetsRejected: String(pm?.packetsRejected ?? 0),\n bytesSent: String(pm?.bytesSent ?? 0),\n lastPacket:\n pm?.lastPacketAt != null\n ? formatRelativeTime(pm.lastPacketAt, now)\n : '—',\n };\n });\n\n const widths = {\n peer: Math.max(HEADERS.peer.length, ...rows.map((r) => r.peer.length)),\n connected: Math.max(\n HEADERS.connected.length,\n ...rows.map((r) => r.connected.length)\n ),\n packetsForwarded: Math.max(\n HEADERS.packetsForwarded.length,\n ...rows.map((r) => r.packetsForwarded.length)\n ),\n packetsRejected: Math.max(\n HEADERS.packetsRejected.length,\n ...rows.map((r) => r.packetsRejected.length)\n ),\n bytesSent: Math.max(\n HEADERS.bytesSent.length,\n ...rows.map((r) => r.bytesSent.length)\n ),\n lastPacket: Math.max(\n HEADERS.lastPacket.length,\n ...rows.map((r) => r.lastPacket.length)\n ),\n };\n\n const headerLine =\n ` ${HEADERS.peer.padEnd(widths.peer)} ` +\n `${HEADERS.connected.padEnd(widths.connected)} ` +\n `${HEADERS.packetsForwarded.padEnd(widths.packetsForwarded)} ` +\n `${HEADERS.packetsRejected.padEnd(widths.packetsRejected)} ` +\n `${HEADERS.bytesSent.padEnd(widths.bytesSent)} ` +\n HEADERS.lastPacket;\n console.log(headerLine);\n console.log(` ${'-'.repeat(headerLine.trim().length)}`);\n for (const row of rows) {\n console.log(\n ` ${row.peer.padEnd(widths.peer)} ` +\n `${row.connected.padEnd(widths.connected)} ` +\n `${row.packetsForwarded.padEnd(widths.packetsForwarded)} ` +\n `${row.packetsRejected.padEnd(widths.packetsRejected)} ` +\n `${row.bytesSent.padEnd(widths.bytesSent)} ` +\n row.lastPacket\n );\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n if (opts.json) {\n emitJsonError(\n `Failed to fetch connector metrics: ${msg}`,\n 'unreachable',\n opts\n );\n } else {\n console.error(`Failed to fetch connector metrics: ${msg}`);\n process.exitCode = 1;\n }\n }\n}\n\n// ── handleLogs ──────────────────────────────────────────────────────────────────\n\ninterface LogsOpts extends DrillOptions {\n lines: number;\n}\n\nasync function resolveContainerName(\n docker: Docker,\n nodeId: string\n): Promise<\n { name: string; service: LogService } | { error: string; code: string }\n> {\n let containers: { Names: string[] }[];\n try {\n containers = (await docker.listContainers({ all: false })) as {\n Names: string[];\n }[];\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n error: `Cannot connect to docker daemon: ${msg}. Is docker running?`,\n code: 'docker-unavailable',\n };\n }\n\n const allNames = containers.flatMap((c) =>\n c.Names.map((n) => n.replace(/^\\//, ''))\n );\n\n // Rule 1: verbatim match if starts with CONTAINER_PREFIX. Verify it actually\n // exists in the running container set so a typo like `townhouse-conector`\n // surfaces as `unknown-node` rather than bubbling out as a raw dockerode 404.\n if (nodeId.startsWith(CONTAINER_PREFIX)) {\n if (!allNames.includes(nodeId)) {\n return {\n error: `Node \"${nodeId}\" is not running (no container named \"${nodeId}\").`,\n code: 'unknown-node',\n };\n }\n const svc = serviceFromContainerName(nodeId) ?? ('town' as LogService);\n return { name: nodeId, service: svc };\n }\n\n // Rule 2: build candidate set\n const candidates: { name: string; service: LogService }[] = [];\n\n // Exact prefix match: townhouse-<nodeId>\n const exactName = `${CONTAINER_PREFIX}${nodeId}`;\n if (allNames.includes(exactName)) {\n const svc = serviceFromContainerName(exactName) ?? ('town' as LogService);\n candidates.push({ name: exactName, service: svc });\n }\n\n // Service-class match: nodeId is a bare service tag (e.g. 'town')\n const isService = (LOG_SERVICES as readonly string[]).includes(nodeId);\n if (isService) {\n for (const name of allNames) {\n if (name === exactName) continue; // already covered above\n const svc = serviceFromContainerName(name);\n if (svc === nodeId) {\n candidates.push({ name, service: svc });\n }\n }\n }\n\n const unique = candidates.filter(\n (c, i) => candidates.findIndex((x) => x.name === c.name) === i\n );\n\n if (unique.length === 0) {\n const resolvedName = `${CONTAINER_PREFIX}${nodeId}`;\n return {\n error: `Node \"${nodeId}\" is not running (no container named \"${resolvedName}\").`,\n code: 'unknown-node',\n };\n }\n\n if (unique.length > 1) {\n const names = unique.map((c) => c.name).join(', ');\n return {\n error: `Ambiguous node-id \"${nodeId}\" — matches multiple containers: ${names}. Use the full container name.`,\n code: 'ambiguous-node',\n };\n }\n\n const first = unique[0];\n if (first === undefined) {\n return {\n error: `Internal error resolving container name for \"${nodeId}\"`,\n code: 'internal',\n };\n }\n return first;\n}\n\nexport async function handleLogs(\n docker: Docker,\n nodeId: string,\n opts: LogsOpts\n): Promise<void> {\n const resolved = await resolveContainerName(docker, nodeId);\n if ('error' in resolved) {\n if (opts.json) {\n emitJsonError(resolved.error, resolved.code, opts);\n } else {\n process.stderr.write(resolved.error + '\\n');\n process.exitCode = 1;\n }\n return;\n }\n\n const { name: containerName, service } = resolved;\n\n const controller = new AbortController();\n\n // SIGINT → abort the stream, drain stdout, then exit honoring exitCode.\n // Avoids both (a) a 50ms race that masked errors arriving in that window\n // and (b) buffer truncation on `townhouse logs --json | jq` pipelines.\n const sigintHandler = () => {\n controller.abort();\n process.stdout.write('', () => {\n process.exit(process.exitCode ?? 0);\n });\n };\n process.once('SIGINT', sigintHandler);\n\n try {\n const gen = tailContainerLogs(docker, containerName, service, {\n tail: opts.lines,\n signal: controller.signal,\n });\n\n for await (const evt of gen) {\n if (opts.json) {\n process.stdout.write(JSON.stringify(evt) + '\\n');\n } else {\n process.stdout.write(\n `${evt.ts} [${evt.service}] ${evt.level}: ${evt.msg}\\n`\n );\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n const isDockerError =\n (msg.includes('ENOENT') && msg.includes('/var/run/docker.sock')) ||\n msg.includes('connect ENOENT') ||\n msg.includes('Cannot connect to the Docker daemon') ||\n (msg.includes('ECONNREFUSED') && msg.includes('docker'));\n if (isDockerError) {\n const errMsg = `Cannot connect to docker daemon: ${msg}. Is docker running?`;\n if (opts.json) {\n emitJsonError(errMsg, 'docker-unavailable', opts);\n } else {\n process.stderr.write(errMsg + '\\n');\n process.exitCode = 1;\n }\n } else {\n const errMsg = `Log stream error for \"${nodeId}\": ${msg}`;\n if (opts.json) {\n emitJsonError(errMsg, 'internal', opts);\n } else {\n process.stderr.write(errMsg + '\\n');\n process.exitCode = 1;\n }\n }\n } finally {\n process.off('SIGINT', sigintHandler);\n }\n}\n\n// ── handlePeerDetail ────────────────────────────────────────────────────────────\n\nexport async function handlePeerDetail(\n adminClient: ConnectorAdminClient,\n peerId: string,\n opts: DrillOptions\n): Promise<void> {\n const now = opts.now ?? new Date();\n\n let peers: PeerStatus[];\n try {\n peers = await adminClient.getPeers();\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n if (opts.json) {\n emitJsonError(msg, 'unreachable', opts);\n } else {\n process.stderr.write(`Failed to fetch peers: ${msg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n\n const peer = peers.find((p) => p.id === peerId);\n if (peer === undefined) {\n const errMsg = `Unknown peer \"${peerId}\". Use \\`townhouse metrics\\` to see registered peers.`;\n if (opts.json) {\n emitJsonError(errMsg, 'unknown-peer', opts);\n } else {\n process.stderr.write(errMsg + '\\n');\n process.exitCode = 1;\n }\n return;\n }\n\n const [earningsRaw, channelsRaw] = await Promise.all([\n adminClient.getEarnings().catch(() => null),\n adminClient.getChannels().catch(() => null),\n ]);\n\n const peerEarnings: PeerEarnings | null =\n earningsRaw?.peers.find((p) => p.peerId === peerId) ?? null;\n const peerChannels: ChannelSummary[] =\n channelsRaw?.filter((c) => c.peerId === peerId) ?? [];\n\n if (opts.json) {\n // Per AC #8: earnings is null when byAsset[] is empty OR when the\n // earnings endpoint returned 503 (already nulled by the .catch above).\n const earningsForJson =\n peerEarnings && peerEarnings.byAsset.length > 0 ? peerEarnings : null;\n emitJson(\n {\n peer,\n earnings: earningsForJson,\n channels: peerChannels,\n },\n opts\n );\n return;\n }\n\n // Human mode — card display\n console.log(`Peer: ${peerId}`);\n console.log('');\n\n // ILP section\n if (peer.ilpAddresses.length === 0) {\n console.log(' (no ILP addresses registered)');\n } else {\n for (const addr of peer.ilpAddresses) {\n console.log(` ${addr}`);\n }\n }\n console.log(` Routes: ${peer.routeCount}`);\n console.log('');\n\n // Status section\n console.log(`Connected: ${peer.connected ? 'yes' : 'no'}`);\n console.log('');\n\n // Earnings section\n if (earningsRaw === null) {\n console.log('Earnings:');\n console.log(\n ' (earnings endpoint unavailable: connector is not settlement-configured)'\n );\n } else if (peerEarnings === null || peerEarnings.byAsset.length === 0) {\n console.log('Earnings:');\n console.log(' (no settlement activity yet)');\n } else {\n console.log('Earnings:');\n for (const asset of peerEarnings.byAsset) {\n const lastClaim = asset.lastClaimAt\n ? formatRelativeTime(asset.lastClaimAt, now)\n : 'never';\n console.log(\n ` ${asset.assetCode} · received ${asset.claimsReceivedTotal} · sent ${asset.claimsSentTotal} · net ${asset.netBalance} · last claim ${lastClaim}`\n );\n }\n }\n console.log('');\n\n // Channels section\n if (channelsRaw === null) {\n console.log('Channels:');\n console.log(\n ' (channels endpoint unavailable: connector is not settlement-configured)'\n );\n } else if (peerChannels.length === 0) {\n console.log('Channels:');\n console.log(' (no channels open)');\n } else {\n console.log('Channels:');\n for (const ch of peerChannels) {\n console.log(\n ` ${truncate16(ch.channelId)} · ${ch.chain} · ${ch.status} · deposit ${ch.deposit} · ${formatRelativeTime(ch.lastActivity, now)}`\n );\n }\n }\n}\n\n// ── handleHealth ────────────────────────────────────────────────────────────────\n\nconst PROBE_TIMEOUT_MS = 3000;\n\nasync function probeConnector(\n adminClient: ConnectorAdminClient\n): Promise<ProbeResult> {\n try {\n // The townhouse host has only the admin port (9401) reachable, not the\n // connector's healthCheckPort (8080, internal). The admin server's /health\n // returns a slim shape that getHealth()'s validator rejects, so use\n // pingAdminLive() which only checks status code.\n await adminClient.pingAdminLive();\n return { source: 'connector', status: 'healthy' };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return { source: 'connector', status: 'unreachable', error: msg };\n }\n}\n\nasync function probeHostApi(\n apiUrl: string,\n fetchImpl: typeof fetch\n): Promise<ProbeResult> {\n try {\n const response = await fetchImpl(`${apiUrl}/health`, {\n signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),\n });\n if (!response.ok) {\n return {\n source: 'api',\n status: 'unhealthy',\n error: `HTTP ${response.status}`,\n };\n }\n const body = (await response.json()) as {\n status: string;\n uptime: number;\n startedAt: string;\n version: string;\n };\n return {\n source: 'api',\n status: 'healthy',\n uptime: body.uptime,\n startedAt: body.startedAt,\n version: body.version,\n };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return { source: 'api', status: 'unreachable', error: msg };\n }\n}\n\nasync function probeNodes(\n apiUrl: string,\n fetchImpl: typeof fetch\n): Promise<ProbeResult[]> {\n let nodes: { id: string }[];\n try {\n const resp = await fetchImpl(`${apiUrl}/api/nodes`, {\n signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),\n });\n if (!resp.ok) {\n // Surface enumeration failure as a sentinel probe so it counts toward\n // computeOverall instead of silently disappearing as \"no nodes\".\n return [\n {\n source: 'nodes',\n status: 'unknown',\n error: `failed to enumerate nodes: HTTP ${resp.status}`,\n },\n ];\n }\n const body = (await resp.json()) as { nodes?: { id: string }[] };\n nodes = body.nodes ?? [];\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return [\n {\n source: 'nodes',\n status: 'unknown',\n error: `failed to enumerate nodes: ${msg}`,\n },\n ];\n }\n\n return Promise.all(\n nodes.map(async (node) => {\n try {\n const resp = await fetchImpl(\n `${apiUrl}/api/nodes/${encodeURIComponent(node.id)}/health`,\n {\n signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),\n }\n );\n if (!resp.ok) {\n return {\n source: `node:${node.id}`,\n status: 'unhealthy' as const,\n error: `HTTP ${resp.status}`,\n };\n }\n const body = (await resp.json()) as { status?: string };\n const s = body.status;\n const status: ProbeResult['status'] =\n s === 'healthy'\n ? 'healthy'\n : s === 'unhealthy'\n ? 'unhealthy'\n : s === 'starting'\n ? 'starting'\n : s === 'degraded'\n ? 'degraded'\n : 'unknown';\n return { source: `node:${node.id}`, status };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n source: `node:${node.id}`,\n status: 'unreachable' as const,\n error: msg,\n };\n }\n })\n );\n}\n\nasync function probeAnyone(\n adminClient: ConnectorAdminClient\n): Promise<ProbeResult> {\n try {\n const result = await adminClient.getHsHostname();\n if (result.hostname !== null) {\n return {\n source: 'anyone-hostname',\n status: 'healthy',\n hostname: result.hostname,\n publishedAt: result.publishedAt ?? undefined,\n };\n }\n return {\n source: 'anyone-hostname',\n status: 'starting',\n message: 'anon publish pending',\n };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n // Accept both the bare prefix from getHsHostname() and any wrapped 503\n // (proxies/middleware that wrap the throw); avoids classifying \"feature\n // off\" as \"unreachable\" when the path picks up wrapping.\n if (\n msg.startsWith('connector is anon-disabled') ||\n /(?:^|:\\s)503\\b/.test(msg)\n ) {\n return {\n source: 'anyone-hostname',\n status: 'n/a',\n message: 'anon disabled in config',\n };\n }\n return { source: 'anyone-hostname', status: 'unreachable', error: msg };\n }\n}\n\nfunction computeOverall(\n probes: ProbeResult[]\n): 'healthy' | 'degraded' | 'unhealthy' {\n const statuses = probes.map((p) => p.status);\n if (\n statuses.some(\n (s) => s === 'unhealthy' || s === 'unreachable' || s === 'unknown'\n )\n ) {\n return 'unhealthy';\n }\n // A per-node `degraded` probe must surface at the rollup; otherwise a\n // partially-degraded fleet rolls up to healthy.\n if (statuses.some((s) => s === 'starting' || s === 'degraded')) {\n return 'degraded';\n }\n return 'healthy';\n}\n\nexport async function handleHealth(\n adminClient: ConnectorAdminClient,\n opts: DrillOptions\n): Promise<void> {\n const apiUrl = opts.apiUrl ?? 'http://127.0.0.1:28090';\n const fetchImpl = opts.fetch ?? fetch;\n\n // Build a 3-second-timeout version of the admin client for connector probe.\n // Read baseUrl through the public getter so a future field rename surfaces\n // as a type error instead of a silent fallback to the hardcoded port.\n const healthClient =\n opts.adminClient ??\n new ConnectorAdminClient(adminClient.getBaseUrl(), PROBE_TIMEOUT_MS);\n\n const [connectorProbe, apiProbe, nodeProbes, anyoneProbe] = await Promise.all(\n [\n probeConnector(healthClient),\n probeHostApi(apiUrl, fetchImpl),\n probeNodes(apiUrl, fetchImpl),\n probeAnyone(healthClient),\n ]\n );\n\n const probes: ProbeResult[] = [\n connectorProbe,\n apiProbe,\n ...nodeProbes,\n anyoneProbe,\n ];\n const overall = computeOverall(probes);\n\n if (opts.json) {\n emitJson({ overall, probes }, opts);\n } else {\n for (const probe of probes) {\n console.log(`${probe.source}: ${probe.status}`);\n if (probe.error) console.log(` error: ${probe.error}`);\n if (probe.uptime !== undefined) console.log(` uptime: ${probe.uptime}s`);\n if (probe.peersConnected !== undefined)\n console.log(\n ` peers: ${probe.peersConnected}/${probe.totalPeers ?? '?'} connected`\n );\n if (probe.startedAt) console.log(` startedAt: ${probe.startedAt}`);\n if (probe.version) console.log(` version: ${probe.version}`);\n if (probe.hostname) console.log(` hostname: ${probe.hostname}`);\n if (probe.publishedAt) console.log(` publishedAt: ${probe.publishedAt}`);\n if (probe.message) console.log(` ${probe.message}`);\n }\n console.log(`Overall: ${overall}`);\n }\n\n if (overall === 'unhealthy') {\n process.exitCode = 1;\n }\n}\n\n// ── dispatchDrillCommand ────────────────────────────────────────────────────────\n\n/**\n * Dispatcher for the five drill verbs (channels, metrics, logs, peer, health).\n * Centralises arg parsing, --json error envelope routing, and admin-client\n * construction so cli.ts stays thin. Returns true when the command was handled.\n *\n * `--json` validation errors (missing positional, bad --lines, etc.) emit a\n * `{ error, code }` envelope to stdout instead of plain stderr text — the\n * universal contract the human-mode handlers already follow.\n */\nexport interface DispatchDrillDeps {\n adminUrl: string;\n apiUrl: string;\n values: Record<string, unknown>;\n positionals: string[];\n docker?: Docker;\n}\n\nexport async function dispatchDrillCommand(\n command: string,\n deps: DispatchDrillDeps\n): Promise<boolean> {\n const { values, positionals, adminUrl, apiUrl } = deps;\n const json = values['json'] === true;\n const jsonCompact = values['json-compact'] === true;\n const baseOpts = { json, jsonCompact };\n\n const usageError = (msg: string, code: string): void => {\n if (json) emitJsonError(msg, code, baseOpts);\n else {\n console.error(msg);\n process.exitCode = 1;\n }\n };\n\n switch (command) {\n case 'channels': {\n await handleChannels(new ConnectorAdminClient(adminUrl), baseOpts);\n return true;\n }\n case 'metrics': {\n await handleMetrics(new ConnectorAdminClient(adminUrl), baseOpts);\n return true;\n }\n case 'logs': {\n const nodeId = positionals[1];\n if (!nodeId) {\n usageError(\n 'Usage: townhouse logs <node-id> [--lines N] [-f|--follow] [--json]',\n 'usage'\n );\n return true;\n }\n const linesRaw = values['lines'] as string | undefined;\n // Strict integer parse: reject empty/whitespace, scientific notation, hex.\n // Number() coerces all of those silently; the help text says \"an integer\".\n let lines = 50;\n if (linesRaw !== undefined) {\n if (!/^\\d+$/.test(linesRaw)) {\n usageError(\n '--lines must be an integer between 0 and 10000',\n 'bad-flag'\n );\n return true;\n }\n lines = Number(linesRaw);\n if (lines < 0 || lines > 10000) {\n usageError(\n '--lines must be an integer between 0 and 10000',\n 'bad-flag'\n );\n return true;\n }\n }\n const docker = deps.docker ?? new Docker();\n await handleLogs(docker, nodeId, { ...baseOpts, lines });\n return true;\n }\n case 'peer': {\n const peerId = positionals[1];\n if (!peerId) {\n usageError('Usage: townhouse peer <id> [--json]', 'usage');\n return true;\n }\n await handlePeerDetail(\n new ConnectorAdminClient(adminUrl),\n peerId,\n baseOpts\n );\n return true;\n }\n case 'health': {\n await handleHealth(new ConnectorAdminClient(adminUrl, PROBE_TIMEOUT_MS), {\n ...baseOpts,\n apiUrl,\n });\n return true;\n }\n default:\n return false;\n }\n}\n","import type { AggregatedEarnings } from '../earnings/aggregator.js';\nimport { formatUsdc } from '../tui/format.js';\n\nexport const USDC_SCALE = 6;\nconst USDC_ASSET = 'USDC';\nconst DECIMAL_RE = /^-?\\d+$/;\nconst POSITIVE_INT_RE = /^[1-9]\\d*$/;\n\nexport interface EarningsRow {\n today: string;\n month: string;\n year: string;\n lifetime: string;\n}\n\nfunction addDecimalStrings(a: string, b: string): string {\n if (!DECIMAL_RE.test(b)) return a;\n try {\n return (BigInt(a) + BigInt(b)).toString();\n } catch {\n return a;\n }\n}\n\nexport function computeUsdcScalars(earnings: AggregatedEarnings): EarningsRow {\n let today = '0';\n let month = '0';\n let year = '0';\n let lifetime = '0';\n\n const apexUsdc = earnings.apex.routingFees[USDC_ASSET];\n if (apexUsdc !== undefined) {\n today = addDecimalStrings(today, apexUsdc.today);\n month = addDecimalStrings(month, apexUsdc.month);\n year = addDecimalStrings(year, apexUsdc.year);\n lifetime = addDecimalStrings(lifetime, apexUsdc.lifetime);\n }\n\n for (const peer of earnings.peers) {\n const peerUsdc = peer.byAsset[USDC_ASSET];\n if (peerUsdc !== undefined) {\n today = addDecimalStrings(today, peerUsdc.today);\n month = addDecimalStrings(month, peerUsdc.month);\n year = addDecimalStrings(year, peerUsdc.year);\n lifetime = addDecimalStrings(lifetime, peerUsdc.lifetime);\n }\n }\n\n return { today, month, year, lifetime };\n}\n\nexport function usdcMicroToSats(\n decimalString: string,\n satsPerUsdc: number\n): string {\n if (!DECIMAL_RE.test(decimalString)) return '0';\n if (!Number.isInteger(satsPerUsdc) || satsPerUsdc <= 0) {\n throw new Error('satsPerUsdc must be a positive integer');\n }\n const negative = decimalString.startsWith('-');\n const absolute = negative ? decimalString.slice(1) : decimalString;\n const sats =\n (BigInt(absolute) * BigInt(satsPerUsdc)) / 10n ** BigInt(USDC_SCALE);\n return (negative && sats !== 0n ? '-' : '') + sats.toString();\n}\n\nexport function formatSatsRow(value: string): string {\n if (!value || !DECIMAL_RE.test(value)) return '0 sats';\n const negative = value.startsWith('-');\n const abs = negative ? value.slice(1) : value;\n if (!abs || abs === '0') return '0 sats';\n\n let formatted: string;\n const absN = BigInt(abs);\n if (absN < BigInt(Number.MAX_SAFE_INTEGER)) {\n formatted = Number(abs).toLocaleString('en-US');\n } else {\n formatted = abs.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n }\n return (negative ? '-' : '') + formatted + ' sats';\n}\n\nexport function renderEarningsSection(opts: {\n earnings: AggregatedEarnings;\n units: 'usdc' | 'sats';\n satsPerUsdc?: number;\n}): string[] {\n if (opts.earnings.status === 'connector_unavailable') {\n return ['', 'Earnings (USDC): unavailable'];\n }\n\n const scalars = computeUsdcScalars(opts.earnings);\n\n if (opts.units === 'usdc') {\n return [\n '',\n 'Earnings (USDC):',\n '----------------',\n ` TODAY ${formatUsdc(scalars.today, USDC_SCALE)}`,\n ` MONTH ${formatUsdc(scalars.month, USDC_SCALE)}`,\n ` YEAR ${formatUsdc(scalars.year, USDC_SCALE)}`,\n ` LIFETIME ${formatUsdc(scalars.lifetime, USDC_SCALE)}`,\n ];\n }\n\n if (\n opts.satsPerUsdc === undefined ||\n !Number.isInteger(opts.satsPerUsdc) ||\n opts.satsPerUsdc <= 0\n ) {\n throw new Error(\n \"renderEarningsSection: units='sats' requires a positive-integer satsPerUsdc\"\n );\n }\n const rate = opts.satsPerUsdc;\n const header = `Earnings (sats @ ${rate}/USDC):`;\n return [\n '',\n header,\n '-'.repeat(header.length),\n ` TODAY ${formatSatsRow(usdcMicroToSats(scalars.today, rate))}`,\n ` MONTH ${formatSatsRow(usdcMicroToSats(scalars.month, rate))}`,\n ` YEAR ${formatSatsRow(usdcMicroToSats(scalars.year, rate))}`,\n ` LIFETIME ${formatSatsRow(usdcMicroToSats(scalars.lifetime, rate))}`,\n ];\n}\n\nexport function resolveSatsRate(\n values: Record<string, unknown>,\n env: NodeJS.ProcessEnv\n): { rate: number } | { error: string } {\n // Treat empty --rate as absent so a valid env var can still take effect.\n const cliRaw =\n typeof values['rate'] === 'string' ? (values['rate'] as string) : undefined;\n const cliRate = cliRaw !== undefined && cliRaw !== '' ? cliRaw : undefined;\n const envRate = env['TOWNHOUSE_SATS_PER_USDC'];\n const raw = cliRate ?? envRate;\n const source =\n cliRate !== undefined ? '--rate' : 'TOWNHOUSE_SATS_PER_USDC env var';\n\n if (raw === undefined) {\n return {\n error:\n '--units=sats requires --rate <sats-per-usdc> or TOWNHOUSE_SATS_PER_USDC env var (e.g. --rate 1500 for 1500 sats per 1 USDC)',\n };\n }\n\n if (!POSITIVE_INT_RE.test(raw)) {\n return {\n error: `${source} must be a positive integer (sats per 1 USDC); got: ${JSON.stringify(raw)}`,\n };\n }\n\n const rate = Number(raw);\n if (!Number.isSafeInteger(rate) || rate <= 0) {\n return { error: `${source} is out of range` };\n }\n\n return { rate };\n}\n","/**\n * Buy Turbo upload credits using a townhouse-derived EVM or SOL key\n * (epic-49, Phase 2).\n *\n * Pure business logic — no stdout writes, no readline prompts. The CLI\n * handler is responsible for argv parsing, confirmation UI, status streaming,\n * and exit codes. This module owns the Turbo SDK interactions only.\n *\n * Credit↔signer linkage (verified at implementation time, 2026-05-21):\n * `TurboFundWithTokensParams` accepts an optional `turboCreditDestinationAddress`\n * (see `@ardrive/turbo-sdk/lib/types/types.d.ts:637-642`). When supplied, the\n * on-chain payment is sent FROM the SOL/EVM signer's address TO the Turbo\n * service, but the resulting winc balance is credited to the specified\n * destination address. That destination address can be an Arweave address,\n * which an `ArweaveSigner` can subsequently use to spend the credits during\n * upload (`topUpWithTokens` → AR address; `ArweaveSigner` → spend).\n *\n * For this Phase 2 work the default is to omit `turboCreditDestinationAddress`\n * so credits land on the funding identity itself (matches what the CLI prints\n * for source/destination address). Phase 4's DVM container handoff will pass\n * the DVM's Arweave address explicitly so credits land where the\n * ArweaveSigner can spend them.\n */\n\nimport { TurboFactory } from '@ardrive/turbo-sdk/node';\n\nimport type { NodeType } from '../docker/types.js';\nimport type { WalletManager } from '../wallet/manager.js';\nimport { buildTurboSigner, type TurboTokenId } from '../wallet/turbo-signer.js';\nimport { parseTokenAmount } from './units.js';\n\nexport interface BuyCreditsOptions {\n /** Unlocked WalletManager. Caller owns lifecycle (locking). */\n wallet: WalletManager;\n /** Which node's keys fund the purchase. DVM in the standard flow. */\n nodeType: NodeType;\n /** Friendly token id (e.g. 'sol', 'eth', 'usdc-eth'). */\n token: TurboTokenId;\n /**\n * Human decimal amount in token units (e.g. \"0.05\" for 0.05 SOL,\n * \"10\" for 10 USDC). Converted to base units (lamports/wei/microUSDC)\n * via `parseTokenAmount` before being handed to the Turbo SDK.\n */\n amount: string;\n /**\n * Optional fee multiplier passed through to `topUpWithTokens` —\n * Turbo's on-chain gas/priority knob. >1 raises the on-chain fee.\n */\n feeMultiplier?: number;\n /**\n * If true, only fetch the quote (no on-chain transaction). Returns a\n * `BuyQuoteResult` with the winc that WOULD be credited for `amount`.\n */\n quoteOnly?: boolean;\n /**\n * Optional explicit credit recipient. When omitted, credits land on the\n * funding identity itself. Phase 4 uses this to credit the DVM's Arweave\n * address from a SOL/EVM funding signer.\n */\n destinationAddress?: string;\n}\n\n/** Quote-only result — no on-chain tx submitted. */\nexport interface BuyQuoteResult {\n kind: 'quote';\n /** Funding-side address (EVM hex / SOL base58 / AR base64url). */\n fromAddress: string;\n /** Credit-recipient address (defaults to fromAddress). */\n creditAddress: string;\n /** Base-unit amount that WOULD be charged on-chain. */\n baseAmount: bigint;\n /** Winc that WOULD be credited for `baseAmount`. */\n winc: bigint;\n /** Raw Turbo response — preserves equivalentWincTokenAmount, fees, etc. */\n raw: {\n winc: string;\n actualTokenAmount: string;\n equivalentWincTokenAmount: string;\n };\n}\n\n/** Submit-path result — Turbo's `topUpWithTokens` return shape, BigInt-typed. */\nexport interface BuySubmitResult {\n kind: 'submit';\n fromAddress: string;\n creditAddress: string;\n baseAmount: bigint;\n /** Winc actually credited (per Turbo's post-submit response). */\n winc: bigint;\n /** On-chain tx id (e.g. SOL signature, EVM tx hash). */\n id: string;\n /** Turbo's tracked status — typically 'pending' immediately after submit. */\n status: 'pending' | 'confirmed' | 'failed';\n /** Token (canonical string Turbo uses, e.g. 'solana', 'ethereum'). */\n token: string;\n /** Optional block height (present when status='confirmed'). */\n block?: number;\n}\n\nexport type BuyResult = BuyQuoteResult | BuySubmitResult;\n\n/**\n * Build a Turbo authenticated client, fetch a quote for `amount` of `token`,\n * and either return the quote (if `quoteOnly`) or submit `topUpWithTokens`.\n *\n * Pure business logic: no console output, no prompts. The CLI handler should\n * stream status messages between awaits.\n */\nexport async function buyCredits(opts: BuyCreditsOptions): Promise<BuyResult> {\n const {\n wallet,\n nodeType,\n token,\n amount,\n feeMultiplier,\n quoteOnly,\n destinationAddress,\n } = opts;\n\n // Parse human → base units first so we fail fast on bad input before\n // building a signer.\n const baseAmount = parseTokenAmount(token, amount);\n\n // EVM/SOL signer = the funding identity. For `ar` the signer is also\n // the credit recipient (you can buy AR credits with AR — though uncommon).\n const {\n signer,\n token: canonicalToken,\n address: fromAddress,\n } = await buildTurboSigner(wallet, nodeType, token);\n\n // Default credit destination = funding identity. Phase 4 will override\n // with the DVM's Arweave address.\n const creditAddress = destinationAddress ?? fromAddress;\n\n const turbo = TurboFactory.authenticated({\n signer,\n token: canonicalToken,\n });\n\n // Quote step — always run, even on the submit path, so callers always\n // have a winc estimate available. The Turbo SDK quotes in token base units\n // (string-form BigNumber to preserve precision).\n const quote = await turbo.getWincForToken({\n tokenAmount: baseAmount.toString(),\n });\n const quotedWinc = BigInt(quote.winc);\n\n if (quoteOnly) {\n return {\n kind: 'quote',\n fromAddress,\n creditAddress,\n baseAmount,\n winc: quotedWinc,\n raw: {\n winc: quote.winc,\n actualTokenAmount: quote.actualTokenAmount,\n equivalentWincTokenAmount: quote.equivalentWincTokenAmount,\n },\n };\n }\n\n // Submit step. `turboCreditDestinationAddress` is omitted when caller did\n // not supply `destinationAddress` (credits land on the funding identity).\n const topUpParams: {\n tokenAmount: string;\n feeMultiplier?: number;\n turboCreditDestinationAddress?: string;\n } = {\n tokenAmount: baseAmount.toString(),\n };\n if (feeMultiplier !== undefined) topUpParams.feeMultiplier = feeMultiplier;\n if (destinationAddress !== undefined) {\n topUpParams.turboCreditDestinationAddress = destinationAddress;\n }\n\n const submitted = await turbo.topUpWithTokens(topUpParams);\n\n return {\n kind: 'submit',\n fromAddress,\n creditAddress,\n baseAmount,\n winc: BigInt(submitted.winc),\n id: submitted.id,\n status: submitted.status,\n token: submitted.token,\n ...(submitted.block !== undefined ? { block: submitted.block } : {}),\n };\n}\n","/**\n * Turbo SDK signer factory — bridges WalletManager-derived keys to the\n * `@ardrive/turbo-sdk` signer abstraction (epic-49, Phase 2).\n *\n * The Turbo SDK accepts an `ArweaveSigner`, `EthereumSigner`, or\n * `HexSolanaSigner` (all re-exported from `@dha-team/arbundles`) to\n * authenticate the funding side of `topUpWithTokens` and `getBalance`.\n *\n * For the credits-buy flow the SIGNER is the funding identity (EVM or SOL),\n * not the credit recipient. The credit recipient is specified separately via\n * `turboCreditDestinationAddress` in the top-up call — see\n * `packages/townhouse/src/credits/buy.ts`.\n *\n * Why the @ardrive/turbo-sdk re-export and not @dha-team/arbundles directly?\n * The Turbo SDK's `signer.d.ts` explicitly re-exports `ArweaveSigner`,\n * `EthereumSigner`, `HexSolanaSigner` (see lib/types/node/signer.d.ts) as a\n * \"utility export to avoid clients having to install arbundles.\" Importing\n * from `@ardrive/turbo-sdk/node` keeps the version pin in one place and\n * guarantees identical class identity to whatever Turbo's internal factory\n * constructs.\n */\n\nimport {\n ArweaveSigner,\n EthereumSigner,\n HexSolanaSigner,\n} from '@ardrive/turbo-sdk/node';\nimport bs58 from 'bs58';\n\nimport type { NodeType } from '../docker/types.js';\nimport type { WalletManager } from './manager.js';\n\n/**\n * Assemble the 64-byte Solana secret key (32-byte private seed + 32-byte\n * public key) and return it base58-encoded — the format `HexSolanaSigner`\n * actually consumes. (The class is misleadingly named: its `sign()` method\n * hex-encodes the message, but its CONSTRUCTOR expects a base58-encoded\n * secret key per `@dha-team/arbundles/.../SolanaSigner.js` which calls\n * `bs58.decode(_key)` on the input.)\n */\nfunction solanaSecretKeyBase58(\n privateKeyHex: string,\n publicKeyBase58: string\n): string {\n const priv = Buffer.from(privateKeyHex, 'hex');\n if (priv.length !== 32) {\n throw new Error(\n `Solana private key seed must be 32 bytes, got ${priv.length}`\n );\n }\n const pub = bs58.decode(publicKeyBase58);\n if (pub.length !== 32) {\n throw new Error(`Solana public key must be 32 bytes, got ${pub.length}`);\n }\n const secret = new Uint8Array(64);\n secret.set(priv, 0);\n secret.set(pub, 32);\n return bs58.encode(secret);\n}\n\n/**\n * Friendly token identifiers exposed to CLI users. Mapped to Turbo's canonical\n * `TokenType` string in `TURBO_TOKEN_MAP`. We keep the friendly names because\n * `pol`/`usdc-pol`/`usdc-eth` are more memorable than `matic`/`polygon-usdc`.\n */\nexport type TurboTokenId =\n | 'eth'\n | 'pol'\n | 'base-eth'\n | 'base-usdc'\n | 'usdc-eth'\n | 'usdc-pol'\n | 'sol'\n | 'ar';\n\n/**\n * Canonical Turbo `TokenType` string per friendly id. Source of truth for the\n * `token` parameter passed to `TurboFactory.authenticated` and downstream\n * `getWincForToken` / `topUpWithTokens` calls.\n *\n * Mapping derived from `@ardrive/turbo-sdk` `tokenTypes` (see\n * `lib/types/types.d.ts:36`):\n * \"arweave\" | \"ario\" | \"base-ario\" | \"solana\" | \"ethereum\" | \"kyve\"\n * | \"matic\" | \"pol\" | \"base-eth\" | \"usdc\" | \"base-usdc\" | \"polygon-usdc\"\n */\nconst TURBO_TOKEN_MAP = {\n eth: 'ethereum',\n pol: 'pol',\n 'base-eth': 'base-eth',\n 'base-usdc': 'base-usdc',\n 'usdc-eth': 'usdc',\n 'usdc-pol': 'polygon-usdc',\n sol: 'solana',\n ar: 'arweave',\n} as const;\n\nexport type TurboCanonicalToken = (typeof TURBO_TOKEN_MAP)[TurboTokenId];\n\n/** EVM family — uses secp256k1 + an `EthereumSigner` regardless of chain. */\nconst EVM_TOKENS: ReadonlySet<TurboTokenId> = new Set([\n 'eth',\n 'pol',\n 'base-eth',\n 'base-usdc',\n 'usdc-eth',\n 'usdc-pol',\n]);\n\n/** Return type for `buildTurboSigner` — opaque to callers besides Turbo. */\nexport interface TurboSignerBundle {\n /** ArweaveSigner | EthereumSigner | HexSolanaSigner instance. */\n signer: ArweaveSigner | EthereumSigner | HexSolanaSigner;\n /** Canonical Turbo token string (passed verbatim to TurboFactory). */\n token: TurboCanonicalToken;\n /**\n * Native address corresponding to the signer (checksummed EVM hex,\n * base58 SOL, or base64url AR). Useful for confirmation prompts and\n * balance queries — for `credits buy` this is the FUNDING address.\n */\n address: string;\n}\n\n/**\n * Map a friendly `TurboTokenId` to the canonical Turbo string. Exported for\n * tests + the CLI argv parser. Throws on unknown ids.\n */\nexport function canonicalTurboToken(token: TurboTokenId): TurboCanonicalToken {\n const canonical = TURBO_TOKEN_MAP[token];\n if (!canonical) {\n throw new Error(\n `Unknown TurboTokenId '${String(token)}'. Supported: ${Object.keys(TURBO_TOKEN_MAP).join(', ')}`\n );\n }\n return canonical;\n}\n\n/**\n * Construct a Turbo SDK signer bundle from a node's WalletManager-derived key.\n *\n * EVM family → `EthereumSigner(privateKeyHex)`.\n * `sol` → `HexSolanaSigner(privateKeyHex)`.\n * `ar` → `await wallet.ensureArweaveKey(...)` then `ArweaveSigner(jwk)`.\n *\n * The caller is responsible for the lifecycle of `wallet` — this function\n * does NOT lock the wallet on completion.\n */\nexport async function buildTurboSigner(\n wallet: WalletManager,\n nodeType: NodeType,\n token: TurboTokenId\n): Promise<TurboSignerBundle> {\n const canonical = canonicalTurboToken(token);\n\n if (EVM_TOKENS.has(token)) {\n const privateKeyHex = wallet.getEvmPrivateKeyHex(nodeType);\n const signer = new EthereumSigner(privateKeyHex);\n const keys = wallet.getNodeKeys(nodeType);\n return { signer, token: canonical, address: keys.evmAddress };\n }\n\n if (token === 'sol') {\n const privateKeyHex = wallet.getSolanaPrivateKeyHex(nodeType);\n const keys = wallet.getNodeKeys(nodeType);\n if (!keys.solanaAddress) {\n // Defensive — getSolanaPrivateKeyHex would have thrown first, but the\n // type system can't see that.\n throw new Error(`Solana address not available for node '${nodeType}'`);\n }\n const secretBase58 = solanaSecretKeyBase58(\n privateKeyHex,\n keys.solanaAddress\n );\n const signer = new HexSolanaSigner(secretBase58);\n return { signer, token: canonical, address: keys.solanaAddress };\n }\n\n if (token === 'ar') {\n // RSA-4096 derivation is 5-30s on first call — Phase 1 contract.\n await wallet.ensureArweaveKey(nodeType);\n const jwk = wallet.getArweaveJwk(nodeType);\n const signer = new ArweaveSigner(jwk);\n const keys = wallet.getNodeKeys(nodeType);\n if (!keys.arweaveAddress) {\n throw new Error(\n `Arweave address not populated for node '${nodeType}' after ensureArweaveKey`\n );\n }\n return { signer, token: canonical, address: keys.arweaveAddress };\n }\n\n // Exhaustiveness — TS sees `token` as `never` here when all variants covered.\n throw new Error(`Unsupported TurboTokenId: ${String(token)}`);\n}\n","/**\n * Display helpers for Turbo credits + on-chain amounts (epic-49, Phase 2).\n *\n * Two concerns:\n * 1. Translate raw winc (winston credit, 1e-12 AR) into a \"~N MB upload\n * capacity\" string for operator-facing surfaces. Per the plan: every winc\n * value shown to a human should be accompanied by its bytes translation.\n * 2. Format on-chain base amounts (lamports, wei, USDC microunits) back into\n * their human decimal representation for confirmation prompts.\n */\n\nimport type { TurboTokenId } from '../wallet/turbo-signer.js';\n\n/**\n * Approximate winc-per-byte pricing constant. The Turbo SDK does NOT expose a\n * static converter — pricing is dynamic via `getTokenPriceForBytes` /\n * `getUploadCosts` (network calls). For at-a-glance display this constant is\n * good enough; precise quotes pass through the SDK.\n *\n * Reference: 1 GiB ≈ ~6.5e14 winc as of 2026-05 (varies with AR/USD rate and\n * network demand). 1 MiB ≈ 6.4e11 winc → 1 byte ≈ 6.1e5 winc.\n *\n * Update with the same cadence as ardrive's published pricing dashboard. This\n * is intentionally a single constant — if it drifts more than ±20% we should\n * be calling `getTokenPriceForBytes` for the display path too.\n */\nconst WINC_PER_BYTE_APPROX = 610_000n;\n\n/**\n * Convert a winc balance into an approximate byte count.\n *\n * Pure BigInt division — no float math. Underestimates very small balances\n * (a wallet with <WINC_PER_BYTE_APPROX winc shows as \"0 B\"), which is the\n * correct semantic: you can't actually upload anything.\n */\nfunction wincToBytes(winc: bigint): bigint {\n if (winc < 0n) return 0n;\n return winc / WINC_PER_BYTE_APPROX;\n}\n\n/**\n * Format a winc value as a human-friendly upload-capacity string.\n *\n * Examples:\n * formatWincAsBytes(0n) → \"~0 B\"\n * formatWincAsBytes(6_100_000n) → \"~10 B\"\n * formatWincAsBytes(61_000_000_000n) → \"~100 KB\"\n * formatWincAsBytes(610_000_000_000n)→ \"~1 MB\"\n *\n * Uses base-1000 (decimal SI) units to match how the ardrive UI presents\n * capacity. Rounds DOWN — never overstates available capacity.\n */\nexport function formatWincAsBytes(winc: bigint): string {\n const bytes = wincToBytes(winc);\n\n if (bytes < 1_000n) return `~${bytes.toString()} B`;\n if (bytes < 1_000_000n) {\n return `~${(bytes / 1_000n).toString()} KB`;\n }\n if (bytes < 1_000_000_000n) {\n return `~${(bytes / 1_000_000n).toString()} MB`;\n }\n if (bytes < 1_000_000_000_000n) {\n return `~${(bytes / 1_000_000_000n).toString()} GB`;\n }\n return `~${(bytes / 1_000_000_000_000n).toString()} TB`;\n}\n\n/**\n * Number of decimal places per token. Mirrors `@ardrive/turbo-sdk`\n * `exponentMap` for the tokens we support (see lib/types/common/token/index.d.ts\n * — exponentMap entries: ar=12, sol=9, eth/pol/base-eth=18, usdc=6).\n */\nconst TOKEN_DECIMALS: Record<TurboTokenId, number> = {\n ar: 12,\n sol: 9,\n eth: 18,\n pol: 18,\n 'base-eth': 18,\n 'base-usdc': 6,\n 'usdc-eth': 6,\n 'usdc-pol': 6,\n};\n\n/** Human-readable token symbol for display (uppercase per market convention). */\nconst TOKEN_SYMBOL: Record<TurboTokenId, string> = {\n ar: 'AR',\n sol: 'SOL',\n eth: 'ETH',\n pol: 'POL',\n 'base-eth': 'ETH (Base)',\n 'base-usdc': 'USDC (Base)',\n 'usdc-eth': 'USDC (Ethereum)',\n 'usdc-pol': 'USDC (Polygon)',\n};\n\n/**\n * Format a base-unit token amount (lamports, wei, USDC microunits, winston)\n * into a human decimal string with the token symbol.\n *\n * Examples:\n * formatTokenAmount('sol', 1_000_000n) → \"0.001000000 SOL\"\n * formatTokenAmount('usdc-eth', 10_000_000n) → \"10.000000 USDC (Ethereum)\"\n * formatTokenAmount('eth', 1_000_000_000_000_000n) → \"0.001000000000000000 ETH\"\n *\n * Uses BigInt arithmetic exclusively — never float. Trailing zeros are\n * preserved so the decimal count visibly matches the token precision (an\n * operator confirming a SOL payment expects to see 9 decimals).\n */\nexport function formatTokenAmount(\n token: TurboTokenId,\n baseAmount: bigint\n): string {\n const decimals = TOKEN_DECIMALS[token];\n const symbol = TOKEN_SYMBOL[token];\n if (decimals === undefined || symbol === undefined) {\n throw new Error(`Unknown TurboTokenId for formatting: ${String(token)}`);\n }\n\n const scale = 10n ** BigInt(decimals);\n const isNegative = baseAmount < 0n;\n const abs = isNegative ? -baseAmount : baseAmount;\n const whole = abs / scale;\n const frac = abs % scale;\n const fracStr = frac.toString().padStart(decimals, '0');\n const sign = isNegative ? '-' : '';\n return `${sign}${whole.toString()}.${fracStr} ${symbol}`;\n}\n\n/**\n * Parse a human decimal amount string (e.g. \"0.05\", \"10\", \"1.234\") to its\n * base-unit BigInt representation for the given token.\n *\n * Strict — rejects scientific notation, leading +, trailing characters,\n * and more decimal places than the token supports. Throws Error with a\n * caller-friendly message on failure.\n *\n * Examples:\n * parseTokenAmount('sol', '0.001') → 1_000_000n\n * parseTokenAmount('usdc-eth', '10') → 10_000_000n\n * parseTokenAmount('eth', '0.0001') → 100_000_000_000_000n\n */\nexport function parseTokenAmount(token: TurboTokenId, decimal: string): bigint {\n const decimals = TOKEN_DECIMALS[token];\n if (decimals === undefined) {\n throw new Error(`Unknown TurboTokenId: ${String(token)}`);\n }\n\n const trimmed = decimal.trim();\n if (!/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n throw new Error(\n `Invalid decimal amount '${decimal}' for token '${token}'. Use plain decimal notation (e.g. \"0.05\").`\n );\n }\n\n const [wholeStr, fracStr = ''] = trimmed.split('.');\n if (fracStr.length > decimals) {\n throw new Error(\n `Amount '${decimal}' has ${fracStr.length} decimal places, but '${token}' supports at most ${decimals}.`\n );\n }\n const fracPadded = fracStr.padEnd(decimals, '0');\n const whole = BigInt(wholeStr);\n const frac = fracPadded.length > 0 ? BigInt(fracPadded) : 0n;\n return whole * 10n ** BigInt(decimals) + frac;\n}\n","/**\n * Query a Turbo credit balance for the address that would fund (and, by\n * default, hold) credits in the `townhouse credits buy` flow\n * (epic-49, Phase 2).\n *\n * Uses an authenticated Turbo client — the SDK's `getBalance()` (no args)\n * returns the balance for the signer's native address. Passing an explicit\n * address is also supported, but for the standard CLI flow the signer's\n * address IS the address we want to query.\n *\n * Pure business logic. The CLI handler formats the winc into a human\n * readable `~N MB` string via `formatWincAsBytes`.\n */\n\nimport { TurboFactory } from '@ardrive/turbo-sdk/node';\n\nimport type { NodeType } from '../docker/types.js';\nimport type { WalletManager } from '../wallet/manager.js';\nimport { buildTurboSigner, type TurboTokenId } from '../wallet/turbo-signer.js';\n\nexport interface GetCreditBalanceOptions {\n wallet: WalletManager;\n nodeType: NodeType;\n token: TurboTokenId;\n /** Optional explicit address to query — defaults to signer's address. */\n address?: string;\n}\n\nexport interface CreditBalanceResult {\n /** Spendable winc balance (per Turbo's `winc` field). */\n winc: bigint;\n /**\n * Winc + currently-revocable approvals (per Turbo's `controlledWinc`).\n * Higher than `winc` when the operator has shared credits with another\n * address that haven't been spent.\n */\n controlledWinc: bigint;\n /**\n * Winc the user can currently spend including received approvals from\n * other addresses (per Turbo's `effectiveBalance`).\n */\n effectiveBalance: bigint;\n /** The address whose balance was queried (funding-identity native form). */\n address: string;\n}\n\n/**\n * Fetch the Turbo credit balance held by the funding identity for `nodeType`.\n *\n * Throws on Turbo SDK network errors — the CLI handler should catch and\n * surface a clean operator-facing message.\n */\nexport async function getCreditBalance(\n opts: GetCreditBalanceOptions\n): Promise<CreditBalanceResult> {\n const { wallet, nodeType, token, address: explicitAddress } = opts;\n\n const {\n signer,\n token: canonicalToken,\n address: signerAddress,\n } = await buildTurboSigner(wallet, nodeType, token);\n\n const turbo = TurboFactory.authenticated({\n signer,\n token: canonicalToken,\n });\n\n // Pass the explicit address when supplied so callers can query a different\n // recipient (Phase 4: query the DVM's Arweave address from a SOL signer).\n // Omit otherwise to use the signer's native address.\n const balance = explicitAddress\n ? await turbo.getBalance(explicitAddress)\n : await turbo.getBalance();\n\n return {\n winc: BigInt(balance.winc),\n controlledWinc: BigInt(balance.controlledWinc),\n effectiveBalance: BigInt(balance.effectiveBalance),\n address: explicitAddress ?? signerAddress,\n };\n}\n","function isOptOut(value: string | undefined): boolean {\n if (value === undefined) return false;\n if (value === '' || value === '0' || value.toLowerCase() === 'false')\n return false;\n return true;\n}\n\nexport function shouldRenderInk(): boolean {\n if (process.stdout.isTTY !== true) return false;\n if (process.env['CI'] === 'true') return false;\n if (isOptOut(process.env['NO_TUI'])) return false;\n if ((process.env['TERM'] ?? '') === 'dumb') return false;\n return true;\n}\n\nexport function isTmux(): boolean {\n if (process.env['TMUX'] !== undefined && process.env['TMUX'] !== '')\n return true;\n const term = process.env['TERM'] ?? '';\n return /^screen|^tmux/.test(term);\n}\n"],"mappings":"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","names":["spawn","Docker","resolve","supportsUnicode","xMark","arrow","resolve","resolve","formatRelativeTime","resolve","body","emitJsonError","TurboFactory","TurboFactory","Docker","createInterface","resolve","spawn","require"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/cli/browser-opener.ts","../src/cli/onboarding-ribbon.ts","../src/cli/failure-copy.ts","../src/cli/password-prompt.ts","../src/cli/preflight-ports.ts","../src/cli/pull-narrator.ts","../src/cli/node-commands.ts","../src/cli/drill-commands.ts","../src/cli/status-earnings.ts","../src/credits/buy.ts","../src/wallet/turbo-signer.ts","../src/credits/units.ts","../src/credits/balance.ts","../src/tui/tty-detect.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * CLI entrypoint for `@toon-protocol/townhouse` (Story 21.1).\n *\n * Subcommands: init, up, down, status, --help\n *\n * Usage:\n * townhouse init [--force]\n * townhouse up\n * townhouse down\n * townhouse status\n */\n\nimport { parseArgs } from 'node:util';\nimport {\n mkdirSync,\n writeFileSync,\n existsSync,\n renameSync,\n rmSync,\n statSync,\n realpathSync,\n} from 'node:fs';\nimport { join, resolve, dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport { pathToFileURL } from 'node:url';\nimport { spawn } from 'node:child_process';\nimport { stringify } from 'yaml';\nimport Docker from 'dockerode';\nimport { nip19 } from 'nostr-tools';\n\nimport { getDefaultConfig } from './config/defaults.js';\nimport type { NetworkMode } from './config/schema.js';\nimport { loadConfig, saveConfig } from './config/loader.js';\nimport type { TownhouseConfig, ChainProviderEntry } from './config/schema.js';\nimport { DockerOrchestrator, OrchestratorError } from './docker/index.js';\nimport type { NodeType } from './docker/types.js';\nimport {\n ConnectorAdminClient,\n TransportProbe,\n DEFAULT_ATOR_PROXY,\n writeHsConnectorConfig,\n writeHsNodeEnvFile,\n} from './connector/index.js';\nimport { materializeComposeTemplate } from './compose-loader.js';\nimport type { ComposeLoaderOptions } from './compose-loader.js';\nimport { BootReconciler } from './reconciler.js';\nimport { createApiServer } from './api/server.js';\nimport { createWizardApiServer } from './api/wizard-server.js';\nimport type { ApiServer } from './api/index.js';\nimport { RealBrowserOpener } from './cli/browser-opener.js';\nimport type { BrowserOpener } from './cli/browser-opener.js';\nimport { OnboardingRibbon } from './cli/onboarding-ribbon.js';\nimport { renderFailure } from './cli/failure-copy.js';\nimport { promptPassword } from './cli/password-prompt.js';\nimport {\n checkHsPortCollisions,\n formatCollisionMessage,\n type PortCollision,\n} from './cli/preflight-ports.js';\nimport { PullNarrator } from './cli/pull-narrator.js';\nimport {\n readImageManifest,\n isSyntheticDigest,\n} from './state/image-manifest.js';\nimport {\n handleNodeAdd,\n handleNodeRemove,\n handleNodeList,\n NODE_HELP,\n NODE_ADD_HELP,\n NODE_REMOVE_HELP,\n NODE_LIST_HELP,\n} from './cli/node-commands.js';\nimport { dispatchDrillCommand } from './cli/drill-commands.js';\nimport {\n renderEarningsSection,\n resolveSatsRate,\n} from './cli/status-earnings.js';\nimport {\n aggregateEarnings,\n type AggregatedEarnings,\n} from './earnings/aggregator.js';\nimport { readNodesYaml } from './state/nodes-yaml.js';\nimport { PeerTypeResolver } from './registry/peer-type-resolver.js';\nimport { createDeltaComputer } from './earnings/snapshot-reader.js';\nimport {\n WalletManager,\n encryptWallet,\n decryptWallet,\n saveWallet,\n loadWallet,\n} from './wallet/index.js';\nimport type { NodeKeyInfo } from './wallet/index.js';\nimport type { TurboTokenId } from './wallet/turbo-signer.js';\nimport { buyCredits } from './credits/buy.js';\nimport { getCreditBalance } from './credits/balance.js';\nimport { formatTokenAmount, formatWincAsBytes } from './credits/units.js';\nimport { shouldRenderInk } from './tui/tty-detect.js';\n\n/**\n * Error thrown when `main()` is invoked with `--help`. Callers (tests) can\n * distinguish this from genuine failures; the top-level entrypoint catches\n * it and exits 0.\n */\nexport class CliHelpRequested extends Error {\n constructor() {\n super(HELP_TEXT);\n this.name = 'CliHelpRequested';\n }\n}\n\nconst HELP_TEXT = `townhouse — TOON node orchestrator\n\nUsage:\n townhouse setup [--no-browser] [--port <n>] [--config-dir <dir>] Run the first-run setup wizard\n townhouse init [--force] [--config-dir <dir>] [--password <pw>] [--preset <name>] [--network <mode>] [--yes] Initialize config + wallet\n townhouse up [--town] [--mill] [--dvm] [-c <path>] [--password <pw>] Start nodes\n townhouse down [-c <path>] Stop all nodes\n townhouse status [-c <path>] Show node status\n townhouse metrics [-c <path>] Show connector metrics\n townhouse wallet show [--json] [--hex] [--paths] [-c <path>] [--password <pw>] Show derived addresses\n townhouse wallet seed --confirm [-c <path>] [--password <pw>] Print the BIP-39 seed phrase (password-gated, requires --confirm)\n townhouse credits buy --token <id> --amount <decimal> [--fee-multiplier <n>] [--quote-only] [--yes] [-c <path>] [--password <pw>]\n Buy Arweave upload credits (token: eth|sol|pol|base-eth|base-usdc|usdc-eth|usdc-pol)\n townhouse credits balance --token <id> [-c <path>] [--password <pw>] Show Turbo credit balance for the funding address\n townhouse hs up [--password <pw>] [--skip-preflight] [-c <path>] Boot apex (connector + .anyone HS) (launches dashboard TUI in TTY mode)\n townhouse hs down [--rotate-keys] [-c <path>] Stop apex (--rotate-keys deletes .anyone keypair)\n townhouse node add [<type>] [--json] [-c <path>] Provision a child node (default: town)\n townhouse node remove <id> [--yes] [--json] [-c <path>] Deprovision a child node\n townhouse node list [--json] [-c <path>] List provisioned nodes\n townhouse chains list [--json] [-c <path>] List configured settlement chains (EVM/Solana/Mina)\n townhouse chains add --chain-type <evm|solana|mina> --chain-id <id> [fields] [-c <path>] Add/update a settlement chain\n townhouse chains remove <chainId> [-c <path>] Remove a settlement chain\n townhouse channels [--json] Show open payment channels\n townhouse logs <node-id> [-f|--follow] [--lines N] [--json] Tail logs for a node (Ctrl-C to stop)\n townhouse peer <id> [--json] Show per-peer detail card\n townhouse health [--json] Probe apex/api/nodes/.anyone health\n townhouse --help Show this help\n\nFlags:\n --town Start Town (Nostr relay) node\n --mill Start Mill (swap) node\n --dvm Start DVM (compute) node\n --password Wallet password (non-interactive mode)\n --rotate-keys Delete the .anyone keypair volume on hs down (produces a new address on next hs up)\n --skip-preflight Skip the port-collision preflight check on hs up (escape hatch)\n --no-browser Skip opening the browser automatically (setup command)\n --port Override the API port (setup command, default 9400)\n --preset Init from a named preset (init only). Supported: demo\n --network Chain network for apex + nodes (init only): mainnet (default), testnet, devnet, custom\n --evm-url / --sol-url RPC URLs for --network custom (the project's dev chains; or EVM_URL/SOL_URL env)\n --yes Non-interactive (init only); with --preset=demo uses demo password if --password absent\n --json Machine-readable JSON output (node commands; NDJSON for \\`logs\\`)\n --lines Number of historical log lines to fetch on attach (logs command, default 50)\n -f|--follow Accepted for \\`tail -f\\` muscle memory on \\`logs\\` (no-op — follow is default)\n If no flags given, starts all enabled nodes from config.`;\n\n/**\n * Dependency-injection overrides for the `hs up` / `hs down` CLI path.\n * Used by unit tests to stub out Docker, file I/O, and admin client.\n */\nexport interface CliHsOverrides {\n /** Override materializeComposeTemplate (avoids disk writes in tests). */\n materializeComposeTemplate?: (\n profile: string,\n opts?: ComposeLoaderOptions\n ) => { composePath: string; manifestPath: string };\n /** Override the DockerOrchestrator constructor (avoids real Docker in tests). */\n createOrchestrator?: (\n docker: Docker,\n config: TownhouseConfig,\n walletManager: WalletManager | undefined,\n options: { profile: 'hs'; composePath: string }\n ) => {\n up: (profiles: NodeType[]) => Promise<void>;\n down: () => Promise<void>;\n on: (event: string, handler: (...args: unknown[]) => void) => unknown;\n /**\n * Pre-pull a single image ref (Epic 49 Followup D).\n * Optional on the stub interface — when omitted on a real orchestrator,\n * the cold-pull narration phase is skipped (silent degrade).\n */\n pullImage?: (image: string) => Promise<void>;\n };\n /** Override ConnectorAdminClient construction (avoids real HTTP in tests). */\n createAdminClient?: (\n baseUrl: string,\n timeoutMs: number\n ) => {\n getHsHostname: () => Promise<{\n hostname: string | null;\n publishedAt: string | null;\n }>;\n };\n /** Override `docker compose down -v` spawn for --rotate-keys (avoids real Docker). */\n runComposeDown?: (composePath: string, withVolumes: boolean) => Promise<void>;\n /**\n * Override BootReconciler construction (Story 46.1). Tests inject a stub\n * with a spied-on `reconcile()` to assert wiring without touching disk\n * or the connector. When omitted, the default constructs a real\n * `BootReconciler` against `~/.townhouse/{nodes.yaml,reconciler.log}`.\n */\n createReconciler?: (\n nodesYamlPath: string,\n reconcilerLogPath: string\n ) => { reconcile: () => Promise<void> };\n /**\n * Override the port-collision preflight check (Epic 49 Followup B).\n * Default invokes `checkHsPortCollisions(docker)` from\n * `./cli/preflight-ports.js`. Tests inject a stub that returns either\n * `[]` (happy path) or a fabricated PortCollision[] (collision path) so\n * the production socket-bind + Docker enrichment is not exercised in\n * unit tests.\n */\n checkPortCollisions?: (docker: Docker) => Promise<PortCollision[]>;\n}\n\n/**\n * Dependency-injection overrides for the `node add` / `node remove` / `node list` CLI path.\n * Used by unit tests to stub `fetch` and the interactive confirmation prompt.\n */\nexport interface CliNodeCommandOverrides {\n fetch?: (url: string, init?: RequestInit) => Promise<Response>;\n confirm?: (question: string) => Promise<boolean>;\n apiUrl?: string;\n}\n\nconst DEFAULT_CONFIG_DIR = join(homedir(), '.townhouse');\nconst DEFAULT_CONFIG_PATH = join(DEFAULT_CONFIG_DIR, 'config.yaml');\n\n/**\n * Print the \"what now\" call-to-action after init. The journey from `init` to a\n * running node is the moment a first-timer is most likely to stall, so init must\n * hand them the exact next command. When a non-default config dir is used, the\n * next command needs an explicit `-c <path>`.\n */\nfunction printInitNextStep(dir: string): void {\n const isDefaultDir = dir === resolve(DEFAULT_CONFIG_DIR);\n const cmd = isDefaultDir\n ? 'npx @toon-protocol/townhouse hs up'\n : `npx @toon-protocol/townhouse hs up -c ${join(dir, 'config.yaml')}`;\n console.log('');\n console.log('Next — start your node:');\n console.log(` ${cmd}`);\n console.log('');\n console.log(\n 'First run pulls container images and bootstraps a hidden service.'\n );\n console.log('It can take a few minutes; progress is shown throughout.');\n}\n\nasync function handleInit(\n force: boolean,\n configDir?: string,\n password?: string,\n preset?: 'demo',\n yes?: boolean,\n network?: NetworkMode,\n endpoints?: { evmUrl?: string; solUrl?: string }\n): Promise<void> {\n const dir = resolve(configDir ?? DEFAULT_CONFIG_DIR);\n const configPath = join(dir, 'config.yaml');\n\n if (existsSync(configPath) && !force) {\n console.error(\n `Config already exists at ${configPath}. Use --force to overwrite.`\n );\n process.exitCode = 1;\n return;\n }\n\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // D2 — preset path takes precedence over default config for non-interactive\n // demo init. Preset writes the same TownhouseConfig shape, so the rest of\n // the init flow (wallet generation, etc.) is unaffected.\n let configToWrite;\n if (preset === 'demo') {\n const { buildDemoConfig, DEMO_DETERMINISTIC_PASSWORD } =\n await import('./presets/demo.js');\n configToWrite = buildDemoConfig({ walletPath: join(dir, 'wallet.enc') });\n // AC-D2-6: --yes without --password under --preset=demo gets the\n // deterministic demo password. Documented as DEMO ONLY.\n if (yes && !password) {\n password = DEMO_DETERMINISTIC_PASSWORD;\n console.log(\n '[demo preset] Using deterministic demo password (insecure — demo only).'\n );\n }\n } else {\n configToWrite = getDefaultConfig();\n // Override wallet path to use the config dir, not the default home-dir path.\n // getDefaultConfig() hardcodes ~/.townhouse/wallet.enc; tests and non-default\n // config dirs need the wallet collocated with config.yaml.\n configToWrite.wallet.encrypted_path = join(dir, 'wallet.enc');\n }\n // Persist the network mode (mainnet/testnet/devnet/custom). Drives chain/RPC\n // config for the apex connector and every node container\n // (resolveNetworkProfile). `custom` + `endpoints` carries operator-supplied\n // RPC URLs pointing at the project's dev chains (e.g. the Akash anvil/solana).\n if (network !== undefined) {\n configToWrite.network = network;\n }\n if (endpoints && (endpoints.evmUrl || endpoints.solUrl)) {\n configToWrite.endpoints = endpoints;\n }\n const yamlContent = stringify(configToWrite);\n writeFileSync(configPath, yamlContent, {\n encoding: 'utf-8',\n mode: 0o600,\n });\n\n console.log(`Config created at ${configPath}`);\n\n // Generate wallet — use config dir for wallet path (overrides default home dir path)\n const walletPath = join(dir, 'wallet.enc');\n if (existsSync(walletPath) && !force) {\n console.log('');\n console.log(\n `Wallet already exists at ${walletPath} — keeping your existing keys.`\n );\n console.log(\n 'Your seed phrase from the first run is still valid; nothing changed.'\n );\n console.log(\n '(Re-run with --force to regenerate, which REPLACES your keys.)'\n );\n printInitNextStep(dir);\n return;\n }\n\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (!walletPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const walletManager = new WalletManager({ encryptedPath: walletPath });\n const { mnemonic } = await walletManager.generate();\n\n // Display mnemonic ONCE for backup — this is the only place it ever appears\n console.log('');\n console.log('=== IMPORTANT: Back up your seed phrase ===');\n console.log('');\n console.log(` ${mnemonic}`);\n console.log('');\n console.log('This is the ONLY time your seed phrase will be shown.');\n console.log('Store it safely. You will need it to recover your node keys.');\n console.log('============================================');\n console.log('');\n\n // Encrypt and save — mnemonic reference is not stored beyond this block\n const encrypted = encryptWallet(mnemonic, walletPassword);\n await saveWallet(walletPath, encrypted);\n console.log(`Wallet saved to ${walletPath}`);\n\n // Display derived addresses\n console.log('');\n console.log('Derived Node Addresses:');\n console.log('-----------------------');\n const allKeys = walletManager.getAllKeys();\n for (const info of allKeys) {\n console.log(` ${info.nodeType.padEnd(6)} Nostr: ${info.nostrPubkey}`);\n console.log(` ${''.padEnd(6)} EVM: ${info.evmAddress}`);\n }\n\n // Zero key material\n walletManager.lock();\n\n printInitNextStep(dir);\n}\n\nasync function handleSetup(\n configDir: string | undefined,\n port: number,\n noBrowser: boolean,\n dockerInstance?: Docker,\n browserOpener?: BrowserOpener\n): Promise<void> {\n const dir = resolve(configDir ?? DEFAULT_CONFIG_DIR);\n const configPath = join(dir, 'config.yaml');\n const walletPath = join(dir, 'wallet.enc');\n\n // Short-circuit only when BOTH the config and the wallet exist — a config\n // without a wallet would land the operator in a circular dead-end (setup\n // says \"already initialized → run `townhouse up`\", up then errors that the\n // wallet is missing). Guide them to clean up and re-run setup instead.\n if (existsSync(configPath) && existsSync(walletPath)) {\n console.log('Already initialized — run `townhouse up` to start your nodes');\n return;\n }\n if (existsSync(configPath) && !existsSync(walletPath)) {\n console.error(\n `Found ${configPath} but no wallet at ${walletPath}.\\n` +\n `Delete the orphan config and re-run \\`townhouse setup\\`, or restore the wallet from backup.`\n );\n process.exitCode = 1;\n return;\n }\n\n const docker = dockerInstance ?? new Docker();\n const opener = browserOpener ?? new RealBrowserOpener();\n\n const wizardServer = await createWizardApiServer({\n configDir: dir,\n configPath,\n walletPath,\n port,\n docker,\n });\n\n const url = `http://127.0.0.1:${port}/wizard`;\n\n try {\n await wizardServer.app.listen({ host: '127.0.0.1', port });\n } catch (err: unknown) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === 'EADDRINUSE') {\n console.error(\n `Port ${port} is already in use. Pass \\`--port <n>\\` to choose a different port.`\n );\n process.exitCode = 1;\n try {\n await wizardServer.close();\n } catch {\n /* best-effort */\n }\n return;\n }\n throw err;\n }\n console.log(`Wizard ready at ${url}`);\n\n if (!noBrowser) {\n await opener.open(url);\n }\n\n // Wire signal handlers via process.once so they self-remove after firing\n // — prevents listener leaks when tests call main(['setup', ...]) repeatedly.\n // The Fastify server keeps the process alive after handleSetup returns;\n // signals trigger graceful close.\n let shuttingDown = false;\n const shutdown = async (sig: string) => {\n if (shuttingDown) return;\n shuttingDown = true;\n console.log(`\\nReceived ${sig}, shutting down...`);\n try {\n await wizardServer.close();\n } catch {\n /* best-effort */\n }\n process.exit(0);\n };\n process.once('SIGINT', () => {\n void shutdown('SIGINT');\n });\n process.once('SIGTERM', () => {\n void shutdown('SIGTERM');\n });\n}\n\n/**\n * Per-node role description shown above the address rows in the cards layout.\n * Mirrors Sally's round-4 wallet-show sketch.\n */\nconst NODE_ROLE_DESCRIPTIONS: Record<NodeType, string> = {\n town: 'Nostr relay — earns ILP fees per event relayed.',\n mill: 'Multi-chain swap peer — settles cross-chain swaps for fees.',\n dvm: 'Compute / DVM worker — collects job payments, signs Arweave uploads.',\n};\n\n/** Per-address purpose labels (one per key type per node). */\ninterface AddressRow {\n label: string; // e.g. \"Nostr\", \"EVM\", \"SOL\", \"Mina\", \"AR\"\n value: string; // e.g. npub1..., 0x..., base58..., AR address, or \"—\"\n purpose: string; // short trailing parenthetical\n hex?: string | undefined; // raw hex pubkey shown under npub when --hex\n path?: string | undefined; // derivation path shown when --paths\n}\n\n/**\n * Build the per-row address records for one node's card. Returns rows in\n * fixed presentation order (Nostr → EVM → chain rows). Optional rows render\n * as `—` when the underlying key is absent (e.g. Town has no SOL).\n */\nfunction buildNodeRows(\n info: NodeKeyInfo,\n options: { hex: boolean; paths: boolean }\n): AddressRow[] {\n const rows: AddressRow[] = [];\n\n // Nostr — always present. Encode as NIP-19 npub for the primary display.\n const npub = nip19.npubEncode(info.nostrPubkey);\n const nostrPurposeByNode: Record<NodeType, string> = {\n town: 'share this to be found',\n mill: 'announces swap quotes',\n dvm: 'offers DVM services',\n };\n rows.push({\n label: 'Nostr',\n value: npub,\n purpose: nostrPurposeByNode[info.nodeType],\n hex: options.hex ? info.nostrPubkey : undefined,\n path: options.paths ? info.nostrDerivationPath : undefined,\n });\n\n // EVM — always present.\n const evmPurposeByNode: Record<NodeType, string> = {\n town: 'receives ILP earnings',\n mill: 'settles EVM swaps',\n dvm: 'collects job payments',\n };\n rows.push({\n label: 'EVM',\n value: info.evmAddress,\n purpose: evmPurposeByNode[info.nodeType],\n path: options.paths ? info.evmDerivationPath : undefined,\n });\n\n // SOL — present for every node after Phase 1 (graceful fallback if absent).\n const solPurposeByNode: Record<NodeType, string> = {\n town: 'receives swap fills',\n mill: 'settles SOL swaps',\n dvm: 'spends Arweave credits',\n };\n rows.push({\n label: 'SOL',\n value: info.solanaAddress ?? '—',\n purpose: solPurposeByNode[info.nodeType],\n path: options.paths ? info.solanaDerivationPath : undefined,\n });\n\n // Mill-only: Mina row after SOL.\n if (info.nodeType === 'mill') {\n rows.push({\n label: 'Mina',\n value: info.minaAddress ?? '—',\n purpose: 'settles Mina swaps',\n // Mina derivation path is not currently surfaced through NodeKeyInfo.\n });\n }\n\n // DVM-only: Arweave row appended after SOL.\n if (info.nodeType === 'dvm') {\n rows.push({\n label: 'AR',\n value: info.arweaveAddress ?? '—',\n purpose: 'signs Arweave uploads',\n path: options.paths ? info.arweaveDerivationPath : undefined,\n });\n }\n\n return rows;\n}\n\n/**\n * Render one node card to stdout using box-drawing characters consistent\n * with the existing CLI aesthetic (see HELP_TEXT, status output).\n *\n * ┌─ TOWN ──── Nostr relay — earns ILP fees ────────┐\n * │ Nostr npub1abc... │\n * │ (share this to be found) │\n * │ EVM 0xAbC... │\n * │ (receives ILP earnings) │\n * └──────────────────────────────────────────────────┘\n *\n * Width is calculated from the widest row; the border auto-fits.\n */\nfunction renderNodeCard(info: NodeKeyInfo, rows: AddressRow[]): string {\n // Compose inner content lines (no border yet, no leading \"│ \").\n const role = NODE_ROLE_DESCRIPTIONS[info.nodeType];\n const labelWidth = Math.max(...rows.map((r) => r.label.length));\n const headerLine = `${info.nodeType.toUpperCase()} — ${role}`;\n\n const bodyLines: string[] = [];\n for (const row of rows) {\n bodyLines.push(`${row.label.padEnd(labelWidth)} ${row.value}`);\n bodyLines.push(`${' '.repeat(labelWidth)} (${row.purpose})`);\n if (row.hex) {\n bodyLines.push(`${' '.repeat(labelWidth)} hex: ${row.hex}`);\n }\n if (row.path) {\n bodyLines.push(`${' '.repeat(labelWidth)} path: ${row.path}`);\n }\n }\n\n const innerWidth = Math.max(\n headerLine.length,\n ...bodyLines.map((l) => l.length)\n );\n // Leave 1 char of padding on each side.\n const totalInner = innerWidth + 2;\n const horizontal = '─'.repeat(totalInner);\n const top = `┌${horizontal}┐`;\n const bottom = `└${horizontal}┘`;\n\n const lines: string[] = [];\n lines.push(top);\n lines.push(`│ ${headerLine.padEnd(innerWidth)} │`);\n // Separator under the header to set off the role text from rows.\n lines.push(`├${horizontal}┤`);\n for (const body of bodyLines) {\n lines.push(`│ ${body.padEnd(innerWidth)} │`);\n }\n lines.push(bottom);\n return lines.join('\\n');\n}\n\n/**\n * Build the structured JSON payload for `wallet show --json`. Schema is\n * documented in the plan; consumers like `jq` rely on the field names below.\n */\nfunction buildWalletJson(allKeys: NodeKeyInfo[]): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const info of allKeys) {\n const node: Record<string, unknown> = {\n nostr: {\n npub: nip19.npubEncode(info.nostrPubkey),\n hex: info.nostrPubkey,\n path: info.nostrDerivationPath,\n },\n evm: { address: info.evmAddress, path: info.evmDerivationPath },\n };\n if (info.solanaAddress) {\n node['sol'] = {\n address: info.solanaAddress,\n path: info.solanaDerivationPath,\n };\n }\n if (info.nodeType === 'mill' && info.minaAddress) {\n node['mina'] = { address: info.minaAddress };\n }\n if (info.nodeType === 'dvm' && info.arweaveAddress) {\n node['arweave'] = {\n address: info.arweaveAddress,\n path: info.arweaveDerivationPath,\n };\n }\n out[info.nodeType] = node;\n }\n return out;\n}\n\n/**\n * Extended `townhouse wallet show` (epic-49, Phase 3).\n *\n * Default output: cards layout (one card per node) using box-drawing\n * characters. Shows NIP-19 npub by default; hex hidden unless --hex.\n * Per Sally's round-4 sketch: role description line above the rows, plus\n * per-row purpose labels (e.g. \"receives ILP earnings\").\n *\n * Flags:\n * --json structured machine-readable output instead of cards\n * --hex append a hex pubkey line under each Nostr npub\n * --paths append a derivation-path line under each address\n *\n * Side effects: triggers `ensureArweaveKey('dvm')` once — RSA-4096 derivation\n * is 5–30s on first call per unlocked session, then cached. A \"deriving…\"\n * status line is printed to stderr so the operator knows why it's pausing.\n */\nasync function handleWalletShow(\n config: TownhouseConfig,\n password?: string,\n options: { json?: boolean; hex?: boolean; paths?: boolean } = {}\n): Promise<void> {\n const walletPath = config.wallet.encrypted_path;\n const result = await loadWallet(walletPath);\n\n if (!result) {\n console.error('No wallet found. Run `townhouse init` first.');\n process.exitCode = 1;\n return;\n }\n\n if (result.permissionsWarning) {\n console.error(result.permissionsWarning);\n }\n\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (!walletPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const walletManager = new WalletManager({ encryptedPath: walletPath });\n try {\n // Decrypt mnemonic in minimal scope — fromMnemonic derives keys then\n // the mnemonic string becomes unreachable (eligible for GC)\n await walletManager.fromMnemonic(\n decryptWallet(result.wallet, walletPassword)\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n try {\n // Derive DVM's Arweave key before rendering so the AR address row is\n // populated. RSA-4096 generation is 5–30s on first call per unlocked\n // session — emit a status line to stderr so the operator knows why\n // we're pausing. Subsequent calls in the same session are instant.\n // On failure (unsupported platform, etc.) we degrade gracefully and\n // render the AR row as `—` rather than aborting `wallet show`.\n const arStartMs = Date.now();\n // Spinner-style status — only worth emitting if the call actually pauses.\n // Cache hit returns in <100ms; cold derivation pays the 5–30s. We can't\n // know up front which path will run, so we set a 200ms timer that prints\n // the status only when needed, and clear it on completion.\n const arStatusTimer = setTimeout(() => {\n process.stderr.write('deriving Arweave key (first run, ~15s)...\\n');\n }, 200);\n try {\n await walletManager.ensureArweaveKey('dvm', walletPassword);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(\n `Warning: Arweave key derivation failed (${msg}). AR address will display as '—'.`\n );\n } finally {\n clearTimeout(arStatusTimer);\n void arStartMs; // keep var alive for potential timing log\n }\n\n const allKeys = walletManager.getAllKeys();\n\n if (options.json) {\n console.log(JSON.stringify(buildWalletJson(allKeys), null, 2));\n return;\n }\n\n const renderOpts = {\n hex: options.hex === true,\n paths: options.paths === true,\n };\n for (const info of allKeys) {\n const rows = buildNodeRows(info, renderOpts);\n console.log(renderNodeCard(info, rows));\n console.log(''); // blank line between cards\n }\n\n // Trailing tips block — guides the operator to scripting and the\n // related credit-funding command added in Phase 2.\n console.log('Tip: townhouse wallet show --json for scripting');\n console.log(' townhouse wallet show --hex to see raw hex pubkeys');\n console.log(' townhouse wallet show --paths to see derivation paths');\n console.log(\n ' townhouse credits buy --token sol --amount <n> to fund Arweave uploads'\n );\n } finally {\n // Zero key material immediately after display\n walletManager.lock();\n }\n}\n\n/**\n * `townhouse wallet seed --confirm` (epic-49, Phase 3).\n *\n * Reveals the BIP-39 mnemonic for recovery. Requires --confirm so it's\n * impossible to leak the seed by typing the wrong subcommand at a public\n * terminal. Same password-sourcing chain as the rest of the wallet commands.\n *\n * Closes the \"I scrolled past the init banner in tmux\" footgun without\n * inventing a new backup channel (clipboard, QR, encrypted USB, etc.).\n */\nasync function handleWalletSeed(\n config: TownhouseConfig,\n password: string | undefined,\n confirm: boolean\n): Promise<void> {\n if (!confirm) {\n console.error(\n 'This command will print your seed phrase to your terminal. Re-run with --confirm to acknowledge.'\n );\n process.exitCode = 1;\n return;\n }\n\n const walletPath = config.wallet.encrypted_path;\n const result = await loadWallet(walletPath);\n if (!result) {\n console.error('No wallet found. Run `townhouse init` first.');\n process.exitCode = 1;\n return;\n }\n if (result.permissionsWarning) {\n console.error(result.permissionsWarning);\n }\n\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (!walletPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const walletManager = new WalletManager({ encryptedPath: walletPath });\n try {\n await walletManager.fromMnemonic(\n decryptWallet(result.wallet, walletPassword)\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n try {\n const mnemonic = walletManager.getMnemonic();\n if (!mnemonic) {\n // Shouldn't happen — fromMnemonic just succeeded — but defend against\n // races with concurrent lock() calls in long-running test processes.\n console.error('Internal error: mnemonic unavailable after unlock.');\n process.exitCode = 1;\n return;\n }\n\n // ASCII-only warning banner — CLAUDE.md forbids emojis unless requested.\n console.log(\n '============================================================='\n );\n console.log(' [!] Anyone who sees this seed owns your townhouse identity.');\n console.log(' [!] Anyone who records this terminal owns your earnings.');\n console.log(\n ' [!] Shoulder-surf, screen-shares, and tmux logs are vectors.'\n );\n console.log(\n '============================================================='\n );\n console.log('');\n console.log('');\n console.log(` ${mnemonic}`);\n console.log('');\n console.log('');\n console.log(\n 'This is the same 12 words shown at `townhouse init`. Storing them elsewhere is your responsibility.'\n );\n } finally {\n walletManager.lock();\n }\n}\n\n// ── Credits commands (epic-49, Phase 2) ────────────────────────────────────\n\n/**\n * Set of valid `--token` values for credits commands. Must stay in sync with\n * `TurboTokenId` in wallet/turbo-signer.ts.\n */\nconst VALID_TURBO_TOKENS: ReadonlySet<TurboTokenId> = new Set([\n 'eth',\n 'pol',\n 'base-eth',\n 'base-usdc',\n 'usdc-eth',\n 'usdc-pol',\n 'sol',\n 'ar',\n]);\n\nfunction isTurboTokenId(value: string): value is TurboTokenId {\n return VALID_TURBO_TOKENS.has(value as TurboTokenId);\n}\n\n/**\n * Resolve the wallet password from --password → env var → TTY prompt → error.\n * Returns the resolved password string OR null when no source is available\n * (caller should set exitCode=1 and return).\n */\nasync function resolveWalletPassword(\n flagPassword: string | undefined\n): Promise<string | null> {\n const envPassword = process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (flagPassword) return flagPassword;\n if (envPassword) return envPassword;\n if (process.stdin.isTTY) {\n return await promptPassword('Wallet password: ');\n }\n return null;\n}\n\n/**\n * Read a single y/N answer from stdin, defaulting to N on empty input.\n * Mirrors the existing readline-based prompt in `hs down --rotate-keys`.\n */\nasync function promptYesNo(question: string): Promise<boolean> {\n const { createInterface } = await import('node:readline');\n const answer = await new Promise<string>((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n rl.question(question, (ans) => {\n rl.close();\n resolve(ans);\n });\n });\n return ['y', 'yes'].includes(answer.trim().toLowerCase());\n}\n\n/**\n * Handle `townhouse credits buy --token <id> --amount <decimal> [...]`.\n *\n * Flow: parse argv → resolve password → unlock wallet → fetch quote →\n * (unless --yes) ask for confirmation → submit topUpWithTokens → stream\n * status to stdout.\n *\n * For the testable contract: returns void, sets process.exitCode on failure\n * paths, writes status to stdout. Pure infrastructure happens in\n * `credits/buy.ts`.\n */\nasync function handleCreditsBuy(\n config: TownhouseConfig,\n values: Record<string, unknown>,\n nodeType: NodeType = 'dvm'\n): Promise<void> {\n // ── 1. Argv validation ──\n const tokenRaw = values['token'] as string | undefined;\n const amountRaw = values['amount'] as string | undefined;\n if (!tokenRaw || !amountRaw) {\n console.error(\n 'Usage: townhouse credits buy --token <id> --amount <decimal> [--fee-multiplier <n>] [--credit-destination <addr>] [--quote-only] [--yes]'\n );\n process.exitCode = 1;\n return;\n }\n if (!isTurboTokenId(tokenRaw)) {\n console.error(\n `Unknown token '${tokenRaw}'. Supported: ${Array.from(VALID_TURBO_TOKENS).join(', ')}`\n );\n process.exitCode = 1;\n return;\n }\n const token: TurboTokenId = tokenRaw;\n\n let feeMultiplier: number | undefined;\n const feeRaw = values['fee-multiplier'] as string | undefined;\n if (feeRaw !== undefined) {\n const parsed = Number(feeRaw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n console.error(\n `--fee-multiplier must be a positive number, got '${feeRaw}'`\n );\n process.exitCode = 1;\n return;\n }\n feeMultiplier = parsed;\n }\n const quoteOnly = values['quote-only'] === true;\n const skipConfirm = values['yes'] === true;\n const destinationOverride = values['credit-destination'] as\n | string\n | undefined;\n\n // ── 2. Wallet unlock ──\n const walletPath = config.wallet.encrypted_path;\n const loaded = await loadWallet(walletPath);\n if (!loaded) {\n console.error(\n `No wallet found at ${walletPath}. Run \\`townhouse init\\` first.`\n );\n process.exitCode = 1;\n return;\n }\n if (loaded.permissionsWarning) console.error(loaded.permissionsWarning);\n\n const resolvedPassword = await resolveWalletPassword(\n values['password'] as string | undefined\n );\n if (!resolvedPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const wallet = new WalletManager({ encryptedPath: walletPath });\n try {\n await wallet.fromMnemonic(decryptWallet(loaded.wallet, resolvedPassword));\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n try {\n // ── 3. Resolve credit destination ──\n // Funding from EVM/SOL must route credits to the DVM's Arweave address so\n // the DVM container's ArweaveSigner can spend them. Funding from `ar`\n // already targets that address natively (signer === destination). An\n // explicit --credit-destination flag overrides both behaviors.\n let destinationAddress: string | undefined;\n if (destinationOverride) {\n destinationAddress = destinationOverride;\n } else if (token !== 'ar' && nodeType === 'dvm') {\n process.stdout.write(\n `Resolving DVM Arweave credit address (first run, ~10s)...\\n`\n );\n await wallet.ensureArweaveKey('dvm', resolvedPassword);\n const dvmKeys = wallet.getNodeKeys('dvm');\n if (!dvmKeys.arweaveAddress) {\n throw new Error(\n 'DVM Arweave address not populated after ensureArweaveKey'\n );\n }\n destinationAddress = dvmKeys.arweaveAddress;\n }\n\n // ── 4. Quote step ──\n process.stdout.write(\n `Quoting ${amountRaw} ${token} for ${nodeType}'s credit address...\\n`\n );\n const quote = await buyCredits({\n wallet,\n nodeType,\n token,\n amount: amountRaw,\n quoteOnly: true,\n ...(destinationAddress ? { destinationAddress } : {}),\n });\n if (quote.kind !== 'quote') {\n throw new Error('Internal error: quoteOnly returned non-quote result');\n }\n const quotedDisplay = `${quote.winc.toString()} winc (${formatWincAsBytes(quote.winc)})`;\n process.stdout.write(\n `Quote: ${formatTokenAmount(token, quote.baseAmount)} → ${quotedDisplay}\\n`\n );\n process.stdout.write(`Source address (${token}): ${quote.fromAddress}\\n`);\n process.stdout.write(`Credit recipient: ${quote.creditAddress}\\n`);\n\n if (quoteOnly) {\n process.stdout.write('Quote-only; no on-chain transaction submitted.\\n');\n return;\n }\n\n // ── 5. Confirmation ──\n if (!skipConfirm) {\n const ok = await promptYesNo('Proceed? [y/N] ');\n if (!ok) {\n process.stdout.write('Aborted. No transaction submitted.\\n');\n process.exitCode = 1;\n return;\n }\n }\n\n // ── 6. Submit ──\n process.stdout.write('Submitting on-chain transaction...\\n');\n const result = await buyCredits({\n wallet,\n nodeType,\n token,\n amount: amountRaw,\n ...(feeMultiplier !== undefined ? { feeMultiplier } : {}),\n ...(destinationAddress ? { destinationAddress } : {}),\n });\n if (result.kind !== 'submit') {\n throw new Error('Internal error: submit path returned non-submit result');\n }\n process.stdout.write(`Transaction submitted: ${result.id}\\n`);\n process.stdout.write(`Status: ${result.status}\\n`);\n process.stdout.write(\n `Credited: ${result.winc.toString()} winc (${formatWincAsBytes(result.winc)})\\n`\n );\n if (result.block !== undefined) {\n process.stdout.write(`Block: ${result.block}\\n`);\n }\n process.stdout.write('Done.\\n');\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`credits buy failed: ${msg}`);\n process.exitCode = 1;\n } finally {\n wallet.lock();\n }\n}\n\n/**\n * Handle `townhouse credits balance --token <id>`.\n *\n * Per AC#6 (plan): require --token explicitly. The funding identity differs\n * per token family (EVM vs SOL vs AR), so there is no sensible default.\n */\nasync function handleCreditsBalance(\n config: TownhouseConfig,\n values: Record<string, unknown>,\n nodeType: NodeType = 'dvm'\n): Promise<void> {\n const tokenRaw = values['token'] as string | undefined;\n if (!tokenRaw) {\n console.error(\n 'Usage: townhouse credits balance --token <id> [-c <path>] [--password <pw>]'\n );\n process.exitCode = 1;\n return;\n }\n if (!isTurboTokenId(tokenRaw)) {\n console.error(\n `Unknown token '${tokenRaw}'. Supported: ${Array.from(VALID_TURBO_TOKENS).join(', ')}`\n );\n process.exitCode = 1;\n return;\n }\n const token: TurboTokenId = tokenRaw;\n\n const walletPath = config.wallet.encrypted_path;\n const loaded = await loadWallet(walletPath);\n if (!loaded) {\n console.error(\n `No wallet found at ${walletPath}. Run \\`townhouse init\\` first.`\n );\n process.exitCode = 1;\n return;\n }\n if (loaded.permissionsWarning) console.error(loaded.permissionsWarning);\n\n const resolvedPassword = await resolveWalletPassword(\n values['password'] as string | undefined\n );\n if (!resolvedPassword) {\n console.error(\n 'Wallet password required. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n process.exitCode = 1;\n return;\n }\n\n const wallet = new WalletManager({ encryptedPath: walletPath });\n try {\n await wallet.fromMnemonic(decryptWallet(loaded.wallet, resolvedPassword));\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n try {\n const balance = await getCreditBalance({ wallet, nodeType, token });\n process.stdout.write(`Address (${token}): ${balance.address}\\n`);\n process.stdout.write(\n `Balance: ${balance.winc.toString()} winc (${formatWincAsBytes(balance.winc)})\\n`\n );\n if (balance.effectiveBalance !== balance.winc) {\n process.stdout.write(\n `Effective (incl. received approvals): ${balance.effectiveBalance.toString()} winc (${formatWincAsBytes(balance.effectiveBalance)})\\n`\n );\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`credits balance failed: ${msg}`);\n process.exitCode = 1;\n } finally {\n wallet.lock();\n }\n}\n\nasync function resolveEarnings(\n adminUrl: string,\n configPath: string\n): Promise<AggregatedEarnings> {\n const base = dirname(configPath);\n try {\n const yaml = await readNodesYaml(join(base, 'nodes.yaml'));\n return await aggregateEarnings({\n connectorAdmin: new ConnectorAdminClient(adminUrl),\n peerTypeResolver: new PeerTypeResolver(yaml),\n deltaComputer: createDeltaComputer({\n snapshotPath: join(base, 'earnings-snapshots.jsonl'),\n }),\n });\n } catch (err) {\n // Distinguish local config/snapshot errors from connector outage:\n // aggregateEarnings handles connector failures internally, so anything\n // surfaced here is a nodes.yaml / snapshot-file / resolver problem.\n console.error(`Earnings unavailable: ${formatLocalEarningsError(err)}`);\n return {\n status: 'connector_unavailable',\n apex: { routingFees: {} },\n peers: [],\n recentClaims: [],\n eventsRelayed: 0,\n uptimeSeconds: 0,\n };\n }\n}\n\n// Render a one-line breadcrumb from a thrown error, collapsing Zod issue\n// lists (multi-line JSON) into `path: message` segments joined by `; `.\nfunction formatLocalEarningsError(err: unknown): string {\n if (\n err !== null &&\n typeof err === 'object' &&\n 'issues' in err &&\n Array.isArray((err as { issues: unknown }).issues)\n ) {\n const issues = (err as { issues: { path?: unknown; message?: unknown }[] })\n .issues;\n const parts = issues\n .map((i) => {\n const path =\n Array.isArray(i.path) && i.path.length > 0\n ? i.path.join('.')\n : '<root>';\n const msg = typeof i.message === 'string' ? i.message : 'invalid';\n return `${path}: ${msg}`;\n })\n .join('; ');\n if (parts) return parts;\n }\n return err instanceof Error ? err.message : String(err);\n}\n\nasync function handleStatus(\n docker: Docker,\n config: TownhouseConfig,\n opts: { units: 'usdc' | 'sats'; satsPerUsdc?: number; configPath: string } = {\n units: 'usdc',\n configPath: DEFAULT_CONFIG_PATH,\n }\n): Promise<void> {\n const orchestrator = new DockerOrchestrator(docker, config, undefined, {\n profile: 'dev',\n });\n const statuses = await orchestrator.status();\n\n console.log('Node Status:');\n console.log('------------');\n for (const s of statuses) {\n const health = s.health ? ` (${s.health})` : '';\n console.log(` ${s.name.padEnd(12)} ${s.state}${health}`);\n }\n\n const connectorHs = config.transport.hiddenService;\n const relayHs = config.transport.relayHiddenService;\n if (\n config.transport.mode === 'ator' ||\n connectorHs?.externalUrl ||\n relayHs?.externalUrl ||\n config.transport.externalUrl\n ) {\n console.log('');\n console.log('Hidden Services:');\n console.log('----------------');\n const connectorUrl =\n connectorHs?.externalUrl ?? config.transport.externalUrl;\n if (connectorUrl) {\n console.log(` Connector (BTP): ${connectorUrl}`);\n }\n if (relayHs?.externalUrl) {\n console.log(` Relay (Nostr): ${relayHs.externalUrl}`);\n }\n if (!connectorUrl && !relayHs?.externalUrl) {\n console.log(' (ator mode set but no externalUrl configured)');\n }\n }\n\n // Try to include connector metrics (graceful degradation)\n try {\n const adminClient = new ConnectorAdminClient(\n `http://127.0.0.1:${config.connector.adminPort}`\n );\n const metrics = await adminClient.getMetrics();\n const peers = await adminClient.getPeers();\n const activePeers = peers.filter((p) => p.connected).length;\n\n console.log('');\n console.log('Connector Metrics:');\n console.log('------------------');\n console.log(` Packets forwarded: ${metrics.aggregate.packetsForwarded}`);\n console.log(` Active peers: ${activePeers}/${peers.length}`);\n } catch {\n console.log('');\n console.log('Connector Metrics: unavailable');\n }\n\n if (opts.units === 'sats' && opts.satsPerUsdc === undefined) return;\n const earnings = await resolveEarnings(\n `http://127.0.0.1:${config.connector.adminPort}`,\n opts.configPath\n );\n for (const line of renderEarningsSection({\n earnings,\n units: opts.units,\n satsPerUsdc: opts.satsPerUsdc,\n }))\n console.log(line);\n}\n\n// handleMetrics moved to cli/drill-commands.ts (Story 48.5)\n\n/**\n * Determine which node profiles to start based on CLI flags and config.\n * If explicit flags (--town, --mill, --dvm) are provided, use those.\n * Otherwise fall back to all enabled nodes from config.\n */\nfunction resolveProfiles(\n values: Record<string, unknown>,\n config: TownhouseConfig\n): NodeType[] {\n const explicitFlags: NodeType[] = [];\n if (values['town']) explicitFlags.push('town');\n if (values['mill']) explicitFlags.push('mill');\n if (values['dvm']) explicitFlags.push('dvm');\n\n if (explicitFlags.length > 0) {\n return explicitFlags;\n }\n\n // No explicit flags — start all enabled nodes from config\n const enabled: NodeType[] = [];\n if (config.nodes.town.enabled) enabled.push('town');\n if (config.nodes.mill.enabled) enabled.push('mill');\n if (config.nodes.dvm.enabled) enabled.push('dvm');\n return enabled;\n}\n\nasync function handleUp(\n configPath: string,\n config: TownhouseConfig,\n profiles: NodeType[],\n docker: Docker,\n password?: string,\n dryRun = false\n): Promise<void> {\n if (profiles.length === 0) {\n console.log(\n 'No nodes enabled in config. Enable nodes in config.yaml first.'\n );\n return;\n }\n\n // Initialize wallet (Round-2 Decision D1:c).\n // The API's GET /wallet depends on an unlocked wallet. If a wallet file\n // exists on disk it MUST be unlockable (fail-fast on bad password). If no\n // wallet file exists, we log a warning and skip API startup entirely so\n // orchestration-only callers (CI, tooling, smoke tests) still work.\n const walletPath = config.wallet.encrypted_path;\n let walletManager: WalletManager | undefined;\n if (!existsSync(walletPath)) {\n console.error(\n `Wallet not found at ${walletPath}. Run \\`townhouse setup\\` first (or restore your wallet backup).`\n );\n process.exitCode = 1;\n return;\n } else {\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n if (!walletPassword) {\n throw new Error(\n 'Wallet password required to start the API. Use --password flag or TOWNHOUSE_WALLET_PASSWORD env var.'\n );\n }\n const loaded = await loadWallet(walletPath);\n if (!loaded) {\n throw new Error(`Wallet at ${walletPath} could not be read.`);\n }\n if (loaded.permissionsWarning) {\n console.error(loaded.permissionsWarning);\n }\n walletManager = new WalletManager({ encryptedPath: walletPath });\n try {\n await walletManager.fromMnemonic(\n decryptWallet(loaded.wallet, walletPassword)\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to decrypt wallet: ${msg}`);\n }\n\n // Pre-warm AR cache when DVM is in the boot set. The orchestrator's later\n // ensureArweaveKey('dvm') call (without password) would otherwise pay the\n // full 5–30s RSA cost AND not write back to disk. Calling here with the\n // password populates both the in-memory + on-disk caches once and lets\n // every subsequent invocation be sub-second (epic-49 Followup A).\n if (profiles.includes('dvm')) {\n try {\n await walletManager.ensureArweaveKey('dvm', walletPassword);\n } catch (err: unknown) {\n // Non-fatal: orchestrator's own ensureArweaveKey call will retry.\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(\n `[townhouse up] AR pre-warm failed (non-fatal, orchestrator will retry): ${msg}`\n );\n }\n }\n }\n\n const orchestrator = new DockerOrchestrator(docker, config, walletManager, {\n profile: 'dev',\n });\n\n // Wire up progress reporting\n orchestrator.on(\n 'containerState',\n (event: { name: string; state: string }) => {\n console.log(` ${event.name}: ${event.state}`);\n }\n );\n orchestrator.on(\n 'pullProgress',\n (event: { image: string; status: string; progress?: string }) => {\n const progress = event.progress ? ` ${event.progress}` : '';\n console.log(` [pull] ${event.image}: ${event.status}${progress}`);\n }\n );\n\n // API server reference for graceful shutdown\n let apiServer: ApiServer | undefined;\n\n // Register SIGINT handler for graceful shutdown\n const sigintHandler = async () => {\n console.log('\\nReceived SIGINT, shutting down gracefully...');\n\n // Close API server first\n if (apiServer) {\n try {\n await apiServer.close();\n } catch {\n // Best-effort\n }\n }\n\n // Then stop containers\n try {\n await orchestrator.down();\n } catch {\n // Best-effort cleanup\n }\n process.exit(0);\n };\n process.on('SIGINT', sigintHandler);\n\n // For SIGTERM\n const sigtermHandler = async () => {\n console.log('\\nReceived SIGTERM, shutting down gracefully...');\n\n if (apiServer) {\n try {\n await apiServer.close();\n } catch {\n // Best-effort\n }\n }\n\n try {\n await orchestrator.down();\n } catch {\n // Best-effort cleanup\n }\n process.exit(0);\n };\n process.on('SIGTERM', sigtermHandler);\n\n // Track if the server started successfully (handlers stay registered if true)\n let serverStarted = false;\n\n if (\n profiles.includes('dvm') &&\n config.nodes.dvm.enabled &&\n !process.env['TURBO_TOKEN']\n ) {\n console.warn(\n '[townhouse] WARN: TURBO_TOKEN is not set — Arweave DVM (kind:5094) uploads will fail at first job.'\n );\n console.warn(\n '[townhouse] Export TURBO_TOKEN=<arweave-jwk-json> before `townhouse up` to enable uploads.'\n );\n }\n\n try {\n console.log(`Starting nodes: ${profiles.join(', ')}...`);\n if (!dryRun) {\n await orchestrator.up(profiles);\n console.log('All nodes started successfully.');\n } else {\n console.log('[dry-run] Skipped orchestrator.up()');\n }\n\n // Start API server after nodes are up\n if (walletManager) {\n const connectorAdmin = new ConnectorAdminClient(\n `http://127.0.0.1:${config.connector.adminPort}`\n );\n\n const transportProbe = new TransportProbe({\n proxyUrl:\n config.transport.mode === 'ator'\n ? (config.transport.socksProxy ?? DEFAULT_ATOR_PROXY)\n : '',\n });\n if (config.transport.mode === 'ator') {\n transportProbe.start();\n }\n\n const apiDeps = {\n configPath,\n config,\n orchestrator,\n wallet: walletManager,\n connectorAdmin,\n transportProbe,\n };\n\n apiServer = await createApiServer(apiDeps);\n\n const { host, port } = config.api;\n if (!dryRun) {\n await apiServer.app.listen({\n host: host ?? '127.0.0.1',\n port: port ?? 9400,\n });\n serverStarted = true;\n\n console.log(\n `\\n[Townhouse API] listening on http://${host ?? '127.0.0.1'}:${port ?? 9400}`\n );\n console.log(\n ' GET /nodes, GET /nodes/:type, PATCH /nodes/:type/config, GET /wallet, WS /metrics'\n );\n } else {\n // Log a structured summary for the dry-run smoke test (Task 8.3).\n console.log(\n `[dry-run] API factory invoked: configPath=${configPath} host=${host ?? '127.0.0.1'} port=${port ?? 9400} connectorAdmin=http://127.0.0.1:${config.connector.adminPort} wallet=WalletManager`\n );\n await apiServer.close();\n apiServer = undefined;\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n if (\n msg.includes('Docker is not running') ||\n msg.includes('ENOENT') ||\n msg.includes('ECONNREFUSED') ||\n msg.includes('socket')\n ) {\n throw new Error(\n `Docker is not available. Please ensure Docker is running and try again. (${msg})`\n );\n }\n throw error;\n } finally {\n // Only remove signal handlers if server never started\n // If server is running, handlers enable graceful shutdown on SIGTERM/SIGINT\n if (!serverStarted) {\n process.removeListener('SIGINT', sigintHandler);\n process.removeListener('SIGTERM', sigtermHandler);\n }\n }\n}\n\nasync function handleDown(\n config: TownhouseConfig,\n docker: Docker\n): Promise<void> {\n const orchestrator = new DockerOrchestrator(docker, config, undefined, {\n profile: 'dev',\n });\n\n orchestrator.on(\n 'containerState',\n (event: { name: string; state: string }) => {\n console.log(` ${event.name}: ${event.state}`);\n }\n );\n\n console.log('Stopping nodes...');\n await orchestrator.down();\n console.log('All nodes stopped.');\n}\n\n/** Connector admin URL for HS mode. */\nconst HS_CONNECTOR_ADMIN_URL = 'http://127.0.0.1:9401';\n/** Townhouse API URL for HS mode (inside the townhouse-api container). */\nconst HS_TOWNHOUSE_API_URL = 'http://127.0.0.1:28090';\n\n/**\n * Run `reconciler.reconcile()` with a brief retry budget for cold-boot\n * transients. The connector container may not have bound its admin port\n * by the time `orchestrator.up()` resolves; treat ECONNREFUSED / timeout\n * on early attempts as \"still warming\" and retry. Persistent failures\n * surface to the caller and end up in the non-fatal stderr log.\n */\nasync function reconcileWithBriefRetry(\n reconciler: { reconcile: () => Promise<unknown> },\n budgetMs: number\n): Promise<void> {\n const deadline = Date.now() + budgetMs;\n for (;;) {\n try {\n await reconciler.reconcile();\n return;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n const transient =\n msg.includes('ECONNREFUSED') ||\n msg.includes('connection refused') ||\n msg.includes('request timeout');\n if (!transient || Date.now() >= deadline) {\n throw err;\n }\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n}\n\n/**\n * Boot the apex (connector + townhouse-api) via `townhouse hs up`.\n * Idempotent: if the apex is already running, re-prints the hostname and exits 0.\n * After the apex is live, writes `~/.townhouse/host.json` and prints the final line.\n */\n/**\n * Collect the apex image refs (digest-pinned) that `hs up` should pre-pull\n * before invoking `docker compose up -d`.\n *\n * Apex always-on services (Story 45.2 / 45.4) are connector + townhouse-api.\n * Profile-gated services (town/mill/dvm) are lazy-provisioned via\n * `POST /api/nodes` and excluded here.\n *\n * Returns `[]` (and never throws) if:\n * - `image-manifest.json` is absent under `<configDir>` (local-dev tree\n * without a CI-produced manifest), OR\n * - the manifest exists but cannot be parsed (corrupt file). The caller\n * treats `[]` as \"skip pre-pull narration\" and lets compose handle it.\n */\nasync function collectApexImageRefs(configDir: string): Promise<string[]> {\n const manifestPath = join(configDir, 'image-manifest.json');\n if (!existsSync(manifestPath)) return [];\n try {\n const manifest = await readImageManifest(manifestPath);\n // Skip pre-pull if the manifest is synthetic (smoke-workflow sentinel).\n // Attempting to pull sha256:dead000… from the registry would fail with a\n // cryptic 404; the compose step will handle image resolution correctly.\n if (\n isSyntheticDigest(manifest.images.connector.digest) ||\n isSyntheticDigest(manifest.images['townhouse-api'].digest)\n ) {\n return [];\n }\n return [\n `${manifest.images.connector.name}@${manifest.images.connector.digest}`,\n `${manifest.images['townhouse-api'].name}@${manifest.images['townhouse-api'].digest}`,\n ];\n } catch {\n return [];\n }\n}\n\n/**\n * Returns true when an OrchestratorError is caused by the ATOR anon SDK's\n * hardcoded 60s bootstrap timeout (@anyone-protocol/anyone-client@1.1.x\n * `setupTimeoutHandler`). The connector container goes unhealthy and compose\n * exits 1 — the retry loop in handleHsUp restarts the stack on this signal.\n */\nfunction isAnonBootstrapTimeout(err: unknown): boolean {\n if (!(err instanceof OrchestratorError)) return false;\n const text = `${err.message}\\n${err.stderr ?? ''}`;\n return /connector.*unhealthy|dependency.*connector.*fail/i.test(text);\n}\n\n/**\n * Foreground the Ink dashboard for an already-live apex when stdout is a TTY.\n * Used by both the cold-boot path and the idempotent re-run path, so that\n * re-running `townhouse hs up` against a running node re-attaches the dashboard\n * instead of just printing the hostname and exiting.\n *\n * A TUI mount/runtime failure must NOT be treated as a boot failure — apex is\n * already live — so it is caught and reported as a display issue. No-op on\n * non-TTY stdout (the process then exits naturally after printing the address).\n */\nasync function attachDashboard(hostname: string): Promise<void> {\n if (!shouldRenderInk()) return;\n try {\n const { mountTui } = await import('./tui/index.js');\n // P27 (D1): thread HS_TOWNHOUSE_API_URL env override into the TUI so\n // operators on a non-default Fastify port don't see eternal fetch_failed.\n const apiUrlOverride = process.env['HS_TOWNHOUSE_API_URL'];\n const mountOpts =\n apiUrlOverride !== undefined ? { apiUrl: apiUrlOverride } : {};\n const instance = mountTui(mountOpts);\n await instance.waitUntilExit();\n } catch (tuiErr: unknown) {\n const detail = tuiErr instanceof Error ? tuiErr.message : String(tuiErr);\n console.error('');\n console.error(`Your node is live at ${hostname}.`);\n console.error(\n `The live dashboard could not open (${detail}) — this is a display ` +\n 'issue, not a node issue. Your node keeps running.'\n );\n console.error(\n 'Stop it anytime with: npx @toon-protocol/townhouse hs down'\n );\n // Leave process.exitCode at success — the node is live.\n }\n}\n\nasync function handleHsUp(\n _configPath: string,\n configDir: string,\n config: TownhouseConfig,\n docker: Docker,\n options: {\n password?: string;\n force?: boolean;\n skipPreflight?: boolean;\n hsOverrides?: CliHsOverrides;\n }\n): Promise<void> {\n const { password, force, skipPreflight, hsOverrides } = options;\n\n // ── Idempotency probe (AC #7) — BEFORE the preflight ────────────────────────\n // If our apex is already live, this is a re-run: re-print the address, refresh\n // host.json, and (in a TTY) re-attach the dashboard, then return. This MUST run\n // before the port preflight: the preflight would otherwise flag our OWN apex's\n // canonical ports as a collision and refuse, making an idempotent re-run (and\n // re-attaching the dashboard) impossible. Skipped under --force (cold rebuild).\n if (!force) {\n const adminClientFactory =\n hsOverrides?.createAdminClient ??\n ((url: string, t: number) => new ConnectorAdminClient(url, t));\n const probe = adminClientFactory(HS_CONNECTOR_ADMIN_URL, 3_000);\n try {\n const existing = await probe.getHsHostname();\n if (existing.hostname !== null) {\n // hostname from the connector already includes the .anyone suffix.\n console.log(`Apex live at ${existing.hostname}`);\n _writeHostJson(configDir, {\n hostname: existing.hostname,\n publishedAt: existing.publishedAt ?? new Date().toISOString(),\n writtenAt: new Date().toISOString(),\n });\n await attachDashboard(existing.hostname);\n return;\n }\n // hostname null → apex started but HS not ready → treat as cold-start.\n } catch (probeErr: unknown) {\n const msg =\n probeErr instanceof Error ? probeErr.message : String(probeErr);\n if (msg.includes('anon-disabled')) {\n // Apex running but anon is disabled — render failure copy and exit.\n const { exitCode } = renderFailure(probeErr);\n process.exitCode = exitCode;\n return;\n }\n // ECONNREFUSED / timeout → not running → fall through to preflight + boot.\n }\n }\n\n // ── Preflight: port-collision check (Epic 49 Followup B) ────────────────────\n // Only reached on a true cold start (no apex already live). Catches the most\n // common operator footgun (contributor dev stack still up, another hs up\n // instance, or an unrelated process bound to the canonical ports) and\n // surfaces an actionable error instead of a cryptic mid-boot EADDRINUSE.\n //\n // `--skip-preflight` bypasses the check (escape hatch for operators who\n // know what they're doing — e.g. running two HS stacks on different\n // network interfaces; rare but harmless).\n if (!skipPreflight) {\n const preflight =\n hsOverrides?.checkPortCollisions ??\n ((d: Docker) => checkHsPortCollisions(d));\n try {\n const collisions = await preflight(docker);\n if (collisions.length > 0) {\n const msg = formatCollisionMessage(collisions);\n // Write directly to stderr (multi-line message — console.error adds\n // an extra newline per call which would shred the formatting).\n process.stderr.write(msg);\n process.exitCode = 1;\n return;\n }\n } catch (preflightErr: unknown) {\n // Preflight itself failed unexpectedly (e.g. kernel out of fds). Log\n // and continue rather than block boot — the existing Docker-level\n // EADDRINUSE handler is still there as a fallback.\n const detail =\n preflightErr instanceof Error\n ? preflightErr.message\n : String(preflightErr);\n console.error(\n `[townhouse hs up] port preflight skipped (non-fatal): ${detail}`\n );\n }\n }\n\n // Resolve wallet password (AC #10): --password → env var → interactive prompt → reject\n const walletPath = config.wallet.encrypted_path;\n if (!existsSync(walletPath)) {\n console.error(\n `Wallet not found at ${walletPath}. Run \\`townhouse init\\` first.`\n );\n process.exitCode = 1;\n return;\n }\n\n const walletPassword = password ?? process.env['TOWNHOUSE_WALLET_PASSWORD'];\n\n let resolvedPassword: string;\n if (walletPassword) {\n resolvedPassword = walletPassword;\n } else if (process.stdin.isTTY) {\n resolvedPassword = await promptPassword('Wallet password: ');\n } else {\n // No interactive terminal (CI, SSH without a TTY, piped stdin). Make the\n // reason explicit so the user knows why no prompt appeared and what to do.\n console.error(\n 'Wallet password required, but no interactive terminal is available to prompt.\\n' +\n 'Pass --password <pw> or set TOWNHOUSE_WALLET_PASSWORD.'\n );\n process.exitCode = 1;\n return;\n }\n\n const loaded = await loadWallet(walletPath);\n if (!loaded) {\n console.error(`Wallet at ${walletPath} could not be read.`);\n process.exitCode = 1;\n return;\n }\n\n let walletManager: WalletManager | undefined;\n try {\n walletManager = new WalletManager({ encryptedPath: walletPath });\n await walletManager.fromMnemonic(\n decryptWallet(loaded.wallet, resolvedPassword)\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`Failed to decrypt wallet: ${msg}`);\n process.exitCode = 1;\n return;\n }\n\n const ribbon = new OnboardingRibbon();\n\n try {\n // Cold-boot path. (The idempotency probe runs earlier — above the preflight —\n // so an already-live apex re-attaches instead of failing the port check.)\n\n // Step 1: write connector.yaml with anon.enabled: true (AC #3).\n writeHsConnectorConfig(configDir, config, { force });\n\n // Step 2: materialize compose template.\n const materialize =\n hsOverrides?.materializeComposeTemplate ?? materializeComposeTemplate;\n const { composePath } = materialize('hs', { townhouseHome: configDir });\n\n // Step 2b: write compose/.env from the `network` mode so the compose\n // template's ${EVM_CHAIN}/${EVM_RPC_URL}/${SOLANA_*} interpolations resolve\n // to real public endpoints for the chosen tier (apex + children share the\n // same network profile). Must run after materialize (which creates compose/).\n writeHsNodeEnvFile(configDir, config);\n\n // Step 3: start the ribbon (phase 1 — pulling).\n ribbon.start('pull');\n\n // Step 4: construct orchestrator and wire ribbon events.\n const orchestratorFactory =\n hsOverrides?.createOrchestrator ??\n ((\n d: Docker,\n cfg: TownhouseConfig,\n wm: WalletManager | undefined,\n opts: { profile: 'hs'; composePath: string }\n ) => new DockerOrchestrator(d, cfg, wm, opts));\n\n const orch = orchestratorFactory(docker, config, walletManager, {\n profile: 'hs',\n composePath,\n });\n\n // ── pullProgress narration (Epic 49 Followup D) ───────────────────────\n // Subscribe BEFORE any pulls so we never miss the first events. Uses\n // the throttled narrator to dedupe Downloading/Extracting noise.\n const narrator = new PullNarrator();\n orch.on('pullProgress', (event: unknown) => {\n const ev = event as {\n image?: string;\n status?: string;\n id?: string;\n progress?: string;\n };\n if (!ev.image || !ev.status) return;\n const line = narrator.format({\n image: ev.image,\n status: ev.status,\n id: ev.id,\n progress: ev.progress,\n });\n if (line !== null) console.log(line);\n });\n\n // Transition ribbon to bootstrap phase when a container starts creating.\n let bootstrapStarted = false;\n orch.on('containerState', (event: unknown) => {\n const ev = event as { name?: string; state?: string; detail?: string };\n if (\n !bootstrapStarted &&\n (ev.state === 'creating' || ev.state === 'starting')\n ) {\n bootstrapStarted = true;\n ribbon.start('bootstrap');\n }\n });\n\n // Stop the pull-phase spinner before the pre-pull narration below. The\n // spinner rewrites its line in place (cursor-up + clear), which smears into\n // duplicated \"Pulling apex image…\" lines when the per-image/layer progress\n // prints interleaved underneath it. The narration IS the progress indicator\n // for this phase; the spinner resumes cleanly for the quiet bootstrap wait.\n ribbon.stop();\n\n // ── Cold-pull pre-warm (Epic 49 Followup D) ───────────────────────────\n // Compose's `up -d` with inheritStdio=true is essentially silent on a\n // cold image cache — operators experience a 5-minute black hole. We\n // pre-pull apex images via dockerode (which emits pullProgress) so the\n // narrator above can render layer-state transitions to stdout. The\n // subsequent `docker compose up -d` finds the images cached and proceeds\n // without re-pulling.\n //\n // Image list comes from the materialized image-manifest.json. If the\n // manifest is missing (local dev tree, not an npm install) or pullImage\n // is absent (stale stub), we silently skip the pre-pull and let compose's\n // own pull behaviour stand. The \"Apex live at <hostname>\" success message\n // remains the canonical completion signal.\n if (typeof orch.pullImage === 'function') {\n try {\n const apexImages = await collectApexImageRefs(configDir);\n if (apexImages.length > 0) {\n console.log(\n `Pulling ${apexImages.length} apex ${apexImages.length === 1 ? 'image' : 'images'}...`\n );\n let pulled = 0;\n for (const ref of apexImages) {\n pulled++;\n console.log(` [${pulled}/${apexImages.length}] ${ref}`);\n await orch.pullImage(ref);\n }\n } else {\n // No pinned image manifest (e.g. local dev tree). Compose will pull\n // images on demand during `up` — which can be a multi-minute period\n // with little output. Tell the user so the wait isn't a silent void.\n console.log(\n 'No pinned image manifest found — Docker will pull images on demand.'\n );\n console.log(\n 'First start can take several minutes with limited progress output.'\n );\n }\n } catch (pullErr: unknown) {\n // Non-fatal: compose up will retry the pull and surface a real error if\n // it fails permanently. Narrate it calmly rather than as an alarm.\n const detail =\n pullErr instanceof Error ? pullErr.message : String(pullErr);\n console.log(\n `Could not pre-pull images (${detail}). Docker will pull them during ` +\n 'startup — this is normal and may take a few minutes.'\n );\n }\n }\n\n // Step 5: up (always-on services only — empty profile array).\n // Inject env vars that Docker Compose interpolates in townhouse-hs.yml:\n // TOWNHOUSE_HOME — operator's config dir; replaces hardcoded `~/.townhouse`\n // bind-mount sources so a custom --config-dir (or test tmpDir) actually\n // reaches the containers. Docker does NOT expand `~` in bind-mount\n // sources, so the template must use an explicit interpolation variable.\n // TOWNHOUSE_WALLET_PASSWORD — required by townhouse-api service\n // TOWNHOUSE_UID — run townhouse-api as the host user so bind-mounted\n // ~/.townhouse files (rw------- 600) are readable inside the container\n // TOWNHOUSE_DOCKER_GID — host docker socket group (typically root:docker\n // mode 660 on Linux); added as supplementary group so the non-root\n // container user can read/write /var/run/docker.sock for the\n // `pull-image` step of POST /api/nodes. Without this, dockerode calls\n // from townhouse-api fail with `connect EACCES /var/run/docker.sock`.\n let dockerSockGid = 0;\n try {\n dockerSockGid = statSync('/var/run/docker.sock').gid;\n } catch {\n // Socket missing — operator will see a clearer error at compose-up time.\n // Fallback 0 keeps Compose interpolation valid; the container just won't\n // gain extra group access (matches pre-fix behaviour for that case).\n }\n const prevTownhouseHome = process.env['TOWNHOUSE_HOME'];\n const prevWalletPassword = process.env['TOWNHOUSE_WALLET_PASSWORD'];\n const prevTownhouseUid = process.env['TOWNHOUSE_UID'];\n const prevWalletDir = process.env['TOWNHOUSE_WALLET_DIR'];\n const prevDockerGid = process.env['TOWNHOUSE_DOCKER_GID'];\n process.env['TOWNHOUSE_HOME'] = configDir;\n process.env['TOWNHOUSE_WALLET_PASSWORD'] = resolvedPassword;\n process.env['TOWNHOUSE_UID'] = String(process.getuid?.() ?? 1000);\n // Inject the wallet dir as an absolute host path so the townhouse-api\n // container can find the wallet at the same path as config.wallet.encrypted_path.\n process.env['TOWNHOUSE_WALLET_DIR'] = dirname(\n resolve(config.wallet.encrypted_path)\n );\n process.env['TOWNHOUSE_DOCKER_GID'] = String(dockerSockGid);\n\n // Guarantee the bootstrap phase is narrated before the up-to-90s hostname\n // wait. The containerState event above may never fire with a matching state\n // (observed in plain/non-TTY boots: a silent ~20s gap here), so trigger the\n // phase explicitly. The event handler no-ops once bootstrapStarted is set.\n if (!bootstrapStarted) {\n bootstrapStarted = true;\n ribbon.start('bootstrap');\n }\n\n // Retry up to 3×: the ATOR anon SDK (@anyone-protocol/anyone-client@1.1.x)\n // has a hardcoded 60s bootstrap timeout that fires when relay descriptor\n // loading is slow. `downHs` omits --volumes so the keypair is preserved.\n const MAX_ANON_RETRIES = 3;\n try {\n for (let attempt = 1; attempt <= MAX_ANON_RETRIES; attempt++) {\n try {\n await orch.up([]);\n break;\n } catch (err: unknown) {\n if (isAnonBootstrapTimeout(err) && attempt < MAX_ANON_RETRIES) {\n console.error(\n `[townhouse hs up] ATOR bootstrap timed out (attempt ${attempt}/${MAX_ANON_RETRIES}) — retrying...`\n );\n await orch.down().catch(() => undefined);\n continue;\n }\n throw err;\n }\n }\n } finally {\n if (prevTownhouseHome === undefined) {\n delete process.env['TOWNHOUSE_HOME'];\n } else {\n process.env['TOWNHOUSE_HOME'] = prevTownhouseHome;\n }\n if (prevWalletPassword === undefined) {\n delete process.env['TOWNHOUSE_WALLET_PASSWORD'];\n } else {\n process.env['TOWNHOUSE_WALLET_PASSWORD'] = prevWalletPassword;\n }\n if (prevTownhouseUid === undefined) {\n delete process.env['TOWNHOUSE_UID'];\n } else {\n process.env['TOWNHOUSE_UID'] = prevTownhouseUid;\n }\n if (prevWalletDir === undefined) {\n delete process.env['TOWNHOUSE_WALLET_DIR'];\n } else {\n process.env['TOWNHOUSE_WALLET_DIR'] = prevWalletDir;\n }\n if (prevDockerGid === undefined) {\n delete process.env['TOWNHOUSE_DOCKER_GID'];\n } else {\n process.env['TOWNHOUSE_DOCKER_GID'] = prevDockerGid;\n }\n }\n\n // Step 5b: reconcile connector peer state to nodes.yaml (Story 46.1).\n // Runs after orchestrator.up([]) but BEFORE host.json is written and the\n // hostname is printed. Reconciler divergences are non-fatal — the\n // failure is logged to stderr but does not block apex boot.\n const nodesYamlPath = join(configDir, 'nodes.yaml');\n const reconcilerLogPath = join(configDir, 'reconciler.log');\n const reconcilerFactory =\n hsOverrides?.createReconciler ??\n ((nodesPath: string, logPath: string) => {\n const reconcilerAdminClient = new ConnectorAdminClient(\n HS_CONNECTOR_ADMIN_URL,\n 5_000\n );\n return new BootReconciler(reconcilerAdminClient, nodesPath, logPath);\n });\n const reconciler = reconcilerFactory(nodesYamlPath, reconcilerLogPath);\n // Brief retry on cold-boot transient errors — orchestrator.up() returns\n // once Docker accepts the create call, not when the connector inside\n // the container has bound its admin port. A short retry budget keeps\n // cold-boot stderr quiet on the common \"connector still warming\" case\n // while still surfacing genuine connector-down failures via the final\n // non-fatal log below.\n try {\n await reconcileWithBriefRetry(reconciler, 5_000);\n } catch (reconcilerErr: unknown) {\n const detail =\n reconcilerErr instanceof Error\n ? (reconcilerErr.stack ?? reconcilerErr.message)\n : String(reconcilerErr);\n console.error(\n `[townhouse hs up] reconciler error (non-fatal): ${detail}`\n );\n }\n\n // Step 6: fetch published hostname and publishedAt for host.json (AC #6).\n const adminClientFactory2 =\n hsOverrides?.createAdminClient ??\n ((url: string, t: number) => new ConnectorAdminClient(url, t));\n const adminClient = adminClientFactory2(HS_CONNECTOR_ADMIN_URL, 5_000);\n const hsInfo = await adminClient.getHsHostname();\n\n const hostname = hsInfo.hostname ?? '';\n const publishedAt = hsInfo.publishedAt ?? new Date().toISOString();\n\n // Step 7: write host.json atomically (AC #6).\n _writeHostJson(configDir, {\n hostname,\n publishedAt,\n writtenAt: new Date().toISOString(),\n });\n\n // Step 8: ribbon phase 3 + final stdout line (AC #5).\n // hostname from the connector already includes the .anyone suffix.\n // ribbon.start('live', hostname) prints: \"Apex live at <hostname>\" as the FINAL stdout line.\n ribbon.start('live', hostname);\n\n // Story 48.1: foreground Ink TUI when stdout is a TTY. Apex is already live\n // here (host.json written, \"Apex live at …\" printed above); attachDashboard\n // isolates any TUI failure so it is never reported as a boot failure.\n await attachDashboard(hostname);\n } catch (err: unknown) {\n const { exitCode } = renderFailure(err);\n process.exitCode = exitCode;\n } finally {\n ribbon.stop();\n if (walletManager) {\n walletManager.lock();\n }\n }\n}\n\n/** Atomically write ~/.townhouse/host.json (AC #6). */\nfunction _writeHostJson(\n configDir: string,\n data: { hostname: string; publishedAt: string; writtenAt: string }\n): void {\n const hostJsonPath = join(configDir, 'host.json');\n const tmpPath = `${hostJsonPath}.tmp`;\n // hostname from the connector already includes the .anyone suffix (e.g. \"abc123.anyone\").\n const content = JSON.stringify(\n {\n hostname: data.hostname,\n publishedAt: data.publishedAt,\n connectorAdminUrl: HS_CONNECTOR_ADMIN_URL,\n townhouseApiUrl: HS_TOWNHOUSE_API_URL,\n writtenAt: data.writtenAt,\n },\n null,\n 2\n );\n writeFileSync(tmpPath, content, { mode: 0o600, encoding: 'utf-8' });\n renameSync(tmpPath, hostJsonPath);\n}\n\n/**\n * Stop the apex via `townhouse hs down`.\n * Default: preserves the townhouse-hs-anon volume (stable .anyone address).\n * --rotate-keys: removes the volume (new address on next hs up).\n */\nasync function handleHsDown(\n configDir: string,\n config: TownhouseConfig,\n docker: Docker,\n options: {\n rotateKeys?: boolean;\n hsOverrides?: CliHsOverrides;\n }\n): Promise<void> {\n const { rotateKeys, hsOverrides } = options;\n\n // Materialize compose template to get the composePath (idempotent re-write).\n const materialize =\n hsOverrides?.materializeComposeTemplate ?? materializeComposeTemplate;\n const { composePath } = materialize('hs', { townhouseHome: configDir });\n\n // Export every env var the compose template interpolates, so `docker compose\n // down` parses the same YAML that `up` parsed. Compose's `${VAR:-default}`\n // fallbacks for the wallet dir use a literal `~/.townhouse` which Docker\n // doesn't expand — they only work when handleHsDown explicitly sets the\n // var. TOWNHOUSE_WALLET_PASSWORD has a `${...:?...}` mandatory-error\n // fallback (Finding J — fixed in the same PR by switching to `:-`), so set\n // an empty string here to bypass it if Compose still requires it. Mirrors\n // the env-export pattern in handleHsUp. Discovered by Story 46.4 live gate\n // run (Finding I, 2026-05-11; supersedes PR #51's TOWNHOUSE_HOME-only fix).\n const prevTownhouseHome = process.env['TOWNHOUSE_HOME'];\n const prevTownhouseUid = process.env['TOWNHOUSE_UID'];\n const prevWalletDir = process.env['TOWNHOUSE_WALLET_DIR'];\n const prevDockerGid = process.env['TOWNHOUSE_DOCKER_GID'];\n const prevWalletPassword = process.env['TOWNHOUSE_WALLET_PASSWORD'];\n process.env['TOWNHOUSE_HOME'] = configDir;\n process.env['TOWNHOUSE_UID'] = String(process.getuid?.() ?? 1000);\n process.env['TOWNHOUSE_WALLET_DIR'] = dirname(\n resolve(config.wallet.encrypted_path)\n );\n let dockerSockGid = 0;\n try {\n dockerSockGid = statSync('/var/run/docker.sock').gid;\n } catch {\n /* Docker socket missing — fall back to 0; compose down won't actually use it */\n }\n process.env['TOWNHOUSE_DOCKER_GID'] = String(dockerSockGid);\n // Empty string keeps Compose interpolation valid even if the template still\n // has a mandatory-error fallback. The container side checks the password\n // itself; compose-down doesn't actually need it.\n if (prevWalletPassword === undefined) {\n process.env['TOWNHOUSE_WALLET_PASSWORD'] = '';\n }\n const restoreTownhouseHome = (): void => {\n if (prevTownhouseHome === undefined) {\n delete process.env['TOWNHOUSE_HOME'];\n } else {\n process.env['TOWNHOUSE_HOME'] = prevTownhouseHome;\n }\n if (prevTownhouseUid === undefined) {\n delete process.env['TOWNHOUSE_UID'];\n } else {\n process.env['TOWNHOUSE_UID'] = prevTownhouseUid;\n }\n if (prevWalletDir === undefined) {\n delete process.env['TOWNHOUSE_WALLET_DIR'];\n } else {\n process.env['TOWNHOUSE_WALLET_DIR'] = prevWalletDir;\n }\n if (prevDockerGid === undefined) {\n delete process.env['TOWNHOUSE_DOCKER_GID'];\n } else {\n process.env['TOWNHOUSE_DOCKER_GID'] = prevDockerGid;\n }\n if (prevWalletPassword === undefined) {\n delete process.env['TOWNHOUSE_WALLET_PASSWORD'];\n }\n };\n\n if (rotateKeys) {\n // Confirmation prompt when TTY is available.\n if (process.stdin.isTTY) {\n // Read the existing hostname from host.json for the warning message.\n let existingHostname = '(unknown)';\n const hostJsonPath = join(configDir, 'host.json');\n if (existsSync(hostJsonPath)) {\n try {\n const { readFileSync } = await import('node:fs');\n const json = JSON.parse(readFileSync(hostJsonPath, 'utf-8')) as {\n hostname?: string;\n };\n existingHostname = json.hostname ?? existingHostname;\n } catch {\n // best-effort\n }\n }\n // Use readline for the yes/no confirmation prompt.\n const { createInterface } = await import('node:readline');\n const answer = await new Promise<string>((resolve) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n rl.question(\n `WARNING: --rotate-keys will permanently delete your current .anyone address (${existingHostname}). The next 'hs up' will publish a new address. Continue? [y/N] `,\n (ans) => {\n rl.close();\n resolve(ans);\n }\n );\n });\n if (!['y', 'yes'].includes(answer.trim().toLowerCase())) {\n console.log('Cancelled.');\n return;\n }\n }\n\n // Run `docker compose down -v` to remove volumes (including townhouse-hs-anon).\n const runDown = hsOverrides?.runComposeDown ?? _runDockerComposeDown;\n try {\n await runDown(composePath, true);\n } catch (err: unknown) {\n const { exitCode } = renderFailure(err);\n process.exitCode = exitCode;\n restoreTownhouseHome();\n return;\n }\n\n // Delete host.json so the stale hostname doesn't outlive the keypair (AC #9).\n rmSync(join(configDir, 'host.json'), { force: true });\n\n console.log(\n \"Apex stopped. Volumes deleted — your next 'hs up' will publish a NEW .anyone address.\"\n );\n restoreTownhouseHome();\n return;\n }\n\n // Default: preserve volumes (townhouse-hs-anon survives → same hostname next hs up).\n const orchestratorFactory =\n hsOverrides?.createOrchestrator ??\n ((\n d: Docker,\n cfg: TownhouseConfig,\n wm: WalletManager | undefined,\n opts: { profile: 'hs'; composePath: string }\n ) => new DockerOrchestrator(d, cfg, wm, opts));\n\n const orch = orchestratorFactory(docker, config, undefined, {\n profile: 'hs',\n composePath,\n });\n\n try {\n await orch.down();\n } catch (err: unknown) {\n const { exitCode } = renderFailure(err);\n process.exitCode = exitCode;\n restoreTownhouseHome();\n return;\n }\n restoreTownhouseHome();\n\n console.log(\n 'Apex stopped. Volumes preserved — your .anyone address is stable.'\n );\n}\n\n/**\n * Run `docker compose -f <composePath> down [-v]` as a subprocess.\n * Used by handleHsDown's --rotate-keys path (AC #9).\n */\nfunction _runDockerComposeDown(\n composePath: string,\n withVolumes: boolean\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const args = ['compose', '-f', composePath, 'down'];\n if (withVolumes) args.push('-v');\n const child = spawn('docker', args, {\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n child.on('error', reject);\n child.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`docker compose down exited with code ${code}`));\n }\n });\n });\n}\n\n/**\n * Main CLI entry — exported for testability (same pattern as Mill CLI).\n * Accepts optional dockerode instance for dependency injection in tests.\n * The optional `hsOverrides` bag is used by unit tests to stub out Docker,\n * file I/O, and admin-client calls in the `hs up` / `hs down` path.\n */\nconst CHAINS_HELP = `townhouse chains — configure settlement chains (connector chainProviders)\n\nThe connector settles ILP payment claims on these chains. Changes take effect\non the next 'townhouse hs down && townhouse hs up'.\n\nUsage:\n townhouse chains list [--json] [-c <path>]\n townhouse chains add --chain-type <evm|solana|mina> --chain-id <id> [fields] [-c <path>]\n townhouse chains remove <chainId> [-c <path>]\n\nFields by chain type:\n evm: --rpc-url <url> --registry <0x..> --token-address <0x..> --key-id <0x..>\n solana: --rpc-url <url> --program-id <addr> --key-id <id> [--ws-url <url>] [--token-mint <addr>]\n mina: --graphql-url <url> --zkapp <addr> [--key-id <id>]`;\n\ninterface ChainsFlags {\n chainType?: string;\n chainId?: string;\n rpcUrl?: string;\n wsUrl?: string;\n registry?: string;\n tokenAddress?: string;\n tokenMint?: string;\n programId?: string;\n graphqlUrl?: string;\n zkapp?: string;\n keyId?: string;\n}\n\n/** Build a typed ChainProviderEntry from CLI flags. Throws on missing fields. */\nfunction buildChainProviderFromFlags(f: ChainsFlags): ChainProviderEntry {\n const { chainType, chainId } = f;\n if (chainType !== 'evm' && chainType !== 'solana' && chainType !== 'mina') {\n throw new Error('--chain-type must be one of: evm, solana, mina');\n }\n if (!chainId) throw new Error('--chain-id is required');\n\n const require = (flag: string, val: string | undefined): string => {\n if (!val) throw new Error(`${flag} is required for ${chainType} chains`);\n return val;\n };\n\n if (chainType === 'evm') {\n return {\n chainType: 'evm',\n chainId,\n rpcUrl: require('--rpc-url', f.rpcUrl),\n registryAddress: require('--registry', f.registry),\n tokenAddress: require('--token-address', f.tokenAddress),\n keyId: require('--key-id', f.keyId),\n };\n }\n if (chainType === 'solana') {\n return {\n chainType: 'solana',\n chainId,\n rpcUrl: require('--rpc-url', f.rpcUrl),\n ...(f.wsUrl ? { wsUrl: f.wsUrl } : {}),\n programId: require('--program-id', f.programId),\n ...(f.tokenMint ? { tokenMint: f.tokenMint } : {}),\n keyId: require('--key-id', f.keyId),\n };\n }\n // mina\n return {\n chainType: 'mina',\n chainId,\n graphqlUrl: require('--graphql-url', f.graphqlUrl),\n zkAppAddress: require('--zkapp', f.zkapp),\n ...(f.keyId ? { keyId: f.keyId } : {}),\n };\n}\n\n/**\n * `townhouse chains <list|add|remove>` — edit the connector settlement chains\n * (config.chainProviders) for EVM / Solana / Mina without hand-editing YAML.\n */\nasync function handleChains(\n action: string | undefined,\n chainIdArg: string | undefined,\n flags: ChainsFlags,\n configPath: string,\n jsonMode: boolean\n): Promise<void> {\n if (!action) {\n console.log(CHAINS_HELP);\n throw new CliHelpRequested();\n }\n\n const config = loadConfig(configPath);\n const providers: ChainProviderEntry[] = config.chainProviders ?? [];\n\n switch (action) {\n case 'list': {\n if (jsonMode) {\n console.log(JSON.stringify(providers, null, 2));\n return;\n }\n if (providers.length === 0) {\n console.log(\n 'No settlement chains configured — the connector uses a built-in dev-Anvil EVM placeholder.'\n );\n console.log(\n 'Add one with: townhouse chains add --chain-type evm --chain-id evm:base:8453 ...'\n );\n return;\n }\n console.log('Configured settlement chains:');\n for (const p of providers) {\n console.log(` ${p.chainType.padEnd(6)} ${p.chainId}`);\n }\n return;\n }\n case 'add': {\n let entry: ChainProviderEntry;\n try {\n entry = buildChainProviderFromFlags(flags);\n } catch (err: unknown) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n return;\n }\n // Idempotent upsert: replace any existing entry with the same chainId.\n const next = providers.filter((p) => p.chainId !== entry.chainId);\n next.push(entry);\n try {\n saveConfig(configPath, { ...config, chainProviders: next });\n } catch (err: unknown) {\n console.error(\n `Invalid chain config: ${err instanceof Error ? err.message : String(err)}`\n );\n process.exitCode = 1;\n return;\n }\n console.log(\n `Added ${entry.chainType} settlement chain '${entry.chainId}'.`\n );\n console.log('Apply with: townhouse hs down && townhouse hs up');\n return;\n }\n case 'remove': {\n if (!chainIdArg) {\n console.error('Usage: townhouse chains remove <chainId>');\n process.exitCode = 1;\n return;\n }\n const next = providers.filter((p) => p.chainId !== chainIdArg);\n if (next.length === providers.length) {\n console.error(\n `No settlement chain with chainId '${chainIdArg}' found.`\n );\n process.exitCode = 1;\n return;\n }\n saveConfig(configPath, {\n ...config,\n chainProviders: next.length > 0 ? next : undefined,\n });\n console.log(`Removed settlement chain '${chainIdArg}'.`);\n console.log('Apply with: townhouse hs down && townhouse hs up');\n return;\n }\n default: {\n // eslint-disable-next-line no-control-regex\n const safe = action.replace(/[\\x00-\\x1f\\x7f]/g, '');\n console.error(`Unknown chains subcommand: ${safe}`);\n console.log(CHAINS_HELP);\n process.exitCode = 1;\n }\n }\n}\n\nexport async function main(\n argv: string[],\n dockerInstance?: Docker,\n browserOpener?: BrowserOpener,\n hsOverrides?: CliHsOverrides,\n nodeCommandOverrides?: CliNodeCommandOverrides\n): Promise<void> {\n const { values, positionals } = parseArgs({\n args: argv,\n options: {\n help: { type: 'boolean' },\n force: { type: 'boolean' },\n config: { type: 'string', short: 'c' },\n 'config-dir': { type: 'string' },\n town: { type: 'boolean' },\n mill: { type: 'boolean' },\n dvm: { type: 'boolean' },\n password: { type: 'string' },\n 'dry-run': { type: 'boolean' },\n 'no-browser': { type: 'boolean' },\n port: { type: 'string' },\n preset: { type: 'string' },\n network: { type: 'string' },\n 'evm-url': { type: 'string' },\n 'sol-url': { type: 'string' },\n yes: { type: 'boolean' },\n 'rotate-keys': { type: 'boolean' },\n 'skip-preflight': { type: 'boolean' },\n json: { type: 'boolean' },\n 'json-compact': { type: 'boolean' },\n lines: { type: 'string' },\n follow: { type: 'boolean', short: 'f' },\n units: { type: 'string' },\n rate: { type: 'string' },\n // credits buy / credits balance (epic-49, Phase 2)\n token: { type: 'string' },\n amount: { type: 'string' },\n 'fee-multiplier': { type: 'string' },\n 'quote-only': { type: 'boolean' },\n 'credit-destination': { type: 'string' },\n // wallet show / wallet seed (epic-49, Phase 3)\n hex: { type: 'boolean' },\n paths: { type: 'boolean' },\n confirm: { type: 'boolean' },\n // chains add (multi-chain settlement config)\n 'chain-type': { type: 'string' },\n 'chain-id': { type: 'string' },\n 'rpc-url': { type: 'string' },\n 'ws-url': { type: 'string' },\n registry: { type: 'string' },\n 'token-address': { type: 'string' },\n 'token-mint': { type: 'string' },\n 'program-id': { type: 'string' },\n 'graphql-url': { type: 'string' },\n zkapp: { type: 'string' },\n 'key-id': { type: 'string' },\n },\n strict: false,\n allowPositionals: true,\n });\n\n const command = positionals[0];\n\n // Handle `townhouse node <verb> --help` before the global --help check so\n // node sub-help takes priority over the global HELP_TEXT.\n if (command === 'node' && values.help) {\n const action = positionals[1];\n const subHelp =\n action === 'add'\n ? NODE_ADD_HELP\n : action === 'remove'\n ? NODE_REMOVE_HELP\n : action === 'list'\n ? NODE_LIST_HELP\n : NODE_HELP;\n console.log(subHelp);\n throw new CliHelpRequested();\n }\n\n if (values.help) {\n console.log(HELP_TEXT);\n throw new CliHelpRequested();\n }\n\n if (!command) {\n console.log(HELP_TEXT);\n throw new CliHelpRequested();\n }\n\n switch (command) {\n case 'setup': {\n const portStr = values['port'] as string | undefined;\n // Reject trailing junk like \"9400foo\" (parseInt would silently accept).\n const port = portStr ? Number(portStr) : 9400;\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n console.error('--port must be an integer between 1 and 65535');\n process.exitCode = 1;\n break;\n }\n await handleSetup(\n values['config-dir'] as string | undefined,\n port,\n values['no-browser'] === true,\n dockerInstance,\n browserOpener\n );\n break;\n }\n case 'init': {\n const presetVal = values.preset as string | undefined;\n if (presetVal !== undefined && presetVal !== 'demo') {\n console.error(`Unknown preset: ${presetVal}. Supported: demo`);\n process.exitCode = 1;\n break;\n }\n const networkVal = values.network as string | undefined;\n if (\n networkVal !== undefined &&\n !['mainnet', 'testnet', 'devnet', 'custom'].includes(networkVal)\n ) {\n console.error(\n `Unknown network: ${networkVal}. Supported: mainnet, testnet, devnet, custom`\n );\n process.exitCode = 1;\n break;\n }\n const evmUrl =\n (values['evm-url'] as string | undefined) ?? process.env['EVM_URL'];\n const solUrl =\n (values['sol-url'] as string | undefined) ?? process.env['SOL_URL'];\n const endpoints =\n evmUrl || solUrl\n ? {\n ...(evmUrl ? { evmUrl } : {}),\n ...(solUrl ? { solUrl } : {}),\n }\n : undefined;\n await handleInit(\n values.force === true,\n values['config-dir'] as string | undefined,\n values.password as string | undefined,\n presetVal,\n values.yes === true,\n networkVal as NetworkMode | undefined,\n endpoints\n );\n break;\n }\n case 'wallet': {\n const subCommand = positionals[1];\n if (subCommand === 'show') {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n await handleWalletShow(config, values.password as string | undefined, {\n json: values.json === true,\n hex: values.hex === true,\n paths: values.paths === true,\n });\n } else if (subCommand === 'seed') {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n await handleWalletSeed(\n config,\n values.password as string | undefined,\n values.confirm === true\n );\n } else {\n console.error(\n 'Usage:\\n' +\n ' townhouse wallet show [--json] [--hex] [--paths] [-c <path>] [--password <pw>]\\n' +\n ' townhouse wallet seed --confirm [-c <path>] [--password <pw>]'\n );\n process.exitCode = 1;\n }\n break;\n }\n case 'credits': {\n const subCommand = positionals[1];\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n if (subCommand === 'buy') {\n await handleCreditsBuy(config, values as Record<string, unknown>);\n } else if (subCommand === 'balance') {\n await handleCreditsBalance(config, values as Record<string, unknown>);\n } else {\n console.error(\n 'Usage:\\n' +\n ' townhouse credits buy --token <id> --amount <decimal> [--fee-multiplier <n>] [--quote-only] [--yes] [-c <path>] [--password <pw>]\\n' +\n ' townhouse credits balance --token <id> [-c <path>] [--password <pw>]'\n );\n process.exitCode = 1;\n }\n break;\n }\n case 'status': {\n const configPath = (values['config'] as string) ?? DEFAULT_CONFIG_PATH;\n const rawUnits = (values['units'] as string | undefined) ?? 'usdc';\n if (rawUnits !== 'usdc' && rawUnits !== 'sats') {\n console.error(`--units must be 'usdc' or 'sats'`);\n process.exitCode = 1;\n break;\n }\n let satsPerUsdc: number | undefined;\n if (rawUnits === 'sats') {\n const r = resolveSatsRate(\n values as Record<string, unknown>,\n process.env\n );\n if ('error' in r) {\n console.error(r.error);\n process.exitCode = 1;\n } else {\n satsPerUsdc = r.rate;\n }\n }\n const units = rawUnits as 'usdc' | 'sats';\n await handleStatus(\n dockerInstance ?? new Docker(),\n loadConfig(configPath),\n { units, satsPerUsdc, configPath }\n );\n break;\n }\n case 'up': {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n const docker = dockerInstance ?? new Docker();\n const profiles = resolveProfiles(values, config);\n await handleUp(\n configPath,\n config,\n profiles,\n docker,\n values.password as string | undefined,\n values['dry-run'] === true\n );\n break;\n }\n case 'down': {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n const docker = dockerInstance ?? new Docker();\n await handleDown(config, docker);\n break;\n }\n case 'channels':\n case 'metrics':\n case 'logs':\n case 'peer':\n case 'health': {\n await dispatchDrillCommand(command, {\n adminUrl: HS_CONNECTOR_ADMIN_URL,\n apiUrl: HS_TOWNHOUSE_API_URL,\n values: values as Record<string, unknown>,\n positionals,\n docker: dockerInstance,\n });\n break;\n }\n case 'hs': {\n const action = positionals[1];\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const config = loadConfig(configPath);\n const docker = dockerInstance ?? new Docker();\n const configDir = dirname(configPath);\n if (action === 'up') {\n await handleHsUp(configPath, configDir, config, docker, {\n password: values.password as string | undefined,\n force: values.force === true,\n skipPreflight: values['skip-preflight'] === true,\n hsOverrides,\n });\n } else if (action === 'down') {\n await handleHsDown(configDir, config, docker, {\n rotateKeys: values['rotate-keys'] === true,\n hsOverrides,\n });\n } else {\n console.error(\n 'Usage: townhouse hs <up|down> [--rotate-keys] [--password <pw>] [-c <path>]'\n );\n process.exitCode = 1;\n }\n break;\n }\n case 'node': {\n const action = positionals[1];\n const jsonMode = values.json === true;\n const yesMode = values.yes === true;\n const nodeApiUrl = nodeCommandOverrides?.apiUrl ?? HS_TOWNHOUSE_API_URL;\n\n if (!action) {\n console.log(NODE_HELP);\n throw new CliHelpRequested();\n }\n\n switch (action) {\n case 'add': {\n const typeArg = positionals[2] ?? 'town';\n await handleNodeAdd(typeArg, {\n json: jsonMode,\n apiUrl: nodeApiUrl,\n fetch: nodeCommandOverrides?.fetch,\n confirm: nodeCommandOverrides?.confirm,\n });\n break;\n }\n case 'remove': {\n const idArg = positionals[2] ?? '';\n await handleNodeRemove(idArg, {\n yes: yesMode,\n json: jsonMode,\n apiUrl: nodeApiUrl,\n fetch: nodeCommandOverrides?.fetch,\n confirm: nodeCommandOverrides?.confirm,\n });\n break;\n }\n case 'list': {\n await handleNodeList({\n json: jsonMode,\n apiUrl: nodeApiUrl,\n fetch: nodeCommandOverrides?.fetch,\n });\n break;\n }\n default: {\n // Sanitize to prevent log injection\n // eslint-disable-next-line no-control-regex\n const safeAction = action.replace(/[\\x00-\\x1f\\x7f]/g, '');\n console.error(`Unknown node subcommand: ${safeAction}`);\n console.log(NODE_HELP);\n process.exitCode = 1;\n }\n }\n break;\n }\n case 'chains': {\n const configPath = (values.config as string) ?? DEFAULT_CONFIG_PATH;\n const action = positionals[1];\n const chainIdArg = positionals[2];\n const flags: ChainsFlags = {\n chainType: values['chain-type'] as string | undefined,\n chainId: values['chain-id'] as string | undefined,\n rpcUrl: values['rpc-url'] as string | undefined,\n wsUrl: values['ws-url'] as string | undefined,\n registry: values['registry'] as string | undefined,\n tokenAddress: values['token-address'] as string | undefined,\n tokenMint: values['token-mint'] as string | undefined,\n programId: values['program-id'] as string | undefined,\n graphqlUrl: values['graphql-url'] as string | undefined,\n zkapp: values['zkapp'] as string | undefined,\n keyId: values['key-id'] as string | undefined,\n };\n await handleChains(\n action,\n chainIdArg,\n flags,\n configPath,\n values.json === true\n );\n break;\n }\n default: {\n // Sanitize user input to prevent log injection (CWE-117)\n // eslint-disable-next-line no-control-regex\n const sanitized = command.replace(/[\\x00-\\x1f\\x7f]/g, '');\n console.error(`Unknown command: ${sanitized}`);\n console.log(HELP_TEXT);\n process.exitCode = 1;\n }\n }\n}\n\n// Self-invoke when run as entrypoint.\n// process.argv[1] can be a symlink (e.g. node_modules/.bin/townhouse created by\n// npm/npx), while import.meta.url is the realpath of dist/cli.js. Comparing them\n// directly makes the guard false under npx/installed-bin, so main() never runs and\n// every command silently no-ops. Resolve symlinks before comparing.\nconst invokedFile = process.argv[1];\nlet invokedDirectly = false;\nif (typeof invokedFile === 'string') {\n try {\n invokedDirectly =\n import.meta.url === pathToFileURL(realpathSync(invokedFile)).href;\n } catch {\n invokedDirectly = import.meta.url === pathToFileURL(invokedFile).href;\n }\n}\n\nif (invokedDirectly) {\n main(process.argv.slice(2)).catch((error: unknown) => {\n if (error instanceof CliHelpRequested) {\n process.exit(0);\n }\n console.error('[Townhouse] Error:', error);\n process.exit(1);\n });\n}\n","/**\n * Cross-platform browser opener for the wizard CLI command.\n * Uses platform-native launchers; errors are non-fatal.\n */\n\nimport { spawn } from 'node:child_process';\n\nexport interface BrowserOpener {\n open(url: string): Promise<void>;\n}\n\nexport class RealBrowserOpener implements BrowserOpener {\n async open(url: string): Promise<void> {\n let cmd: string;\n let args: string[];\n\n switch (process.platform) {\n case 'darwin':\n cmd = 'open';\n args = [url];\n break;\n case 'win32':\n cmd = 'cmd';\n args = ['/c', 'start', '', url];\n break;\n default:\n cmd = 'xdg-open';\n args = [url];\n break;\n }\n\n return new Promise<void>((resolve) => {\n let settled = false;\n const settle = () => {\n if (settled) return;\n settled = true;\n resolve();\n };\n\n try {\n const child = spawn(cmd, args, {\n stdio: ['ignore', 'ignore', 'ignore'],\n detached: true,\n });\n\n // ENOENT (e.g. xdg-open not on PATH on a minimal container/WSL2 env)\n // surfaces asynchronously via the 'error' event, NOT a synchronous\n // throw from spawn. Subscribe so we don't crash with an unhandled\n // 'error' event and so the user gets a hint about why no browser opened.\n child.once('error', (err: Error) => {\n console.warn(\n `[Townhouse] Could not open browser via ${cmd}: ${err.message}`\n );\n settle();\n });\n child.once('spawn', () => {\n child.unref();\n settle();\n });\n } catch (err: unknown) {\n // Synchronous spawn error path (rare)\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[Townhouse] Could not open browser: ${msg}`);\n settle();\n }\n });\n }\n}\n\nexport class NoopBrowserOpener implements BrowserOpener {\n public readonly calls: string[] = [];\n\n async open(url: string): Promise<void> {\n this.calls.push(url);\n }\n}\n","/**\n * UX-DR4: Three-phase onboarding ribbon for `townhouse hs up` (Story 45.4).\n *\n * Phases: pull → bootstrap → live\n * TTY + unicode: in-place ANSI cursor-up + line-clear rewrite.\n * Fallback (non-TTY, NO_COLOR, CI, dumb terminal): plain lines + ASCII spinner.\n */\n\nconst PHASES = {\n pull: 'Pulling apex image…',\n bootstrap: 'Bootstrapping hidden service (this takes 30–90s)…',\n} as const;\n\nconst SPINNER_FRAMES = ['|', '/', '-', '\\\\'];\n\nfunction isTty(): boolean {\n return process.stdout.isTTY === true;\n}\n\nfunction supportsUnicode(): boolean {\n const term = process.env['TERM'] ?? '';\n if (term === 'dumb') return false;\n if (/xterm|screen|tmux/i.test(term)) return true;\n if (process.env['COLORTERM'] !== undefined) return true;\n return false;\n}\n\nfunction isAnimationDisabled(): boolean {\n if (process.env['NO_COLOR'] !== undefined && process.env['NO_COLOR'] !== '')\n return true;\n if (process.env['CI'] === 'true') return true;\n return false;\n}\n\nfunction useAnsiRewrite(): boolean {\n return isTty() && supportsUnicode() && !isAnimationDisabled();\n}\n\nexport type RibbonPhase = 'pull' | 'bootstrap' | 'live';\n\nexport class OnboardingRibbon {\n private currentPhase: RibbonPhase | null = null;\n private spinnerTimer: ReturnType<typeof setInterval> | null = null;\n private spinnerFrame = 0;\n private hasWrittenLine = false;\n\n start(phase: RibbonPhase, detail?: string): void {\n this._stopSpinner();\n\n if (phase === 'live') {\n const line = detail ? `Apex live at ${detail}` : 'Apex live.';\n this._writeLine(line);\n this.currentPhase = 'live';\n return;\n }\n\n const text = PHASES[phase];\n\n if (useAnsiRewrite() && this.hasWrittenLine) {\n // Move cursor up one line and clear it before writing the new phase.\n process.stdout.write('\\x1b[1A\\x1b[2K');\n }\n\n if (isAnimationDisabled() || !isTty()) {\n this._writeLine(text);\n } else {\n // Start a spinner for the bootstrap/pull phases.\n this._writeLine(`${text} ${SPINNER_FRAMES[0]}`);\n this.spinnerFrame = 1;\n this.spinnerTimer = setInterval(() => {\n const idx = this.spinnerFrame % SPINNER_FRAMES.length;\n const frame = SPINNER_FRAMES[idx] ?? '|';\n this.spinnerFrame++;\n if (useAnsiRewrite()) {\n process.stdout.write('\\x1b[1A\\x1b[2K');\n process.stdout.write(`${text} ${frame}\\n`);\n } else {\n process.stdout.write(`${text} ${frame}\\n`);\n }\n }, 100);\n }\n\n this.currentPhase = phase;\n }\n\n stop(): void {\n this._stopSpinner();\n }\n\n private _stopSpinner(): void {\n if (this.spinnerTimer !== null) {\n clearInterval(this.spinnerTimer);\n this.spinnerTimer = null;\n }\n }\n\n private _writeLine(text: string): void {\n process.stdout.write(`${text}\\n`);\n this.hasWrittenLine = true;\n }\n}\n","/**\n * Sally's failure-state copy library — apex-side error classes (UX-DR5 partial, Story 45.4).\n * Covers: anon-timeout, anon-disabled, image-pull-failure, port-collision,\n * missing-docker-sock, and generic fallback.\n */\n\nimport { OrchestratorError } from '../docker/orchestrator.js';\n\ninterface FailureCopyEntry {\n headline: string;\n explanation: string;\n nextStep: string;\n}\n\nconst FAILURE_COPY: Readonly<Record<string, FailureCopyEntry>> = {\n 'anon-timeout': {\n headline: \"Hidden service didn't publish in time.\",\n explanation:\n 'The .anyone descriptor did not publish within the allotted time.',\n nextStep: 'Re-run with DEBUG=townhouse:* for verbose anon logs.',\n },\n 'anon-disabled': {\n headline: 'Connector is anon-disabled.',\n explanation: 'The connector config has anon.enabled: false.',\n nextStep: 'Edit ~/.townhouse/connector.yaml and set anon.enabled: true.',\n },\n 'image-pull-failure': {\n headline: 'Image pull failed.',\n explanation: 'Docker could not pull the required townhouse images.',\n nextStep: 'Check your network and try again.',\n },\n 'port-collision': {\n headline: 'Port already in use.',\n explanation: 'A required host port is already bound by another process.',\n nextStep:\n 'Stop the conflicting service or override the port via --connector-admin-port.',\n },\n 'missing-docker-sock': {\n headline: 'Docker daemon unreachable.',\n explanation:\n 'The Docker socket is not accessible or Docker is not running.',\n nextStep: 'Start Docker and re-run `townhouse hs up`.',\n },\n generic: {\n headline: 'Apex boot failed.',\n explanation: '',\n nextStep: 'Run with DEBUG=townhouse:* for verbose logs.',\n },\n};\n\nfunction supportsUnicode(): boolean {\n const term = process.env['TERM'] ?? '';\n if (term === 'dumb') return false;\n if (/xterm|screen|tmux/i.test(term)) return true;\n if (process.env['COLORTERM'] !== undefined) return true;\n return false;\n}\n\nexport function useAscii(): boolean {\n if (process.env['NO_COLOR'] !== undefined && process.env['NO_COLOR'] !== '')\n return true;\n return !supportsUnicode();\n}\n\ntype ErrorClass =\n | 'anon-timeout'\n | 'anon-disabled'\n | 'image-pull-failure'\n | 'port-collision'\n | 'missing-docker-sock'\n | 'generic';\n\nfunction classify(error: unknown): { key: ErrorClass; explanation: string } {\n const msg = error instanceof Error ? error.message : String(error);\n const isOrchError = error instanceof OrchestratorError;\n const stderr = isOrchError ? (error.stderr ?? '') : '';\n\n // Anon timeout: orchestrator exhausted the 120s polling budget.\n if (msg.includes('HS hostname publication timeout')) {\n return { key: 'anon-timeout', explanation: msg };\n }\n\n // Anon disabled: the orchestrator caught a 503 early-exit during polling\n // (OrchestratorError wrapping the anon-disabled signal) → treat as anon-timeout\n // so the operator gets actionable next steps (verbose logs to diagnose).\n if (isOrchError && msg.includes('anon-disabled')) {\n return { key: 'anon-timeout', explanation: msg };\n }\n\n // Anon disabled: direct 503 from the idempotency probe (plain Error, not OrchestratorError).\n if (!isOrchError && msg.includes('anon-disabled')) {\n return { key: 'anon-disabled', explanation: msg };\n }\n\n // Image pull failure: Docker couldn't fetch the image.\n if (\n stderr.includes('failed to pull') ||\n stderr.includes('pull access denied') ||\n msg.includes('failed to pull') ||\n msg.includes('pull access denied')\n ) {\n return { key: 'image-pull-failure', explanation: msg };\n }\n\n // Port collision: a required port is already in use.\n if (\n stderr.includes('address already in use') ||\n stderr.includes('port is already allocated') ||\n msg.includes('address already in use') ||\n msg.includes('port is already allocated')\n ) {\n return { key: 'port-collision', explanation: msg };\n }\n\n // Missing Docker: daemon not running or docker CLI not found.\n if (\n stderr.includes('Cannot connect to the Docker daemon') ||\n msg.includes('Cannot connect to the Docker daemon') ||\n msg.includes('docker CLI not found on PATH')\n ) {\n return { key: 'missing-docker-sock', explanation: msg };\n }\n\n return { key: 'generic', explanation: msg };\n}\n\n/**\n * Classify `error` and write Sally's three-line failure copy to stderr.\n * Returns `{ exitCode: 1 }` for the caller to propagate via `process.exitCode`.\n */\nexport function renderFailure(error: unknown): { exitCode: number } {\n const ascii = useAscii();\n const { key, explanation } = classify(error);\n\n const entry = FAILURE_COPY[key];\n if (!entry) {\n const xMark = ascii ? '[X]' : '✕';\n const arrow = ascii ? '->' : '→';\n process.stderr.write(`${xMark} Apex boot failed.\\n`);\n process.stderr.write(` ${explanation}\\n`);\n process.stderr.write(\n ` ${arrow} Run with DEBUG=townhouse:* for verbose logs.\\n`\n );\n return { exitCode: 1 };\n }\n\n const xMark = ascii ? '[X]' : '✕';\n const arrow = ascii ? '->' : '→';\n\n const explanationText = key === 'generic' ? explanation : entry.explanation;\n\n process.stderr.write(`${xMark} ${entry.headline}\\n`);\n process.stderr.write(` ${explanationText}\\n`);\n process.stderr.write(` ${arrow} ${entry.nextStep}\\n`);\n\n return { exitCode: 1 };\n}\n","/**\n * Interactive password prompt using `node:readline` with character masking.\n * Used by `townhouse hs up` when neither --password flag nor\n * TOWNHOUSE_WALLET_PASSWORD env var is set and stdin is a TTY (Story 45.4).\n *\n * No external dependencies — uses only Node built-ins per architecture rules.\n */\n\nimport { createInterface } from 'node:readline';\n\n/**\n * Prompt for a password interactively. Masks each typed character with '*'.\n * The mute trick overrides `_writeToOutput` on the Interface instance so\n * every character echo is replaced with `*`.\n *\n * @returns The entered password string (without trailing newline).\n * @throws Never — on I/O errors the returned promise rejects.\n */\nexport function promptPassword(prompt = 'Wallet password: '): Promise<string> {\n return new Promise((resolve, reject) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n terminal: true,\n });\n\n // Intercept all output from readline and replace with '*' per character.\n // Casting needed: _writeToOutput is a protected internal implementation\n // detail, not in the public Node.js type definitions.\n const iface = rl as unknown as {\n _writeToOutput: (str: string) => void;\n output: NodeJS.WritableStream;\n };\n\n const origWrite = iface._writeToOutput.bind(iface);\n iface._writeToOutput = (str: string) => {\n // Pass through control sequences (cursor movement, newlines) but mask\n // printable characters.\n if (str === '\\r\\n' || str === '\\n' || str === '\\r') {\n // Let the newline through so the cursor advances.\n origWrite(str);\n } else if (/^[\\x20-\\x7e-]/.test(str)) {\n // Printable character — mask with '*'.\n origWrite('*'.repeat(str.length));\n } else {\n // Control sequence — pass through unchanged.\n origWrite(str);\n }\n };\n\n rl.question(prompt, (answer) => {\n // Restore original writer before closing so subsequent console.log\n // calls are not masked.\n iface._writeToOutput = origWrite;\n // Emit a newline so the terminal cursor lands on the next line.\n process.stdout.write('\\n');\n rl.close();\n resolve(answer);\n });\n\n rl.once('error', (err) => {\n iface._writeToOutput = origWrite;\n rl.close();\n reject(err);\n });\n\n rl.once('close', () => {\n // No-op: resolved above or rejected above.\n });\n });\n}\n","/**\n * Port-collision preflight for `townhouse hs up` (Epic 49 Followup B).\n *\n * Detects host-port conflicts BEFORE handing off to Docker, so operators get\n * an actionable error message instead of a cryptic mid-boot EADDRINUSE.\n *\n * Detection strategy (defense in depth):\n * 1. Bind a transient TCP socket to 127.0.0.1:<port> and immediately close.\n * If bind throws EADDRINUSE, the port is occupied. Pure Node, no deps,\n * works on Linux/Mac/WSL — this is the source of truth.\n * 2. If (1) flags a collision, ask Docker for the offending container's\n * name + compose project so the message can name a culprit. Best-effort\n * enrichment — Docker may be unreachable, in which case we still report\n * the port and suggest `lsof` for non-Docker processes.\n */\n\nimport { createServer } from 'node:net';\nimport type { AddressInfo } from 'node:net';\nimport type Docker from 'dockerode';\nimport type { ContainerInfo } from 'dockerode';\n\n/**\n * Canonical HS-mode host ports — sourced from the compose template at\n * `packages/townhouse/compose/townhouse-hs.yml`. If that template is edited\n * to bind a new port (or unbind an existing one), update this list.\n *\n * HS-mode is single-tenant by design (per packages/townhouse/README.md), so\n * these ports cannot be remapped — collisions MUST be cleared before boot.\n *\n * 9401 : connector admin\n * 28090 : townhouse-api Fastify\n * 7100 : town relay WebSocket (profile: town, lazy-provisioned)\n * 3100 : town BLS health (profile: town, lazy-provisioned)\n * 3200 : mill BLS health (profile: mill, lazy-provisioned)\n * 3400 : dvm BLS health (profile: dvm, lazy-provisioned)\n *\n * Profile-gated peer ports are checked too: even though `townhouse hs up`\n * boots only connector + townhouse-api at apex install, the SAME compose\n * file is re-parsed when `townhouse node add <type>` lazy-provisions a peer.\n * Catching all six up-front means `hs up` succeeds AND the operator's\n * subsequent `node add town` won't fail with the same EADDRINUSE error.\n */\nexport const HS_CANONICAL_PORTS: readonly number[] = [\n 9401, 28090, 7100, 3100, 3200, 3400,\n];\n\nexport interface PortCollision {\n /** The host port that is already bound. */\n port: number;\n /** Name of the Docker container holding the port (when known). */\n containerName?: string;\n /** Compose project the container belongs to (when known). */\n composeProject?: string;\n /** Container status string e.g. \"Up 5 hours\" (when known). */\n status?: string;\n}\n\n/**\n * Probe one port: bind a transient TCP server to 127.0.0.1:<port> and close\n * it immediately. Returns true if the bind fails with EADDRINUSE, false if\n * the port is free, throws for other errors (kernel issues, permissions).\n */\nexport async function isPortInUse(port: number): Promise<boolean> {\n return new Promise<boolean>((resolve, reject) => {\n const server = createServer();\n let settled = false;\n\n const finalize = (result: boolean | Error): void => {\n if (settled) return;\n settled = true;\n // Drop listeners so `close` doesn't re-trigger them.\n server.removeAllListeners('error');\n server.removeAllListeners('listening');\n try {\n server.close();\n } catch {\n /* best-effort */\n }\n if (result instanceof Error) reject(result);\n else resolve(result);\n };\n\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n finalize(true);\n } else {\n finalize(err);\n }\n });\n\n server.once('listening', () => {\n // Capture the actual port to confirm the bind succeeded; then close.\n const addr = server.address() as AddressInfo | null;\n void addr; // touched for clarity; not used further\n finalize(false);\n });\n\n try {\n // exclusive:true ensures we don't share a socket with another listener.\n server.listen({ port, host: '127.0.0.1', exclusive: true });\n } catch (err) {\n finalize(err as Error);\n }\n });\n}\n\n/**\n * Walk a dockerode `ContainerInfo.Ports[]` for an entry that maps host port\n * `port` on 127.0.0.1 or 0.0.0.0. Returns the container's display name and\n * compose project label if found, undefined otherwise.\n */\nfunction findDockerCulprit(\n containers: readonly ContainerInfo[],\n port: number\n):\n | Pick<PortCollision, 'containerName' | 'composeProject' | 'status'>\n | undefined {\n for (const c of containers) {\n const ports = c.Ports ?? [];\n for (const p of ports) {\n // PublicPort is the host-side port; IP \"\" or \"0.0.0.0\" or \"127.0.0.1\" all bind to loopback's view.\n if (p.PublicPort === port) {\n // Names come back as [\"/foo\"] — strip the leading slash.\n const rawName = c.Names?.[0] ?? '';\n const name = rawName.startsWith('/') ? rawName.slice(1) : rawName;\n const project = c.Labels?.['com.docker.compose.project'];\n return {\n containerName: name || undefined,\n composeProject: project,\n status: c.Status,\n };\n }\n }\n }\n return undefined;\n}\n\n/**\n * Preflight check for `townhouse hs up`. Probes each canonical HS port for\n * a collision and (if Docker is reachable) enriches each collision with the\n * offending container's name + compose project.\n *\n * Pure logic: never throws on Docker failures, never calls process.exit, never\n * writes to stdout/stderr. Returns `[]` when all ports are free.\n *\n * @param docker - Dockerode instance for enrichment. If undefined or\n * unreachable, collisions are still reported (port-only).\n * @param ports - List of ports to probe. Defaults to HS_CANONICAL_PORTS.\n */\nexport async function checkHsPortCollisions(\n docker?: Pick<Docker, 'listContainers'>,\n ports: readonly number[] = HS_CANONICAL_PORTS\n): Promise<PortCollision[]> {\n // 1) Socket-bind probe in parallel. Fast — sub-millisecond per port.\n const probes = await Promise.all(\n ports.map(async (port) => {\n try {\n const inUse = await isPortInUse(port);\n return { port, inUse, probeError: undefined as Error | undefined };\n } catch (err) {\n // Unexpected bind error (EACCES on privileged port, EMFILE, etc.).\n // Treat as \"unknown\" — surface as a collision so the operator\n // investigates rather than as a silent pass.\n return {\n port,\n inUse: true,\n probeError: err instanceof Error ? err : new Error(String(err)),\n };\n }\n })\n );\n\n const taken = probes.filter((p) => p.inUse);\n if (taken.length === 0) return [];\n\n // 2) Enrich with Docker info (best effort).\n let containers: readonly ContainerInfo[] = [];\n if (docker) {\n try {\n containers = await docker.listContainers({ all: false });\n } catch {\n // Docker unreachable or daemon down — fall through with empty list.\n containers = [];\n }\n }\n\n return taken.map((t) => {\n const culprit = findDockerCulprit(containers, t.port);\n return {\n port: t.port,\n ...(culprit ?? {}),\n };\n });\n}\n\n/**\n * Format port collisions into a multi-line operator-facing error message.\n * Designed to be written to stderr; ends with a trailing newline.\n *\n * Shape (matches the spec):\n *\n * townhouse hs up: cannot start — host ports already in use:\n *\n * 127.0.0.1:9401 in use by container 'townhouse-hs-connector'\n * (compose project 'compose', Up 5 hours)\n * 127.0.0.1:3100 port in use (no Docker container found — try `sudo lsof -iTCP:3100 -sTCP:LISTEN`)\n *\n * The HS template needs canonical ports — it cannot remap.\n * Stop the conflicting project to free them:\n *\n * docker compose -p <project> down\n *\n * Or, if the conflicting process is NOT a townhouse stack, identify it with:\n *\n * sudo lsof -iTCP:<port> -sTCP:LISTEN\n *\n * Re-run with --skip-preflight to bypass this check.\n */\nexport function formatCollisionMessage(\n collisions: readonly PortCollision[]\n): string {\n if (collisions.length === 0) return '';\n\n const lines: string[] = [];\n lines.push('townhouse hs up: cannot start — host ports already in use:');\n lines.push('');\n\n for (const c of collisions) {\n const portLabel = `127.0.0.1:${c.port}`.padEnd(18);\n if (c.containerName) {\n lines.push(` ${portLabel}in use by container '${c.containerName}'`);\n const project = c.composeProject ?? '<no compose project>';\n const status = c.status ? `, ${c.status}` : '';\n lines.push(` ${' '.repeat(18)}(compose project '${project}'${status})`);\n } else {\n lines.push(\n ` ${portLabel}port in use (no Docker container found — try \\`sudo lsof -iTCP:${c.port} -sTCP:LISTEN\\`)`\n );\n }\n }\n\n lines.push('');\n lines.push('The HS template needs canonical ports — it cannot remap.');\n\n // Suggest a `docker compose -p <project> down` for the most-common project\n // (typically `compose` or `townhouse-hs`). Dedupe to avoid spamming.\n const projects = new Set<string>();\n for (const c of collisions) {\n if (c.composeProject) projects.add(c.composeProject);\n }\n if (projects.size > 0) {\n lines.push('Stop the conflicting project to free them:');\n lines.push('');\n for (const project of projects) {\n lines.push(` docker compose -p ${project} down`);\n }\n lines.push('');\n lines.push(\n 'Or, if the conflicting process is NOT a townhouse stack, identify it with:'\n );\n } else {\n lines.push('Identify the conflicting processes with:');\n }\n\n lines.push('');\n // Pick the first collision's port for the example lsof command — keeps the\n // message concrete rather than dropping `<port>` placeholder text.\n const examplePort = collisions[0]?.port ?? 9401;\n lines.push(` sudo lsof -iTCP:${examplePort} -sTCP:LISTEN`);\n lines.push('');\n lines.push('Re-run with --skip-preflight to bypass this check.');\n\n return lines.join('\\n') + '\\n';\n}\n","/**\n * Pull-progress narrator (Epic 49 Followup D).\n *\n * Dockerode's `pullProgress` events fire many times per second per layer\n * (e.g., a single 80 MB layer emits one event per a few KB downloaded). If we\n * naively forwarded every event to stdout the operator would see ~thousands of\n * lines per image — exactly the noise we are trying to fix.\n *\n * This narrator:\n * 1. Always prints layer-state TRANSITIONS (`Pulling fs layer` →\n * `Downloading` → `Extracting` → `Pull complete`). Operators care about\n * these because each one tells them the pull is still alive.\n * 2. Throttles repeated `Downloading`/`Extracting` updates to at most one\n * line per second PER IMAGE so a slow download still narrates progress\n * without flooding.\n * 3. Always passes through final image-level statuses\n * (`Status: Downloaded newer image for ...`,\n * `Status: Image is up to date for ...`,\n * `Pull complete`, `Already exists`) — the operator's signal that the\n * image is done.\n *\n * It is a pure stateful object — no I/O — so unit tests can drive a sequence\n * of events and assert the rendered lines deterministically.\n */\n\n/** Subset of the dockerode pull-progress event shape we consume. */\nexport interface PullProgressEvent {\n image: string;\n status: string;\n id?: string | undefined;\n progress?: string | undefined;\n}\n\n/** Per-image throttle bookkeeping. */\ninterface ImageState {\n /** Last status string we PRINTED for this image (any layer). */\n lastStatus: string | undefined;\n /** Wall-clock ms of the last printed `Downloading`/`Extracting` line. */\n lastThrottledAtMs: number;\n}\n\n/**\n * Status strings that are noisy (\"happens many times per second per layer\")\n * and therefore subject to the per-image 1Hz throttle.\n *\n * Everything NOT in this set is treated as a transition and printed verbatim.\n */\nconst THROTTLED_STATUSES = new Set(['Downloading', 'Extracting']);\n\nexport interface PullNarratorOptions {\n /** Wall-clock source. Defaults to `Date.now`. Tests inject a fake clock. */\n now?: () => number;\n /** Throttle interval for noisy statuses (ms). Default 1000. */\n throttleMs?: number;\n}\n\n/**\n * Throttling state machine for pull-progress events. Stateful but I/O-free.\n *\n * Usage:\n * ```\n * const narrator = new PullNarrator();\n * orch.on('pullProgress', (event) => {\n * const line = narrator.format(event);\n * if (line !== null) console.log(line);\n * });\n * ```\n */\nexport class PullNarrator {\n private readonly now: () => number;\n private readonly throttleMs: number;\n private readonly perImage = new Map<string, ImageState>();\n\n constructor(options: PullNarratorOptions = {}) {\n this.now = options.now ?? Date.now;\n this.throttleMs = options.throttleMs ?? 1000;\n }\n\n /**\n * Render an event to a stdout-ready line, or `null` if it should be\n * suppressed by the throttle.\n */\n format(event: PullProgressEvent): string | null {\n const status = event.status;\n // Drop empty-status events (dockerode occasionally emits them on errored\n // streams). Nothing useful to narrate.\n if (!status) {\n return null;\n }\n const state = this.perImage.get(event.image) ?? {\n lastStatus: undefined,\n lastThrottledAtMs: 0,\n };\n\n const isThrottled = THROTTLED_STATUSES.has(status);\n const isTransition = state.lastStatus !== status;\n\n if (isThrottled && !isTransition) {\n // Same noisy status as last print — apply the per-image 1Hz throttle.\n const elapsed = this.now() - state.lastThrottledAtMs;\n if (elapsed < this.throttleMs) {\n return null;\n }\n }\n\n // Print this event. Update bookkeeping.\n state.lastStatus = status;\n if (isThrottled) {\n state.lastThrottledAtMs = this.now();\n }\n this.perImage.set(event.image, state);\n\n const progress = event.progress ? ` ${event.progress}` : '';\n return ` [pull] ${event.image}: ${status}${progress}`;\n }\n\n /**\n * Reset the narrator's per-image state. Useful between separate pull\n * batches in the same process.\n */\n reset(): void {\n this.perImage.clear();\n }\n}\n","/**\n * CLI node lifecycle handlers: `townhouse node add` / `remove` / `list`.\n *\n * D4 DECISION (Story 46.3):\n * In HS mode, `nodes.yaml` is the single source of truth for provisioned nodes.\n * `townhouse node add <type>` ignores `config.nodes[type].enabled` entirely —\n * the static flag is the source of truth for the `dev` profile (`townhouse up\n * --town`) only. Epic 46 lazy provisioning and the static dev-profile config\n * are orthogonal: lifecycle-managed nodes answer to nodes.yaml, not the flag.\n */\n\nimport * as readline from 'node:readline';\nimport { renderFailure, useAscii } from './failure-copy.js';\n\n// Default Townhouse host API URL for HS mode.\nconst DEFAULT_HS_API_URL = 'http://127.0.0.1:28090';\n\n// Maps server-side step identifiers to the user-visible stage labels.\nconst STEP_TO_STAGE: Record<string, string> = {\n preflight: 'Preflight',\n 'derive-key': 'Deriving wallet',\n 'pull-image': 'Pulling image',\n 'write-yaml': 'Deriving wallet', // same disk-class bucket from operator POV\n 'write-mill-config': 'Deriving wallet', // same disk-class bucket as write-yaml\n 'start-container': 'Registering with apex',\n healthcheck: 'Registering with apex',\n 'register-peer': 'Live',\n};\n\nconst STAGE_LABELS = [\n 'Pulling image',\n 'Deriving wallet',\n 'Registering with apex',\n 'Live',\n];\n\n/** Sub-help text printed by `townhouse node <verb> --help`. */\nexport const NODE_ADD_HELP = `townhouse node add — Provision a child node\n\nUsage:\n townhouse node add [<type>] [--json] [-c <path>]\n\nArguments:\n <type> Node type to provision: town, mill, dvm (default: town)\n\nFlags:\n --json Machine-readable JSON output\n -c Path to config file\n\nExamples:\n townhouse node add # provision a Town relay (default)\n townhouse node add town # same as above\n townhouse node add mill # earn from chain swaps (5x earnings unlock)\n townhouse node add dvm # add a DVM compute node`;\n\nexport const NODE_REMOVE_HELP = `townhouse node remove — Deprovision a child node\n\nUsage:\n townhouse node remove <id> [--yes] [--json] [-c <path>]\n\nArguments:\n <id> Node ID to remove (use 'townhouse node list' to find IDs)\n\nFlags:\n --yes Skip confirmation prompt (required in non-interactive mode)\n --json Machine-readable JSON output; implies non-interactive (no prompt)\n -c Path to config file`;\n\nexport const NODE_LIST_HELP = `townhouse node list — List provisioned nodes\n\nUsage:\n townhouse node list [--json] [-c <path>]\n\nFlags:\n --json Machine-readable JSON output (emits API response verbatim)\n -c Path to config file`;\n\nexport const NODE_HELP = `townhouse node — Manage child nodes\n\nUsage:\n townhouse node add [<type>] [--json] [-c <path>] Provision a child node (default: town)\n townhouse node remove <id> [--yes] [--json] [-c <path>] Deprovision a child node\n townhouse node list [--json] [-c <path>] List provisioned nodes\n\nRun 'townhouse node <verb> --help' for details on each verb.\n\nTip:\n townhouse node add mill # earn from chain swaps (5x earnings unlock)`;\n\n// ── Shared helpers ─────────────────────────────────────────────────────────────\n\ntype FetchFn = (url: string, init?: RequestInit) => Promise<Response>;\n\nfunction resolveApiUrl(apiUrl?: string): string {\n return apiUrl ?? DEFAULT_HS_API_URL;\n}\n\nfunction formatRelativeTime(iso: string): string {\n const ts = new Date(iso).getTime();\n if (Number.isNaN(ts)) return '—';\n const diffMs = Date.now() - ts;\n if (diffMs < 0) return 'just now';\n const secs = Math.floor(diffMs / 1000);\n if (secs < 60) return `${secs}s ago`;\n const mins = Math.floor(secs / 60);\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.floor(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n return `${Math.floor(hours / 24)}d ago`;\n}\n\nasync function confirmInteractive(question: string): Promise<boolean> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n try {\n const answer = await new Promise<string>((resolve) =>\n rl.question(question, resolve)\n );\n return /^y(es)?$/i.test(answer.trim());\n } finally {\n rl.close();\n }\n}\n\nfunction emitJsonError(obj: Record<string, unknown>, exitCode = 1): void {\n process.stdout.write(JSON.stringify(obj) + '\\n');\n process.exitCode = exitCode;\n}\n\n// ── handleNodeAdd ──────────────────────────────────────────────────────────────\n\nexport interface NodeAddOptions {\n json: boolean;\n apiUrl?: string;\n fetch?: FetchFn;\n confirm?: (question: string) => Promise<boolean>;\n}\n\nexport async function handleNodeAdd(\n type: string,\n options: NodeAddOptions\n): Promise<void> {\n const ascii = useAscii();\n const check = ascii ? '[OK]' : '✓';\n const xMark = ascii ? '[X]' : '✕';\n const dot = ascii ? '.' : '·';\n\n if (type !== 'town' && type !== 'mill' && type !== 'dvm') {\n const msg = `Unknown type: '${type}'. Supported: town, mill, dvm`;\n if (options.json) {\n emitJsonError({ ok: false, error: 'invalid_type', message: msg });\n } else {\n process.stderr.write(`${xMark} ${msg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n\n const url = resolveApiUrl(options.apiUrl);\n const fetchImpl = options.fetch ?? fetch;\n\n if (!options.json) {\n // Print all stages dim while waiting for the blocking POST to return.\n process.stdout.write(\n ` ${STAGE_LABELS.map((s) => `${dot} ${s}`).join(' · ')}\\n`\n );\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 120_000);\n\n let response: Response;\n try {\n response = await fetchImpl(`${url}/api/nodes`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ type }),\n signal: controller.signal,\n });\n } catch (err: unknown) {\n clearTimeout(timer);\n const isAborted = err instanceof Error && err.name === 'AbortError';\n const errMsg = isAborted\n ? 'Request timed out after 120 seconds.'\n : \"townhouse hs up isn't running. Run 'townhouse hs up' first.\";\n if (options.json) {\n emitJsonError({\n ok: false,\n error: isAborted ? 'timeout' : 'econnrefused',\n message: errMsg,\n });\n } else {\n process.stderr.write(`${xMark} ${errMsg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n clearTimeout(timer);\n\n if (response.status === 201) {\n const body = (await response.json().catch(() => ({}))) as {\n id?: string;\n type?: string;\n peerId?: string;\n ilpAddress?: string;\n hsRoute?: string;\n healthCheckUrl?: string;\n };\n if (options.json) {\n process.stdout.write(JSON.stringify({ ok: true, ...body }) + '\\n');\n } else {\n // Re-print all stages green.\n process.stdout.write(\n ` ${STAGE_LABELS.map((s) => `${check} ${s}`).join(' · ')}\\n`\n );\n const addedId = body.id ?? type;\n const addedPeer = body.peerId ? ` (${body.peerId})` : '';\n const addedAddr = body.ilpAddress ? ` at ${body.ilpAddress}` : '';\n process.stdout.write(` Added ${addedId}${addedPeer}${addedAddr}\\n`);\n }\n return;\n }\n\n // Error path — surface the step field from the response body.\n const body = (await response.json().catch(() => ({}))) as {\n step?: string;\n err?: string;\n rollbackError?: string;\n error?: string;\n type?: string;\n existingId?: string;\n };\n\n if (options.json) {\n emitJsonError({ ok: false, ...body });\n return;\n }\n\n // 409 node_type_in_use\n if (response.status === 409 && body.error === 'node_type_in_use') {\n // The desired node already exists — only one node per type is supported\n // (v1 constraint). Point the operator at how to inspect it and how to\n // recreate it, rather than the unhelpful \"use a different type\" (there are\n // only three types, and they likely already have the ones they want).\n const existingId = body.existingId ?? body.type ?? type;\n process.stderr.write(\n `${xMark} A '${body.type}' node already exists (id '${existingId}'). ` +\n `Only one node per type is supported.\\n` +\n ` See your nodes: townhouse node list\\n` +\n ` Recreate it: townhouse node remove ${existingId} && townhouse node add ${body.type}\\n`\n );\n process.exitCode = 1;\n return;\n }\n\n // 409 node_lifecycle_in_flight\n if (response.status === 409 && body.error === 'node_lifecycle_in_flight') {\n process.stderr.write(\n `${xMark} Another node operation is in flight. Try again in a moment.\\n`\n );\n process.exitCode = 1;\n return;\n }\n\n const step = body.step ?? 'unknown';\n const errText = body.err ?? '';\n\n // pull-image → use renderFailure with synthetic error matching classifier.\n if (step === 'pull-image') {\n const syntheticErr = new Error(`failed to pull: ${errText}`);\n renderFailure(syntheticErr);\n } else if (\n step === 'start-container' &&\n (errText.includes('port is already allocated') ||\n errText.includes('Cannot connect to the Docker daemon'))\n ) {\n renderFailure(new Error(errText));\n } else if (step === 'preflight') {\n // Preflight failures are configuration errors — the stack is healthy and\n // must NOT be torn down. Give targeted advice instead of the generic\n // \"hs down && hs up\" which would destroy a working stack for no reason.\n const arrow = ascii ? '->' : '→';\n process.stderr.write(`${xMark} ${errText}\\n`);\n process.stderr.write(\n ` ${arrow} Fix the configuration above, then retry 'townhouse node add'.\\n`\n );\n } else {\n // Generic 3-line failure for all other steps.\n const stageName = STEP_TO_STAGE[step] ?? step;\n const arrow = ascii ? '->' : '→';\n process.stderr.write(\n `${xMark} Step ${step} failed (stage: ${stageName}): ${errText}\\n`\n );\n process.stderr.write(\n ` ${arrow} Run 'townhouse hs down && townhouse hs up' to reset state, then retry.\\n`\n );\n }\n\n if (body.rollbackError) {\n process.stderr.write(` Rollback error: ${body.rollbackError}\\n`);\n }\n\n process.exitCode = 1;\n}\n\n// ── handleNodeRemove ───────────────────────────────────────────────────────────\n\nexport interface NodeRemoveOptions {\n yes: boolean;\n json: boolean;\n apiUrl?: string;\n fetch?: FetchFn;\n confirm?: (question: string) => Promise<boolean>;\n}\n\nconst NODE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;\n\nexport async function handleNodeRemove(\n id: string,\n options: NodeRemoveOptions\n): Promise<void> {\n const ascii = useAscii();\n const check = ascii ? '[OK]' : '✓';\n const xMark = ascii ? '[X]' : '✕';\n\n if (!id) {\n const msg = 'Usage: townhouse node remove <id> [--yes] [--json]';\n if (options.json) {\n emitJsonError({ ok: false, error: 'missing_id', message: msg });\n } else {\n process.stderr.write(`${msg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n\n // Sanitize id against the route pattern before sending (fail fast, no round-trip).\n if (!NODE_ID_PATTERN.test(id)) {\n const msg = `Invalid node id '${id}'. IDs must match ^[a-z][a-z0-9-]*$ (lowercase, no leading hyphens or underscores).`;\n if (options.json) {\n emitJsonError({ ok: false, error: 'invalid_id', message: msg });\n } else {\n process.stderr.write(`${xMark} ${msg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n\n // Confirmation gate.\n const skipPrompt = options.yes || options.json;\n if (!skipPrompt) {\n if (!process.stdin.isTTY) {\n const msg =\n '--yes required when stdin is not a TTY (use --yes for non-interactive removal).';\n process.stderr.write(`${xMark} ${msg}\\n`);\n process.exitCode = 1;\n return;\n }\n const confirmFn = options.confirm ?? confirmInteractive;\n const confirmed = await confirmFn(\n `Remove node '${id}'? This deprovisions the container and deregisters the peer. [y/N] `\n );\n if (!confirmed) {\n process.stdout.write('Cancelled.\\n');\n return;\n }\n }\n\n const url = resolveApiUrl(options.apiUrl);\n const fetchImpl = options.fetch ?? fetch;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 60_000);\n\n let response: Response;\n try {\n response = await fetchImpl(`${url}/api/nodes/${encodeURIComponent(id)}`, {\n method: 'DELETE',\n signal: controller.signal,\n });\n } catch (err: unknown) {\n clearTimeout(timer);\n const isAborted = err instanceof Error && err.name === 'AbortError';\n const errMsg = isAborted\n ? 'Request timed out.'\n : \"townhouse hs up isn't running. Run 'townhouse hs up' first.\";\n if (options.json) {\n emitJsonError({\n ok: false,\n error: isAborted ? 'timeout' : 'econnrefused',\n message: errMsg,\n });\n } else {\n process.stderr.write(`${xMark} ${errMsg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n clearTimeout(timer);\n\n if (response.status === 200) {\n const body = (await response.json().catch(() => ({}))) as {\n id?: string;\n type?: string;\n };\n const removedId = body.id ?? id;\n if (options.json) {\n process.stdout.write(\n JSON.stringify({ ok: true, id: removedId, type: body.type }) + '\\n'\n );\n } else {\n process.stdout.write(`${check} Removed ${removedId}\\n`);\n }\n return;\n }\n\n const body = (await response.json().catch(() => ({}))) as {\n error?: string;\n step?: string;\n err?: string;\n id?: string;\n };\n\n if (options.json) {\n emitJsonError({ ok: false, ...body });\n return;\n }\n\n if (response.status === 404) {\n process.stderr.write(`${xMark} No node with id '${id}'\\n`);\n } else if (\n response.status === 409 &&\n body.error === 'node_lifecycle_in_flight'\n ) {\n process.stderr.write(\n `${xMark} Another node operation is in flight. Try again in a moment.\\n`\n );\n } else {\n const step = body.step ?? 'unknown';\n process.stderr.write(`${xMark} Step ${step} failed: ${body.err ?? ''}\\n`);\n }\n process.exitCode = 1;\n}\n\n// ── handleNodeList ─────────────────────────────────────────────────────────────\n\nexport interface NodeListOptions {\n json: boolean;\n apiUrl?: string;\n fetch?: FetchFn;\n}\n\ninterface NodeEntry {\n id: string;\n type: string;\n peerId: string;\n ilpAddress: string;\n status: 'connected' | 'disconnected' | 'unknown';\n enabledAt: string;\n lastSeenAt: string | null;\n}\n\nexport async function handleNodeList(options: NodeListOptions): Promise<void> {\n const ascii = useAscii();\n const xMark = ascii ? '[X]' : '✕';\n const emDash = ascii ? '-' : '—';\n\n const url = resolveApiUrl(options.apiUrl);\n const fetchImpl = options.fetch ?? fetch;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 30_000);\n\n let response: Response;\n try {\n response = await fetchImpl(`${url}/api/nodes`, {\n method: 'GET',\n signal: controller.signal,\n });\n } catch (err: unknown) {\n clearTimeout(timer);\n const isAborted = err instanceof Error && err.name === 'AbortError';\n const errMsg = isAborted\n ? 'Request timed out.'\n : \"townhouse hs up isn't running. Run 'townhouse hs up' first.\";\n if (options.json) {\n emitJsonError({\n ok: false,\n error: isAborted ? 'timeout' : 'econnrefused',\n message: errMsg,\n });\n } else {\n process.stderr.write(`${xMark} ${errMsg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n clearTimeout(timer);\n\n if (response.status !== 200) {\n const body = (await response.json().catch(() => ({}))) as Record<\n string,\n unknown\n >;\n if (options.json) {\n emitJsonError({ ok: false, ...body });\n } else {\n process.stderr.write(\n `${xMark} Failed to fetch nodes (HTTP ${response.status})\\n`\n );\n process.exitCode = 1;\n }\n return;\n }\n\n const body = (await response.json().catch(() => ({ nodes: [] }))) as {\n nodes?: NodeEntry[];\n };\n const nodes = body.nodes ?? [];\n\n if (options.json) {\n // Emit the API response body verbatim (no `ok` envelope) — consistent with kubectl -o json.\n process.stdout.write(JSON.stringify({ nodes }) + '\\n');\n return;\n }\n\n if (nodes.length === 0) {\n process.stdout.write(\n \"No nodes provisioned. Run 'townhouse node add town' to add one.\\n\"\n );\n return;\n }\n\n // Print 4-column table. Header labels follow AC #5 literal (`peer · type ·\n // status · last claim`). Column widths grow to fit the longest value so long\n // IDs / peer handles are never silently sliced.\n const rows = nodes.map((node) => ({\n peer: node.id,\n type: node.type,\n status: node.status,\n lastClaim:\n node.lastSeenAt !== null ? formatRelativeTime(node.lastSeenAt) : emDash,\n }));\n\n const HEADERS = {\n peer: 'peer',\n type: 'type',\n status: 'status',\n lastClaim: 'last claim',\n };\n const widths = {\n peer: Math.max(HEADERS.peer.length, ...rows.map((r) => r.peer.length)),\n type: Math.max(HEADERS.type.length, ...rows.map((r) => r.type.length)),\n status: Math.max(\n HEADERS.status.length,\n ...rows.map((r) => r.status.length)\n ),\n lastClaim: Math.max(\n HEADERS.lastClaim.length,\n ...rows.map((r) => r.lastClaim.length)\n ),\n };\n\n function pad(s: string, width: number): string {\n return s.length >= width ? s : s + ' '.repeat(width - s.length);\n }\n\n const divider = ascii ? '-' : '─';\n process.stdout.write(\n `${pad(HEADERS.peer, widths.peer)} ${pad(HEADERS.type, widths.type)} ${pad(HEADERS.status, widths.status)} ${HEADERS.lastClaim}\\n`\n );\n process.stdout.write(\n `${divider.repeat(widths.peer)} ${divider.repeat(widths.type)} ${divider.repeat(widths.status)} ${divider.repeat(widths.lastClaim)}\\n`\n );\n\n for (const row of rows) {\n process.stdout.write(\n `${pad(row.peer, widths.peer)} ${pad(row.type, widths.type)} ${pad(row.status, widths.status)} ${row.lastClaim}\\n`\n );\n }\n}\n","/**\n * Drill-subcommand handlers: channels, metrics (moved from cli.ts), logs, peer, health.\n *\n * Boundary: imports ONLY from ../connector/, ../docker/log-tail.js,\n * ../tui/format.js, ../constants.js, dockerode, and node:* stdlib.\n * No imports from ../api/, ../tui/components/, ../earnings/, or ../docker/orchestrator.js.\n */\n\nimport Docker from 'dockerode';\nimport { CONTAINER_PREFIX } from '../constants.js';\nimport { ConnectorAdminClient } from '../connector/admin-client.js';\nimport type {\n ChannelSummary,\n MetricsResponse,\n PeerStatus,\n PeerEarnings,\n} from '../connector/types.js';\nimport {\n tailContainerLogs,\n serviceFromContainerName,\n LOG_SERVICES,\n type LogService,\n} from '../docker/log-tail.js';\nimport { formatRelativeTime } from '../tui/format.js';\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport interface DrillOptions {\n json: boolean;\n jsonCompact: boolean;\n now?: Date;\n adminClient?: ConnectorAdminClient;\n apiUrl?: string;\n fetch?: typeof fetch;\n docker?: Docker;\n}\n\nexport interface ProbeResult {\n source: string;\n status:\n | 'healthy'\n | 'unhealthy'\n | 'unreachable'\n | 'unknown'\n | 'starting'\n | 'n/a'\n | 'degraded';\n error?: string;\n uptime?: number;\n peersConnected?: number;\n totalPeers?: number;\n startedAt?: string;\n version?: string;\n hostname?: string;\n publishedAt?: string;\n message?: string;\n}\n\n// ── Help text constants ────────────────────────────────────────────────────────\n\nexport const CHANNELS_HELP = ` townhouse channels [--json] [-c <path>] Show open payment channels`;\nexport const LOGS_HELP = ` townhouse logs <node-id> [--lines N] [--json] [-c <path>] Tail logs for a node (Ctrl-C to stop)`;\nexport const PEER_HELP = ` townhouse peer <id> [--json] [-c <path>] Show per-peer detail card`;\nexport const HEALTH_HELP = ` townhouse health [--json] [-c <path>] Probe apex/api/nodes/.anyone health`;\n\n// ── Shared helpers ─────────────────────────────────────────────────────────────\n\nfunction truncate16(s: string): string {\n return s.length > 16 ? s.slice(0, 16) + '…' : s;\n}\n\nfunction emitJson(payload: unknown, opts: { compact: boolean }): void {\n process.stdout.write(\n JSON.stringify(payload, null, opts.compact ? 0 : 2) + '\\n'\n );\n}\n\nexport function emitJsonError(\n message: string,\n code: string,\n opts: { compact: boolean }\n): void {\n process.stdout.write(\n JSON.stringify({ error: message, code }, null, opts.compact ? 0 : 2) + '\\n'\n );\n process.exitCode = 1;\n}\n\n// ── handleChannels ──────────────────────────────────────────────────────────────\n\nexport async function handleChannels(\n adminClient: ConnectorAdminClient,\n opts: DrillOptions\n): Promise<void> {\n let channels: ChannelSummary[];\n try {\n channels = await adminClient.getChannels();\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n if (opts.json) {\n emitJsonError(\n `Failed to fetch connector channels: ${msg}`,\n 'unreachable',\n opts\n );\n } else {\n console.error(`Failed to fetch connector channels: ${msg}`);\n process.exitCode = 1;\n }\n return;\n }\n\n if (opts.json) {\n emitJson(channels, opts);\n return;\n }\n\n if (channels.length === 0) {\n console.log('No channels open');\n return;\n }\n\n const now = opts.now ?? new Date();\n const HEADERS = {\n channel: 'CHANNEL',\n peer: 'PEER',\n chain: 'CHAIN',\n status: 'STATUS',\n deposit: 'DEPOSIT',\n lastActivity: 'LAST ACTIVITY',\n };\n\n const rows = channels.map((c) => ({\n channel: truncate16(c.channelId),\n peer: truncate16(c.peerId),\n chain: c.chain,\n status: c.status,\n deposit: c.deposit,\n lastActivity: formatRelativeTime(c.lastActivity, now),\n }));\n\n const widths = {\n channel: Math.max(\n HEADERS.channel.length,\n ...rows.map((r) => r.channel.length)\n ),\n peer: Math.max(HEADERS.peer.length, ...rows.map((r) => r.peer.length)),\n chain: Math.max(HEADERS.chain.length, ...rows.map((r) => r.chain.length)),\n status: Math.max(\n HEADERS.status.length,\n ...rows.map((r) => r.status.length)\n ),\n deposit: Math.max(\n HEADERS.deposit.length,\n ...rows.map((r) => r.deposit.length)\n ),\n lastActivity: Math.max(\n HEADERS.lastActivity.length,\n ...rows.map((r) => r.lastActivity.length)\n ),\n };\n\n const header =\n HEADERS.channel.padEnd(widths.channel) +\n ' ' +\n HEADERS.peer.padEnd(widths.peer) +\n ' ' +\n HEADERS.chain.padEnd(widths.chain) +\n ' ' +\n HEADERS.status.padEnd(widths.status) +\n ' ' +\n HEADERS.deposit.padEnd(widths.deposit) +\n ' ' +\n HEADERS.lastActivity;\n console.log(header);\n console.log('-'.repeat(header.length));\n\n for (const row of rows) {\n console.log(\n row.channel.padEnd(widths.channel) +\n ' ' +\n row.peer.padEnd(widths.peer) +\n ' ' +\n row.chain.padEnd(widths.chain) +\n ' ' +\n row.status.padEnd(widths.status) +\n ' ' +\n row.deposit.padEnd(widths.deposit) +\n ' ' +\n row.lastActivity\n );\n }\n}\n\n// ── handleMetrics (moved from cli.ts) ──────────────────────────────────────────\n\nexport async function handleMetrics(\n adminClient: ConnectorAdminClient,\n opts: DrillOptions\n): Promise<void> {\n try {\n const metrics: MetricsResponse = await adminClient.getMetrics();\n const peers: PeerStatus[] = await adminClient.getPeers();\n\n const peerMetrics = new Map(metrics.peers.map((p) => [p.peerId, p]));\n\n if (opts.json) {\n emitJson(\n {\n aggregate: metrics.aggregate,\n peers: metrics.peers,\n peersDetail: peers,\n uptimeSeconds: metrics.uptimeSeconds,\n timestamp: metrics.timestamp,\n },\n opts\n );\n return;\n }\n\n const now = opts.now ?? new Date();\n\n console.log('Connector Metrics:');\n console.log('------------------');\n console.log(` Packets forwarded: ${metrics.aggregate.packetsForwarded}`);\n console.log(` Packets rejected: ${metrics.aggregate.packetsRejected}`);\n console.log(` Bytes sent: ${metrics.aggregate.bytesSent}`);\n console.log('');\n console.log('Peers:');\n console.log('------');\n if (peers.length === 0) {\n console.log(' No peers connected');\n } else {\n const HEADERS = {\n peer: 'PEER',\n connected: 'STATUS',\n packetsForwarded: 'PACKETS FWD',\n packetsRejected: 'PACKETS REJ',\n bytesSent: 'BYTES SENT',\n lastPacket: 'LAST PACKET',\n };\n\n const rows = peers.map((peer) => {\n const pm = peerMetrics.get(peer.id);\n return {\n peer: peer.id,\n connected: peer.connected ? 'connected' : 'disconnected',\n packetsForwarded: String(pm?.packetsForwarded ?? 0),\n packetsRejected: String(pm?.packetsRejected ?? 0),\n bytesSent: String(pm?.bytesSent ?? 0),\n lastPacket:\n pm?.lastPacketAt != null\n ? formatRelativeTime(pm.lastPacketAt, now)\n : '—',\n };\n });\n\n const widths = {\n peer: Math.max(HEADERS.peer.length, ...rows.map((r) => r.peer.length)),\n connected: Math.max(\n HEADERS.connected.length,\n ...rows.map((r) => r.connected.length)\n ),\n packetsForwarded: Math.max(\n HEADERS.packetsForwarded.length,\n ...rows.map((r) => r.packetsForwarded.length)\n ),\n packetsRejected: Math.max(\n HEADERS.packetsRejected.length,\n ...rows.map((r) => r.packetsRejected.length)\n ),\n bytesSent: Math.max(\n HEADERS.bytesSent.length,\n ...rows.map((r) => r.bytesSent.length)\n ),\n lastPacket: Math.max(\n HEADERS.lastPacket.length,\n ...rows.map((r) => r.lastPacket.length)\n ),\n };\n\n const headerLine =\n ` ${HEADERS.peer.padEnd(widths.peer)} ` +\n `${HEADERS.connected.padEnd(widths.connected)} ` +\n `${HEADERS.packetsForwarded.padEnd(widths.packetsForwarded)} ` +\n `${HEADERS.packetsRejected.padEnd(widths.packetsRejected)} ` +\n `${HEADERS.bytesSent.padEnd(widths.bytesSent)} ` +\n HEADERS.lastPacket;\n console.log(headerLine);\n console.log(` ${'-'.repeat(headerLine.trim().length)}`);\n for (const row of rows) {\n console.log(\n ` ${row.peer.padEnd(widths.peer)} ` +\n `${row.connected.padEnd(widths.connected)} ` +\n `${row.packetsForwarded.padEnd(widths.packetsForwarded)} ` +\n `${row.packetsRejected.padEnd(widths.packetsRejected)} ` +\n `${row.bytesSent.padEnd(widths.bytesSent)} ` +\n row.lastPacket\n );\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n if (opts.json) {\n emitJsonError(\n `Failed to fetch connector metrics: ${msg}`,\n 'unreachable',\n opts\n );\n } else {\n console.error(`Failed to fetch connector metrics: ${msg}`);\n process.exitCode = 1;\n }\n }\n}\n\n// ── handleLogs ──────────────────────────────────────────────────────────────────\n\ninterface LogsOpts extends DrillOptions {\n lines: number;\n}\n\nasync function resolveContainerName(\n docker: Docker,\n nodeId: string\n): Promise<\n { name: string; service: LogService } | { error: string; code: string }\n> {\n let containers: { Names: string[] }[];\n try {\n containers = (await docker.listContainers({ all: false })) as {\n Names: string[];\n }[];\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n error: `Cannot connect to docker daemon: ${msg}. Is docker running?`,\n code: 'docker-unavailable',\n };\n }\n\n const allNames = containers.flatMap((c) =>\n c.Names.map((n) => n.replace(/^\\//, ''))\n );\n\n // Rule 1: verbatim match if starts with CONTAINER_PREFIX. Verify it actually\n // exists in the running container set so a typo like `townhouse-conector`\n // surfaces as `unknown-node` rather than bubbling out as a raw dockerode 404.\n if (nodeId.startsWith(CONTAINER_PREFIX)) {\n if (!allNames.includes(nodeId)) {\n return {\n error: `Node \"${nodeId}\" is not running (no container named \"${nodeId}\").`,\n code: 'unknown-node',\n };\n }\n const svc = serviceFromContainerName(nodeId) ?? ('town' as LogService);\n return { name: nodeId, service: svc };\n }\n\n // Rule 2: build candidate set\n const candidates: { name: string; service: LogService }[] = [];\n\n // Exact prefix match: townhouse-<nodeId>\n const exactName = `${CONTAINER_PREFIX}${nodeId}`;\n if (allNames.includes(exactName)) {\n const svc = serviceFromContainerName(exactName) ?? ('town' as LogService);\n candidates.push({ name: exactName, service: svc });\n }\n\n // Service-class match: nodeId is a bare service tag (e.g. 'town')\n const isService = (LOG_SERVICES as readonly string[]).includes(nodeId);\n if (isService) {\n for (const name of allNames) {\n if (name === exactName) continue; // already covered above\n const svc = serviceFromContainerName(name);\n if (svc === nodeId) {\n candidates.push({ name, service: svc });\n }\n }\n }\n\n const unique = candidates.filter(\n (c, i) => candidates.findIndex((x) => x.name === c.name) === i\n );\n\n if (unique.length === 0) {\n const resolvedName = `${CONTAINER_PREFIX}${nodeId}`;\n return {\n error: `Node \"${nodeId}\" is not running (no container named \"${resolvedName}\").`,\n code: 'unknown-node',\n };\n }\n\n if (unique.length > 1) {\n const names = unique.map((c) => c.name).join(', ');\n return {\n error: `Ambiguous node-id \"${nodeId}\" — matches multiple containers: ${names}. Use the full container name.`,\n code: 'ambiguous-node',\n };\n }\n\n const first = unique[0];\n if (first === undefined) {\n return {\n error: `Internal error resolving container name for \"${nodeId}\"`,\n code: 'internal',\n };\n }\n return first;\n}\n\nexport async function handleLogs(\n docker: Docker,\n nodeId: string,\n opts: LogsOpts\n): Promise<void> {\n const resolved = await resolveContainerName(docker, nodeId);\n if ('error' in resolved) {\n if (opts.json) {\n emitJsonError(resolved.error, resolved.code, opts);\n } else {\n process.stderr.write(resolved.error + '\\n');\n process.exitCode = 1;\n }\n return;\n }\n\n const { name: containerName, service } = resolved;\n\n const controller = new AbortController();\n\n // SIGINT → abort the stream, drain stdout, then exit honoring exitCode.\n // Avoids both (a) a 50ms race that masked errors arriving in that window\n // and (b) buffer truncation on `townhouse logs --json | jq` pipelines.\n const sigintHandler = () => {\n controller.abort();\n process.stdout.write('', () => {\n process.exit(process.exitCode ?? 0);\n });\n };\n process.once('SIGINT', sigintHandler);\n\n try {\n const gen = tailContainerLogs(docker, containerName, service, {\n tail: opts.lines,\n signal: controller.signal,\n });\n\n for await (const evt of gen) {\n if (opts.json) {\n process.stdout.write(JSON.stringify(evt) + '\\n');\n } else {\n process.stdout.write(\n `${evt.ts} [${evt.service}] ${evt.level}: ${evt.msg}\\n`\n );\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n const isDockerError =\n (msg.includes('ENOENT') && msg.includes('/var/run/docker.sock')) ||\n msg.includes('connect ENOENT') ||\n msg.includes('Cannot connect to the Docker daemon') ||\n (msg.includes('ECONNREFUSED') && msg.includes('docker'));\n if (isDockerError) {\n const errMsg = `Cannot connect to docker daemon: ${msg}. Is docker running?`;\n if (opts.json) {\n emitJsonError(errMsg, 'docker-unavailable', opts);\n } else {\n process.stderr.write(errMsg + '\\n');\n process.exitCode = 1;\n }\n } else {\n const errMsg = `Log stream error for \"${nodeId}\": ${msg}`;\n if (opts.json) {\n emitJsonError(errMsg, 'internal', opts);\n } else {\n process.stderr.write(errMsg + '\\n');\n process.exitCode = 1;\n }\n }\n } finally {\n process.off('SIGINT', sigintHandler);\n }\n}\n\n// ── handlePeerDetail ────────────────────────────────────────────────────────────\n\nexport async function handlePeerDetail(\n adminClient: ConnectorAdminClient,\n peerId: string,\n opts: DrillOptions\n): Promise<void> {\n const now = opts.now ?? new Date();\n\n let peers: PeerStatus[];\n try {\n peers = await adminClient.getPeers();\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n if (opts.json) {\n emitJsonError(msg, 'unreachable', opts);\n } else {\n process.stderr.write(`Failed to fetch peers: ${msg}\\n`);\n process.exitCode = 1;\n }\n return;\n }\n\n const peer = peers.find((p) => p.id === peerId);\n if (peer === undefined) {\n const errMsg = `Unknown peer \"${peerId}\". Use \\`townhouse metrics\\` to see registered peers.`;\n if (opts.json) {\n emitJsonError(errMsg, 'unknown-peer', opts);\n } else {\n process.stderr.write(errMsg + '\\n');\n process.exitCode = 1;\n }\n return;\n }\n\n const [earningsRaw, channelsRaw] = await Promise.all([\n adminClient.getEarnings().catch(() => null),\n adminClient.getChannels().catch(() => null),\n ]);\n\n const peerEarnings: PeerEarnings | null =\n earningsRaw?.peers.find((p) => p.peerId === peerId) ?? null;\n const peerChannels: ChannelSummary[] =\n channelsRaw?.filter((c) => c.peerId === peerId) ?? [];\n\n if (opts.json) {\n // Per AC #8: earnings is null when byAsset[] is empty OR when the\n // earnings endpoint returned 503 (already nulled by the .catch above).\n const earningsForJson =\n peerEarnings && peerEarnings.byAsset.length > 0 ? peerEarnings : null;\n emitJson(\n {\n peer,\n earnings: earningsForJson,\n channels: peerChannels,\n },\n opts\n );\n return;\n }\n\n // Human mode — card display\n console.log(`Peer: ${peerId}`);\n console.log('');\n\n // ILP section\n if (peer.ilpAddresses.length === 0) {\n console.log(' (no ILP addresses registered)');\n } else {\n for (const addr of peer.ilpAddresses) {\n console.log(` ${addr}`);\n }\n }\n console.log(` Routes: ${peer.routeCount}`);\n console.log('');\n\n // Status section\n console.log(`Connected: ${peer.connected ? 'yes' : 'no'}`);\n console.log('');\n\n // Earnings section\n if (earningsRaw === null) {\n console.log('Earnings:');\n console.log(\n ' (earnings endpoint unavailable: connector is not settlement-configured)'\n );\n } else if (peerEarnings === null || peerEarnings.byAsset.length === 0) {\n console.log('Earnings:');\n console.log(' (no settlement activity yet)');\n } else {\n console.log('Earnings:');\n for (const asset of peerEarnings.byAsset) {\n const lastClaim = asset.lastClaimAt\n ? formatRelativeTime(asset.lastClaimAt, now)\n : 'never';\n console.log(\n ` ${asset.assetCode} · received ${asset.claimsReceivedTotal} · sent ${asset.claimsSentTotal} · net ${asset.netBalance} · last claim ${lastClaim}`\n );\n }\n }\n console.log('');\n\n // Channels section\n if (channelsRaw === null) {\n console.log('Channels:');\n console.log(\n ' (channels endpoint unavailable: connector is not settlement-configured)'\n );\n } else if (peerChannels.length === 0) {\n console.log('Channels:');\n console.log(' (no channels open)');\n } else {\n console.log('Channels:');\n for (const ch of peerChannels) {\n console.log(\n ` ${truncate16(ch.channelId)} · ${ch.chain} · ${ch.status} · deposit ${ch.deposit} · ${formatRelativeTime(ch.lastActivity, now)}`\n );\n }\n }\n}\n\n// ── handleHealth ────────────────────────────────────────────────────────────────\n\nconst PROBE_TIMEOUT_MS = 3000;\n\nasync function probeConnector(\n adminClient: ConnectorAdminClient\n): Promise<ProbeResult> {\n try {\n // The townhouse host has only the admin port (9401) reachable, not the\n // connector's healthCheckPort (8080, internal). The admin server's /health\n // returns a slim shape that getHealth()'s validator rejects, so use\n // pingAdminLive() which only checks status code.\n await adminClient.pingAdminLive();\n return { source: 'connector', status: 'healthy' };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return { source: 'connector', status: 'unreachable', error: msg };\n }\n}\n\nasync function probeHostApi(\n apiUrl: string,\n fetchImpl: typeof fetch\n): Promise<ProbeResult> {\n try {\n const response = await fetchImpl(`${apiUrl}/health`, {\n signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),\n });\n if (!response.ok) {\n return {\n source: 'api',\n status: 'unhealthy',\n error: `HTTP ${response.status}`,\n };\n }\n const body = (await response.json()) as {\n status: string;\n uptime: number;\n startedAt: string;\n version: string;\n };\n return {\n source: 'api',\n status: 'healthy',\n uptime: body.uptime,\n startedAt: body.startedAt,\n version: body.version,\n };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return { source: 'api', status: 'unreachable', error: msg };\n }\n}\n\nasync function probeNodes(\n apiUrl: string,\n fetchImpl: typeof fetch\n): Promise<ProbeResult[]> {\n let nodes: { id: string }[];\n try {\n const resp = await fetchImpl(`${apiUrl}/api/nodes`, {\n signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),\n });\n if (!resp.ok) {\n // Surface enumeration failure as a sentinel probe so it counts toward\n // computeOverall instead of silently disappearing as \"no nodes\".\n return [\n {\n source: 'nodes',\n status: 'unknown',\n error: `failed to enumerate nodes: HTTP ${resp.status}`,\n },\n ];\n }\n const body = (await resp.json()) as { nodes?: { id: string }[] };\n nodes = body.nodes ?? [];\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return [\n {\n source: 'nodes',\n status: 'unknown',\n error: `failed to enumerate nodes: ${msg}`,\n },\n ];\n }\n\n return Promise.all(\n nodes.map(async (node) => {\n try {\n const resp = await fetchImpl(\n `${apiUrl}/api/nodes/${encodeURIComponent(node.id)}/health`,\n {\n signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),\n }\n );\n if (!resp.ok) {\n return {\n source: `node:${node.id}`,\n status: 'unhealthy' as const,\n error: `HTTP ${resp.status}`,\n };\n }\n const body = (await resp.json()) as { status?: string };\n const s = body.status;\n const status: ProbeResult['status'] =\n s === 'healthy'\n ? 'healthy'\n : s === 'unhealthy'\n ? 'unhealthy'\n : s === 'starting'\n ? 'starting'\n : s === 'degraded'\n ? 'degraded'\n : 'unknown';\n return { source: `node:${node.id}`, status };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n source: `node:${node.id}`,\n status: 'unreachable' as const,\n error: msg,\n };\n }\n })\n );\n}\n\nasync function probeAnyone(\n adminClient: ConnectorAdminClient\n): Promise<ProbeResult> {\n try {\n const result = await adminClient.getHsHostname();\n if (result.hostname !== null) {\n return {\n source: 'anyone-hostname',\n status: 'healthy',\n hostname: result.hostname,\n publishedAt: result.publishedAt ?? undefined,\n };\n }\n return {\n source: 'anyone-hostname',\n status: 'starting',\n message: 'anon publish pending',\n };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n // Accept both the bare prefix from getHsHostname() and any wrapped 503\n // (proxies/middleware that wrap the throw); avoids classifying \"feature\n // off\" as \"unreachable\" when the path picks up wrapping.\n if (\n msg.startsWith('connector is anon-disabled') ||\n /(?:^|:\\s)503\\b/.test(msg)\n ) {\n return {\n source: 'anyone-hostname',\n status: 'n/a',\n message: 'anon disabled in config',\n };\n }\n return { source: 'anyone-hostname', status: 'unreachable', error: msg };\n }\n}\n\nfunction computeOverall(\n probes: ProbeResult[]\n): 'healthy' | 'degraded' | 'unhealthy' {\n const statuses = probes.map((p) => p.status);\n if (\n statuses.some(\n (s) => s === 'unhealthy' || s === 'unreachable' || s === 'unknown'\n )\n ) {\n return 'unhealthy';\n }\n // A per-node `degraded` probe must surface at the rollup; otherwise a\n // partially-degraded fleet rolls up to healthy.\n if (statuses.some((s) => s === 'starting' || s === 'degraded')) {\n return 'degraded';\n }\n return 'healthy';\n}\n\nexport async function handleHealth(\n adminClient: ConnectorAdminClient,\n opts: DrillOptions\n): Promise<void> {\n const apiUrl = opts.apiUrl ?? 'http://127.0.0.1:28090';\n const fetchImpl = opts.fetch ?? fetch;\n\n // Build a 3-second-timeout version of the admin client for connector probe.\n // Read baseUrl through the public getter so a future field rename surfaces\n // as a type error instead of a silent fallback to the hardcoded port.\n const healthClient =\n opts.adminClient ??\n new ConnectorAdminClient(adminClient.getBaseUrl(), PROBE_TIMEOUT_MS);\n\n const [connectorProbe, apiProbe, nodeProbes, anyoneProbe] = await Promise.all(\n [\n probeConnector(healthClient),\n probeHostApi(apiUrl, fetchImpl),\n probeNodes(apiUrl, fetchImpl),\n probeAnyone(healthClient),\n ]\n );\n\n const probes: ProbeResult[] = [\n connectorProbe,\n apiProbe,\n ...nodeProbes,\n anyoneProbe,\n ];\n const overall = computeOverall(probes);\n\n if (opts.json) {\n emitJson({ overall, probes }, opts);\n } else {\n for (const probe of probes) {\n console.log(`${probe.source}: ${probe.status}`);\n if (probe.error) console.log(` error: ${probe.error}`);\n if (probe.uptime !== undefined) console.log(` uptime: ${probe.uptime}s`);\n if (probe.peersConnected !== undefined)\n console.log(\n ` peers: ${probe.peersConnected}/${probe.totalPeers ?? '?'} connected`\n );\n if (probe.startedAt) console.log(` startedAt: ${probe.startedAt}`);\n if (probe.version) console.log(` version: ${probe.version}`);\n if (probe.hostname) console.log(` hostname: ${probe.hostname}`);\n if (probe.publishedAt) console.log(` publishedAt: ${probe.publishedAt}`);\n if (probe.message) console.log(` ${probe.message}`);\n }\n console.log(`Overall: ${overall}`);\n }\n\n if (overall === 'unhealthy') {\n process.exitCode = 1;\n }\n}\n\n// ── dispatchDrillCommand ────────────────────────────────────────────────────────\n\n/**\n * Dispatcher for the five drill verbs (channels, metrics, logs, peer, health).\n * Centralises arg parsing, --json error envelope routing, and admin-client\n * construction so cli.ts stays thin. Returns true when the command was handled.\n *\n * `--json` validation errors (missing positional, bad --lines, etc.) emit a\n * `{ error, code }` envelope to stdout instead of plain stderr text — the\n * universal contract the human-mode handlers already follow.\n */\nexport interface DispatchDrillDeps {\n adminUrl: string;\n apiUrl: string;\n values: Record<string, unknown>;\n positionals: string[];\n docker?: Docker;\n}\n\nexport async function dispatchDrillCommand(\n command: string,\n deps: DispatchDrillDeps\n): Promise<boolean> {\n const { values, positionals, adminUrl, apiUrl } = deps;\n const json = values['json'] === true;\n const jsonCompact = values['json-compact'] === true;\n const baseOpts = { json, jsonCompact };\n\n const usageError = (msg: string, code: string): void => {\n if (json) emitJsonError(msg, code, baseOpts);\n else {\n console.error(msg);\n process.exitCode = 1;\n }\n };\n\n switch (command) {\n case 'channels': {\n await handleChannels(new ConnectorAdminClient(adminUrl), baseOpts);\n return true;\n }\n case 'metrics': {\n await handleMetrics(new ConnectorAdminClient(adminUrl), baseOpts);\n return true;\n }\n case 'logs': {\n const nodeId = positionals[1];\n if (!nodeId) {\n usageError(\n 'Usage: townhouse logs <node-id> [--lines N] [-f|--follow] [--json]',\n 'usage'\n );\n return true;\n }\n const linesRaw = values['lines'] as string | undefined;\n // Strict integer parse: reject empty/whitespace, scientific notation, hex.\n // Number() coerces all of those silently; the help text says \"an integer\".\n let lines = 50;\n if (linesRaw !== undefined) {\n if (!/^\\d+$/.test(linesRaw)) {\n usageError(\n '--lines must be an integer between 0 and 10000',\n 'bad-flag'\n );\n return true;\n }\n lines = Number(linesRaw);\n if (lines < 0 || lines > 10000) {\n usageError(\n '--lines must be an integer between 0 and 10000',\n 'bad-flag'\n );\n return true;\n }\n }\n const docker = deps.docker ?? new Docker();\n await handleLogs(docker, nodeId, { ...baseOpts, lines });\n return true;\n }\n case 'peer': {\n const peerId = positionals[1];\n if (!peerId) {\n usageError('Usage: townhouse peer <id> [--json]', 'usage');\n return true;\n }\n await handlePeerDetail(\n new ConnectorAdminClient(adminUrl),\n peerId,\n baseOpts\n );\n return true;\n }\n case 'health': {\n await handleHealth(new ConnectorAdminClient(adminUrl, PROBE_TIMEOUT_MS), {\n ...baseOpts,\n apiUrl,\n });\n return true;\n }\n default:\n return false;\n }\n}\n","import type { AggregatedEarnings } from '../earnings/aggregator.js';\nimport { formatUsdc } from '../tui/format.js';\n\nexport const USDC_SCALE = 6;\nconst USDC_ASSET = 'USDC';\nconst DECIMAL_RE = /^-?\\d+$/;\nconst POSITIVE_INT_RE = /^[1-9]\\d*$/;\n\nexport interface EarningsRow {\n today: string;\n month: string;\n year: string;\n lifetime: string;\n}\n\nfunction addDecimalStrings(a: string, b: string): string {\n if (!DECIMAL_RE.test(b)) return a;\n try {\n return (BigInt(a) + BigInt(b)).toString();\n } catch {\n return a;\n }\n}\n\nexport function computeUsdcScalars(earnings: AggregatedEarnings): EarningsRow {\n let today = '0';\n let month = '0';\n let year = '0';\n let lifetime = '0';\n\n const apexUsdc = earnings.apex.routingFees[USDC_ASSET];\n if (apexUsdc !== undefined) {\n today = addDecimalStrings(today, apexUsdc.today);\n month = addDecimalStrings(month, apexUsdc.month);\n year = addDecimalStrings(year, apexUsdc.year);\n lifetime = addDecimalStrings(lifetime, apexUsdc.lifetime);\n }\n\n for (const peer of earnings.peers) {\n const peerUsdc = peer.byAsset[USDC_ASSET];\n if (peerUsdc !== undefined) {\n today = addDecimalStrings(today, peerUsdc.today);\n month = addDecimalStrings(month, peerUsdc.month);\n year = addDecimalStrings(year, peerUsdc.year);\n lifetime = addDecimalStrings(lifetime, peerUsdc.lifetime);\n }\n }\n\n return { today, month, year, lifetime };\n}\n\nexport function usdcMicroToSats(\n decimalString: string,\n satsPerUsdc: number\n): string {\n if (!DECIMAL_RE.test(decimalString)) return '0';\n if (!Number.isInteger(satsPerUsdc) || satsPerUsdc <= 0) {\n throw new Error('satsPerUsdc must be a positive integer');\n }\n const negative = decimalString.startsWith('-');\n const absolute = negative ? decimalString.slice(1) : decimalString;\n const sats =\n (BigInt(absolute) * BigInt(satsPerUsdc)) / 10n ** BigInt(USDC_SCALE);\n return (negative && sats !== 0n ? '-' : '') + sats.toString();\n}\n\nexport function formatSatsRow(value: string): string {\n if (!value || !DECIMAL_RE.test(value)) return '0 sats';\n const negative = value.startsWith('-');\n const abs = negative ? value.slice(1) : value;\n if (!abs || abs === '0') return '0 sats';\n\n let formatted: string;\n const absN = BigInt(abs);\n if (absN < BigInt(Number.MAX_SAFE_INTEGER)) {\n formatted = Number(abs).toLocaleString('en-US');\n } else {\n formatted = abs.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n }\n return (negative ? '-' : '') + formatted + ' sats';\n}\n\nexport function renderEarningsSection(opts: {\n earnings: AggregatedEarnings;\n units: 'usdc' | 'sats';\n satsPerUsdc?: number;\n}): string[] {\n if (opts.earnings.status === 'connector_unavailable') {\n return ['', 'Earnings (USDC): unavailable'];\n }\n\n const scalars = computeUsdcScalars(opts.earnings);\n\n if (opts.units === 'usdc') {\n return [\n '',\n 'Earnings (USDC):',\n '----------------',\n ` TODAY ${formatUsdc(scalars.today, USDC_SCALE)}`,\n ` MONTH ${formatUsdc(scalars.month, USDC_SCALE)}`,\n ` YEAR ${formatUsdc(scalars.year, USDC_SCALE)}`,\n ` LIFETIME ${formatUsdc(scalars.lifetime, USDC_SCALE)}`,\n ];\n }\n\n if (\n opts.satsPerUsdc === undefined ||\n !Number.isInteger(opts.satsPerUsdc) ||\n opts.satsPerUsdc <= 0\n ) {\n throw new Error(\n \"renderEarningsSection: units='sats' requires a positive-integer satsPerUsdc\"\n );\n }\n const rate = opts.satsPerUsdc;\n const header = `Earnings (sats @ ${rate}/USDC):`;\n return [\n '',\n header,\n '-'.repeat(header.length),\n ` TODAY ${formatSatsRow(usdcMicroToSats(scalars.today, rate))}`,\n ` MONTH ${formatSatsRow(usdcMicroToSats(scalars.month, rate))}`,\n ` YEAR ${formatSatsRow(usdcMicroToSats(scalars.year, rate))}`,\n ` LIFETIME ${formatSatsRow(usdcMicroToSats(scalars.lifetime, rate))}`,\n ];\n}\n\nexport function resolveSatsRate(\n values: Record<string, unknown>,\n env: NodeJS.ProcessEnv\n): { rate: number } | { error: string } {\n // Treat empty --rate as absent so a valid env var can still take effect.\n const cliRaw =\n typeof values['rate'] === 'string' ? (values['rate'] as string) : undefined;\n const cliRate = cliRaw !== undefined && cliRaw !== '' ? cliRaw : undefined;\n const envRate = env['TOWNHOUSE_SATS_PER_USDC'];\n const raw = cliRate ?? envRate;\n const source =\n cliRate !== undefined ? '--rate' : 'TOWNHOUSE_SATS_PER_USDC env var';\n\n if (raw === undefined) {\n return {\n error:\n '--units=sats requires --rate <sats-per-usdc> or TOWNHOUSE_SATS_PER_USDC env var (e.g. --rate 1500 for 1500 sats per 1 USDC)',\n };\n }\n\n if (!POSITIVE_INT_RE.test(raw)) {\n return {\n error: `${source} must be a positive integer (sats per 1 USDC); got: ${JSON.stringify(raw)}`,\n };\n }\n\n const rate = Number(raw);\n if (!Number.isSafeInteger(rate) || rate <= 0) {\n return { error: `${source} is out of range` };\n }\n\n return { rate };\n}\n","/**\n * Buy Turbo upload credits using a townhouse-derived EVM or SOL key\n * (epic-49, Phase 2).\n *\n * Pure business logic — no stdout writes, no readline prompts. The CLI\n * handler is responsible for argv parsing, confirmation UI, status streaming,\n * and exit codes. This module owns the Turbo SDK interactions only.\n *\n * Credit↔signer linkage (verified at implementation time, 2026-05-21):\n * `TurboFundWithTokensParams` accepts an optional `turboCreditDestinationAddress`\n * (see `@ardrive/turbo-sdk/lib/types/types.d.ts:637-642`). When supplied, the\n * on-chain payment is sent FROM the SOL/EVM signer's address TO the Turbo\n * service, but the resulting winc balance is credited to the specified\n * destination address. That destination address can be an Arweave address,\n * which an `ArweaveSigner` can subsequently use to spend the credits during\n * upload (`topUpWithTokens` → AR address; `ArweaveSigner` → spend).\n *\n * For this Phase 2 work the default is to omit `turboCreditDestinationAddress`\n * so credits land on the funding identity itself (matches what the CLI prints\n * for source/destination address). Phase 4's DVM container handoff will pass\n * the DVM's Arweave address explicitly so credits land where the\n * ArweaveSigner can spend them.\n */\n\nimport { TurboFactory } from '@ardrive/turbo-sdk/node';\n\nimport type { NodeType } from '../docker/types.js';\nimport type { WalletManager } from '../wallet/manager.js';\nimport { buildTurboSigner, type TurboTokenId } from '../wallet/turbo-signer.js';\nimport { parseTokenAmount } from './units.js';\n\nexport interface BuyCreditsOptions {\n /** Unlocked WalletManager. Caller owns lifecycle (locking). */\n wallet: WalletManager;\n /** Which node's keys fund the purchase. DVM in the standard flow. */\n nodeType: NodeType;\n /** Friendly token id (e.g. 'sol', 'eth', 'usdc-eth'). */\n token: TurboTokenId;\n /**\n * Human decimal amount in token units (e.g. \"0.05\" for 0.05 SOL,\n * \"10\" for 10 USDC). Converted to base units (lamports/wei/microUSDC)\n * via `parseTokenAmount` before being handed to the Turbo SDK.\n */\n amount: string;\n /**\n * Optional fee multiplier passed through to `topUpWithTokens` —\n * Turbo's on-chain gas/priority knob. >1 raises the on-chain fee.\n */\n feeMultiplier?: number;\n /**\n * If true, only fetch the quote (no on-chain transaction). Returns a\n * `BuyQuoteResult` with the winc that WOULD be credited for `amount`.\n */\n quoteOnly?: boolean;\n /**\n * Optional explicit credit recipient. When omitted, credits land on the\n * funding identity itself. Phase 4 uses this to credit the DVM's Arweave\n * address from a SOL/EVM funding signer.\n */\n destinationAddress?: string;\n}\n\n/** Quote-only result — no on-chain tx submitted. */\nexport interface BuyQuoteResult {\n kind: 'quote';\n /** Funding-side address (EVM hex / SOL base58 / AR base64url). */\n fromAddress: string;\n /** Credit-recipient address (defaults to fromAddress). */\n creditAddress: string;\n /** Base-unit amount that WOULD be charged on-chain. */\n baseAmount: bigint;\n /** Winc that WOULD be credited for `baseAmount`. */\n winc: bigint;\n /** Raw Turbo response — preserves equivalentWincTokenAmount, fees, etc. */\n raw: {\n winc: string;\n actualTokenAmount: string;\n equivalentWincTokenAmount: string;\n };\n}\n\n/** Submit-path result — Turbo's `topUpWithTokens` return shape, BigInt-typed. */\nexport interface BuySubmitResult {\n kind: 'submit';\n fromAddress: string;\n creditAddress: string;\n baseAmount: bigint;\n /** Winc actually credited (per Turbo's post-submit response). */\n winc: bigint;\n /** On-chain tx id (e.g. SOL signature, EVM tx hash). */\n id: string;\n /** Turbo's tracked status — typically 'pending' immediately after submit. */\n status: 'pending' | 'confirmed' | 'failed';\n /** Token (canonical string Turbo uses, e.g. 'solana', 'ethereum'). */\n token: string;\n /** Optional block height (present when status='confirmed'). */\n block?: number;\n}\n\nexport type BuyResult = BuyQuoteResult | BuySubmitResult;\n\n/**\n * Build a Turbo authenticated client, fetch a quote for `amount` of `token`,\n * and either return the quote (if `quoteOnly`) or submit `topUpWithTokens`.\n *\n * Pure business logic: no console output, no prompts. The CLI handler should\n * stream status messages between awaits.\n */\nexport async function buyCredits(opts: BuyCreditsOptions): Promise<BuyResult> {\n const {\n wallet,\n nodeType,\n token,\n amount,\n feeMultiplier,\n quoteOnly,\n destinationAddress,\n } = opts;\n\n // Parse human → base units first so we fail fast on bad input before\n // building a signer.\n const baseAmount = parseTokenAmount(token, amount);\n\n // EVM/SOL signer = the funding identity. For `ar` the signer is also\n // the credit recipient (you can buy AR credits with AR — though uncommon).\n const {\n signer,\n token: canonicalToken,\n address: fromAddress,\n } = await buildTurboSigner(wallet, nodeType, token);\n\n // Default credit destination = funding identity. Phase 4 will override\n // with the DVM's Arweave address.\n const creditAddress = destinationAddress ?? fromAddress;\n\n const turbo = TurboFactory.authenticated({\n signer,\n token: canonicalToken,\n });\n\n // Quote step — always run, even on the submit path, so callers always\n // have a winc estimate available. The Turbo SDK quotes in token base units\n // (string-form BigNumber to preserve precision).\n const quote = await turbo.getWincForToken({\n tokenAmount: baseAmount.toString(),\n });\n const quotedWinc = BigInt(quote.winc);\n\n if (quoteOnly) {\n return {\n kind: 'quote',\n fromAddress,\n creditAddress,\n baseAmount,\n winc: quotedWinc,\n raw: {\n winc: quote.winc,\n actualTokenAmount: quote.actualTokenAmount,\n equivalentWincTokenAmount: quote.equivalentWincTokenAmount,\n },\n };\n }\n\n // Submit step. `turboCreditDestinationAddress` is omitted when caller did\n // not supply `destinationAddress` (credits land on the funding identity).\n const topUpParams: {\n tokenAmount: string;\n feeMultiplier?: number;\n turboCreditDestinationAddress?: string;\n } = {\n tokenAmount: baseAmount.toString(),\n };\n if (feeMultiplier !== undefined) topUpParams.feeMultiplier = feeMultiplier;\n if (destinationAddress !== undefined) {\n topUpParams.turboCreditDestinationAddress = destinationAddress;\n }\n\n const submitted = await turbo.topUpWithTokens(topUpParams);\n\n return {\n kind: 'submit',\n fromAddress,\n creditAddress,\n baseAmount,\n winc: BigInt(submitted.winc),\n id: submitted.id,\n status: submitted.status,\n token: submitted.token,\n ...(submitted.block !== undefined ? { block: submitted.block } : {}),\n };\n}\n","/**\n * Turbo SDK signer factory — bridges WalletManager-derived keys to the\n * `@ardrive/turbo-sdk` signer abstraction (epic-49, Phase 2).\n *\n * The Turbo SDK accepts an `ArweaveSigner`, `EthereumSigner`, or\n * `HexSolanaSigner` (all re-exported from `@dha-team/arbundles`) to\n * authenticate the funding side of `topUpWithTokens` and `getBalance`.\n *\n * For the credits-buy flow the SIGNER is the funding identity (EVM or SOL),\n * not the credit recipient. The credit recipient is specified separately via\n * `turboCreditDestinationAddress` in the top-up call — see\n * `packages/townhouse/src/credits/buy.ts`.\n *\n * Why the @ardrive/turbo-sdk re-export and not @dha-team/arbundles directly?\n * The Turbo SDK's `signer.d.ts` explicitly re-exports `ArweaveSigner`,\n * `EthereumSigner`, `HexSolanaSigner` (see lib/types/node/signer.d.ts) as a\n * \"utility export to avoid clients having to install arbundles.\" Importing\n * from `@ardrive/turbo-sdk/node` keeps the version pin in one place and\n * guarantees identical class identity to whatever Turbo's internal factory\n * constructs.\n */\n\nimport {\n ArweaveSigner,\n EthereumSigner,\n HexSolanaSigner,\n} from '@ardrive/turbo-sdk/node';\nimport bs58 from 'bs58';\n\nimport type { NodeType } from '../docker/types.js';\nimport type { WalletManager } from './manager.js';\n\n/**\n * Assemble the 64-byte Solana secret key (32-byte private seed + 32-byte\n * public key) and return it base58-encoded — the format `HexSolanaSigner`\n * actually consumes. (The class is misleadingly named: its `sign()` method\n * hex-encodes the message, but its CONSTRUCTOR expects a base58-encoded\n * secret key per `@dha-team/arbundles/.../SolanaSigner.js` which calls\n * `bs58.decode(_key)` on the input.)\n */\nfunction solanaSecretKeyBase58(\n privateKeyHex: string,\n publicKeyBase58: string\n): string {\n const priv = Buffer.from(privateKeyHex, 'hex');\n if (priv.length !== 32) {\n throw new Error(\n `Solana private key seed must be 32 bytes, got ${priv.length}`\n );\n }\n const pub = bs58.decode(publicKeyBase58);\n if (pub.length !== 32) {\n throw new Error(`Solana public key must be 32 bytes, got ${pub.length}`);\n }\n const secret = new Uint8Array(64);\n secret.set(priv, 0);\n secret.set(pub, 32);\n return bs58.encode(secret);\n}\n\n/**\n * Friendly token identifiers exposed to CLI users. Mapped to Turbo's canonical\n * `TokenType` string in `TURBO_TOKEN_MAP`. We keep the friendly names because\n * `pol`/`usdc-pol`/`usdc-eth` are more memorable than `matic`/`polygon-usdc`.\n */\nexport type TurboTokenId =\n | 'eth'\n | 'pol'\n | 'base-eth'\n | 'base-usdc'\n | 'usdc-eth'\n | 'usdc-pol'\n | 'sol'\n | 'ar';\n\n/**\n * Canonical Turbo `TokenType` string per friendly id. Source of truth for the\n * `token` parameter passed to `TurboFactory.authenticated` and downstream\n * `getWincForToken` / `topUpWithTokens` calls.\n *\n * Mapping derived from `@ardrive/turbo-sdk` `tokenTypes` (see\n * `lib/types/types.d.ts:36`):\n * \"arweave\" | \"ario\" | \"base-ario\" | \"solana\" | \"ethereum\" | \"kyve\"\n * | \"matic\" | \"pol\" | \"base-eth\" | \"usdc\" | \"base-usdc\" | \"polygon-usdc\"\n */\nconst TURBO_TOKEN_MAP = {\n eth: 'ethereum',\n pol: 'pol',\n 'base-eth': 'base-eth',\n 'base-usdc': 'base-usdc',\n 'usdc-eth': 'usdc',\n 'usdc-pol': 'polygon-usdc',\n sol: 'solana',\n ar: 'arweave',\n} as const;\n\nexport type TurboCanonicalToken = (typeof TURBO_TOKEN_MAP)[TurboTokenId];\n\n/** EVM family — uses secp256k1 + an `EthereumSigner` regardless of chain. */\nconst EVM_TOKENS: ReadonlySet<TurboTokenId> = new Set([\n 'eth',\n 'pol',\n 'base-eth',\n 'base-usdc',\n 'usdc-eth',\n 'usdc-pol',\n]);\n\n/** Return type for `buildTurboSigner` — opaque to callers besides Turbo. */\nexport interface TurboSignerBundle {\n /** ArweaveSigner | EthereumSigner | HexSolanaSigner instance. */\n signer: ArweaveSigner | EthereumSigner | HexSolanaSigner;\n /** Canonical Turbo token string (passed verbatim to TurboFactory). */\n token: TurboCanonicalToken;\n /**\n * Native address corresponding to the signer (checksummed EVM hex,\n * base58 SOL, or base64url AR). Useful for confirmation prompts and\n * balance queries — for `credits buy` this is the FUNDING address.\n */\n address: string;\n}\n\n/**\n * Map a friendly `TurboTokenId` to the canonical Turbo string. Exported for\n * tests + the CLI argv parser. Throws on unknown ids.\n */\nexport function canonicalTurboToken(token: TurboTokenId): TurboCanonicalToken {\n const canonical = TURBO_TOKEN_MAP[token];\n if (!canonical) {\n throw new Error(\n `Unknown TurboTokenId '${String(token)}'. Supported: ${Object.keys(TURBO_TOKEN_MAP).join(', ')}`\n );\n }\n return canonical;\n}\n\n/**\n * Construct a Turbo SDK signer bundle from a node's WalletManager-derived key.\n *\n * EVM family → `EthereumSigner(privateKeyHex)`.\n * `sol` → `HexSolanaSigner(privateKeyHex)`.\n * `ar` → `await wallet.ensureArweaveKey(...)` then `ArweaveSigner(jwk)`.\n *\n * The caller is responsible for the lifecycle of `wallet` — this function\n * does NOT lock the wallet on completion.\n */\nexport async function buildTurboSigner(\n wallet: WalletManager,\n nodeType: NodeType,\n token: TurboTokenId\n): Promise<TurboSignerBundle> {\n const canonical = canonicalTurboToken(token);\n\n if (EVM_TOKENS.has(token)) {\n const privateKeyHex = wallet.getEvmPrivateKeyHex(nodeType);\n const signer = new EthereumSigner(privateKeyHex);\n const keys = wallet.getNodeKeys(nodeType);\n return { signer, token: canonical, address: keys.evmAddress };\n }\n\n if (token === 'sol') {\n const privateKeyHex = wallet.getSolanaPrivateKeyHex(nodeType);\n const keys = wallet.getNodeKeys(nodeType);\n if (!keys.solanaAddress) {\n // Defensive — getSolanaPrivateKeyHex would have thrown first, but the\n // type system can't see that.\n throw new Error(`Solana address not available for node '${nodeType}'`);\n }\n const secretBase58 = solanaSecretKeyBase58(\n privateKeyHex,\n keys.solanaAddress\n );\n const signer = new HexSolanaSigner(secretBase58);\n return { signer, token: canonical, address: keys.solanaAddress };\n }\n\n if (token === 'ar') {\n // RSA-4096 derivation is 5-30s on first call — Phase 1 contract.\n await wallet.ensureArweaveKey(nodeType);\n const jwk = wallet.getArweaveJwk(nodeType);\n const signer = new ArweaveSigner(jwk);\n const keys = wallet.getNodeKeys(nodeType);\n if (!keys.arweaveAddress) {\n throw new Error(\n `Arweave address not populated for node '${nodeType}' after ensureArweaveKey`\n );\n }\n return { signer, token: canonical, address: keys.arweaveAddress };\n }\n\n // Exhaustiveness — TS sees `token` as `never` here when all variants covered.\n throw new Error(`Unsupported TurboTokenId: ${String(token)}`);\n}\n","/**\n * Display helpers for Turbo credits + on-chain amounts (epic-49, Phase 2).\n *\n * Two concerns:\n * 1. Translate raw winc (winston credit, 1e-12 AR) into a \"~N MB upload\n * capacity\" string for operator-facing surfaces. Per the plan: every winc\n * value shown to a human should be accompanied by its bytes translation.\n * 2. Format on-chain base amounts (lamports, wei, USDC microunits) back into\n * their human decimal representation for confirmation prompts.\n */\n\nimport type { TurboTokenId } from '../wallet/turbo-signer.js';\n\n/**\n * Approximate winc-per-byte pricing constant. The Turbo SDK does NOT expose a\n * static converter — pricing is dynamic via `getTokenPriceForBytes` /\n * `getUploadCosts` (network calls). For at-a-glance display this constant is\n * good enough; precise quotes pass through the SDK.\n *\n * Reference: 1 GiB ≈ ~6.5e14 winc as of 2026-05 (varies with AR/USD rate and\n * network demand). 1 MiB ≈ 6.4e11 winc → 1 byte ≈ 6.1e5 winc.\n *\n * Update with the same cadence as ardrive's published pricing dashboard. This\n * is intentionally a single constant — if it drifts more than ±20% we should\n * be calling `getTokenPriceForBytes` for the display path too.\n */\nconst WINC_PER_BYTE_APPROX = 610_000n;\n\n/**\n * Convert a winc balance into an approximate byte count.\n *\n * Pure BigInt division — no float math. Underestimates very small balances\n * (a wallet with <WINC_PER_BYTE_APPROX winc shows as \"0 B\"), which is the\n * correct semantic: you can't actually upload anything.\n */\nfunction wincToBytes(winc: bigint): bigint {\n if (winc < 0n) return 0n;\n return winc / WINC_PER_BYTE_APPROX;\n}\n\n/**\n * Format a winc value as a human-friendly upload-capacity string.\n *\n * Examples:\n * formatWincAsBytes(0n) → \"~0 B\"\n * formatWincAsBytes(6_100_000n) → \"~10 B\"\n * formatWincAsBytes(61_000_000_000n) → \"~100 KB\"\n * formatWincAsBytes(610_000_000_000n)→ \"~1 MB\"\n *\n * Uses base-1000 (decimal SI) units to match how the ardrive UI presents\n * capacity. Rounds DOWN — never overstates available capacity.\n */\nexport function formatWincAsBytes(winc: bigint): string {\n const bytes = wincToBytes(winc);\n\n if (bytes < 1_000n) return `~${bytes.toString()} B`;\n if (bytes < 1_000_000n) {\n return `~${(bytes / 1_000n).toString()} KB`;\n }\n if (bytes < 1_000_000_000n) {\n return `~${(bytes / 1_000_000n).toString()} MB`;\n }\n if (bytes < 1_000_000_000_000n) {\n return `~${(bytes / 1_000_000_000n).toString()} GB`;\n }\n return `~${(bytes / 1_000_000_000_000n).toString()} TB`;\n}\n\n/**\n * Number of decimal places per token. Mirrors `@ardrive/turbo-sdk`\n * `exponentMap` for the tokens we support (see lib/types/common/token/index.d.ts\n * — exponentMap entries: ar=12, sol=9, eth/pol/base-eth=18, usdc=6).\n */\nconst TOKEN_DECIMALS: Record<TurboTokenId, number> = {\n ar: 12,\n sol: 9,\n eth: 18,\n pol: 18,\n 'base-eth': 18,\n 'base-usdc': 6,\n 'usdc-eth': 6,\n 'usdc-pol': 6,\n};\n\n/** Human-readable token symbol for display (uppercase per market convention). */\nconst TOKEN_SYMBOL: Record<TurboTokenId, string> = {\n ar: 'AR',\n sol: 'SOL',\n eth: 'ETH',\n pol: 'POL',\n 'base-eth': 'ETH (Base)',\n 'base-usdc': 'USDC (Base)',\n 'usdc-eth': 'USDC (Ethereum)',\n 'usdc-pol': 'USDC (Polygon)',\n};\n\n/**\n * Format a base-unit token amount (lamports, wei, USDC microunits, winston)\n * into a human decimal string with the token symbol.\n *\n * Examples:\n * formatTokenAmount('sol', 1_000_000n) → \"0.001000000 SOL\"\n * formatTokenAmount('usdc-eth', 10_000_000n) → \"10.000000 USDC (Ethereum)\"\n * formatTokenAmount('eth', 1_000_000_000_000_000n) → \"0.001000000000000000 ETH\"\n *\n * Uses BigInt arithmetic exclusively — never float. Trailing zeros are\n * preserved so the decimal count visibly matches the token precision (an\n * operator confirming a SOL payment expects to see 9 decimals).\n */\nexport function formatTokenAmount(\n token: TurboTokenId,\n baseAmount: bigint\n): string {\n const decimals = TOKEN_DECIMALS[token];\n const symbol = TOKEN_SYMBOL[token];\n if (decimals === undefined || symbol === undefined) {\n throw new Error(`Unknown TurboTokenId for formatting: ${String(token)}`);\n }\n\n const scale = 10n ** BigInt(decimals);\n const isNegative = baseAmount < 0n;\n const abs = isNegative ? -baseAmount : baseAmount;\n const whole = abs / scale;\n const frac = abs % scale;\n const fracStr = frac.toString().padStart(decimals, '0');\n const sign = isNegative ? '-' : '';\n return `${sign}${whole.toString()}.${fracStr} ${symbol}`;\n}\n\n/**\n * Parse a human decimal amount string (e.g. \"0.05\", \"10\", \"1.234\") to its\n * base-unit BigInt representation for the given token.\n *\n * Strict — rejects scientific notation, leading +, trailing characters,\n * and more decimal places than the token supports. Throws Error with a\n * caller-friendly message on failure.\n *\n * Examples:\n * parseTokenAmount('sol', '0.001') → 1_000_000n\n * parseTokenAmount('usdc-eth', '10') → 10_000_000n\n * parseTokenAmount('eth', '0.0001') → 100_000_000_000_000n\n */\nexport function parseTokenAmount(token: TurboTokenId, decimal: string): bigint {\n const decimals = TOKEN_DECIMALS[token];\n if (decimals === undefined) {\n throw new Error(`Unknown TurboTokenId: ${String(token)}`);\n }\n\n const trimmed = decimal.trim();\n if (!/^\\d+(\\.\\d+)?$/.test(trimmed)) {\n throw new Error(\n `Invalid decimal amount '${decimal}' for token '${token}'. Use plain decimal notation (e.g. \"0.05\").`\n );\n }\n\n const [wholeStr, fracStr = ''] = trimmed.split('.');\n if (fracStr.length > decimals) {\n throw new Error(\n `Amount '${decimal}' has ${fracStr.length} decimal places, but '${token}' supports at most ${decimals}.`\n );\n }\n const fracPadded = fracStr.padEnd(decimals, '0');\n const whole = BigInt(wholeStr);\n const frac = fracPadded.length > 0 ? BigInt(fracPadded) : 0n;\n return whole * 10n ** BigInt(decimals) + frac;\n}\n","/**\n * Query a Turbo credit balance for the address that would fund (and, by\n * default, hold) credits in the `townhouse credits buy` flow\n * (epic-49, Phase 2).\n *\n * Uses an authenticated Turbo client — the SDK's `getBalance()` (no args)\n * returns the balance for the signer's native address. Passing an explicit\n * address is also supported, but for the standard CLI flow the signer's\n * address IS the address we want to query.\n *\n * Pure business logic. The CLI handler formats the winc into a human\n * readable `~N MB` string via `formatWincAsBytes`.\n */\n\nimport { TurboFactory } from '@ardrive/turbo-sdk/node';\n\nimport type { NodeType } from '../docker/types.js';\nimport type { WalletManager } from '../wallet/manager.js';\nimport { buildTurboSigner, type TurboTokenId } from '../wallet/turbo-signer.js';\n\nexport interface GetCreditBalanceOptions {\n wallet: WalletManager;\n nodeType: NodeType;\n token: TurboTokenId;\n /** Optional explicit address to query — defaults to signer's address. */\n address?: string;\n}\n\nexport interface CreditBalanceResult {\n /** Spendable winc balance (per Turbo's `winc` field). */\n winc: bigint;\n /**\n * Winc + currently-revocable approvals (per Turbo's `controlledWinc`).\n * Higher than `winc` when the operator has shared credits with another\n * address that haven't been spent.\n */\n controlledWinc: bigint;\n /**\n * Winc the user can currently spend including received approvals from\n * other addresses (per Turbo's `effectiveBalance`).\n */\n effectiveBalance: bigint;\n /** The address whose balance was queried (funding-identity native form). */\n address: string;\n}\n\n/**\n * Fetch the Turbo credit balance held by the funding identity for `nodeType`.\n *\n * Throws on Turbo SDK network errors — the CLI handler should catch and\n * surface a clean operator-facing message.\n */\nexport async function getCreditBalance(\n opts: GetCreditBalanceOptions\n): Promise<CreditBalanceResult> {\n const { wallet, nodeType, token, address: explicitAddress } = opts;\n\n const {\n signer,\n token: canonicalToken,\n address: signerAddress,\n } = await buildTurboSigner(wallet, nodeType, token);\n\n const turbo = TurboFactory.authenticated({\n signer,\n token: canonicalToken,\n });\n\n // Pass the explicit address when supplied so callers can query a different\n // recipient (Phase 4: query the DVM's Arweave address from a SOL signer).\n // Omit otherwise to use the signer's native address.\n const balance = explicitAddress\n ? await turbo.getBalance(explicitAddress)\n : await turbo.getBalance();\n\n return {\n winc: BigInt(balance.winc),\n controlledWinc: BigInt(balance.controlledWinc),\n effectiveBalance: BigInt(balance.effectiveBalance),\n address: explicitAddress ?? signerAddress,\n };\n}\n","function isOptOut(value: string | undefined): boolean {\n if (value === undefined) return false;\n if (value === '' || value === '0' || value.toLowerCase() === 'false')\n return false;\n return true;\n}\n\nexport function shouldRenderInk(): boolean {\n if (process.stdout.isTTY !== true) return false;\n if (process.env['CI'] === 'true') return false;\n if (isOptOut(process.env['NO_TUI'])) return false;\n if ((process.env['TERM'] ?? '') === 'dumb') return false;\n return true;\n}\n\nexport function isTmux(): boolean {\n if (process.env['TMUX'] !== undefined && process.env['TMUX'] !== '')\n return true;\n const term = process.env['TERM'] ?? '';\n return /^screen|^tmux/.test(term);\n}\n"],"mappings":"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","names":["spawn","Docker","resolve","supportsUnicode","xMark","arrow","resolve","resolve","formatRelativeTime","resolve","body","emitJsonError","TurboFactory","TurboFactory","Docker","createInterface","resolve","spawn","require"]}
|