1// 4-hex site addresses and vault:alias target parsing.2// address (int 0..65535) = vaultSlot << 12 | siteId34/** Number of named vault slots — the high nibble of an address (0x0–0xf). */5export const VAULT_SLOTS = 16;6/** Sites addressable within a single vault — the low three nibbles (0x000–0xfff). */7export const SITES_PER_VAULT = 0x1000;89const ADDR_RE = /^[0-9a-f]{4}$/;10const NAME_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;1112/** True if `s` is a well-formed 4-hex address like `"2a3f"`. */13export function isAddressString(s: string): boolean {14 return ADDR_RE.test(s);15}1617/** "2a3f" → 0x2a3f. Throws on malformed input. */18export function parseAddress(s: string): number {19 if (!ADDR_RE.test(s)) throw new Error(`invalid address: ${JSON.stringify(s)}`);20 return parseInt(s, 16);21}2223/** 0x2a3f → "2a3f" */24export function formatAddress(addr: number): string {25 if (!Number.isInteger(addr) || addr < 0 || addr >= VAULT_SLOTS * SITES_PER_VAULT) {26 throw new Error(`address out of range: ${addr}`);27 }28 return addr.toString(16).padStart(4, "0");29}3031/** Extract the vault slot (0–15) from an address integer. */32export function vaultSlotOf(addr: number): number {33 return addr >> 12;34}3536/** Valid vault or alias name: lowercase alnum + hyphen, 1-32 chars, must not look like an address. */37export function isValidName(name: string): boolean {38 return NAME_RE.test(name) && !ADDR_RE.test(name);39}4041/** A parsed target: a raw address, a `vault:alias` pair, or a bare alias in the default vault. */42export type ParsedTarget =43 | { kind: "address"; address: number }44 | { kind: "vaultAlias"; vault: string; alias: string }45 | { kind: "alias"; alias: string };4647/** Parse a CLI/API target: "2a3f" | "work:demo" | "demo". */48export function parseTarget(s: string): ParsedTarget {49 if (ADDR_RE.test(s)) return { kind: "address", address: parseInt(s, 16) };50 const colon = s.indexOf(":");51 if (colon !== -1) {52 const vault = s.slice(0, colon);53 const alias = s.slice(colon + 1);54 if (!isValidName(vault) || !isValidName(alias)) {55 throw new Error(`invalid target: ${JSON.stringify(s)}`);56 }57 return { kind: "vaultAlias", vault, alias };58 }59 if (!isValidName(s)) throw new Error(`invalid target: ${JSON.stringify(s)}`);60 return { kind: "alias", alias: s };61}