import { createServer } from "node:http"; import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { HWPOD_NODE_OPS_CONTRACT_VERSION } from "./hwpod-node-ops-contract.ts"; const NODE_VERSION = "0.1.0-thin-node-ops"; const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024; const BOOLEAN_OPTIONS = new Set(["help", "h", "json"]); type ParsedArgs = Record & { _: string[] }; export async function mainHwpodNode(argv = process.argv.slice(2), options: any = {}) { const stdinText = options.stdinText ?? await readCliStdinForRun(argv); const result = await runHwpodNode(argv, { ...options, stdinText }); if (result?.keepAlive) return; console.log(JSON.stringify(result.payload, null, 2)); process.exitCode = result.exitCode; } async function readCliStdinForRun(argv: string[]) { const command = argv.find((token) => !token.startsWith("-")) || "help"; const hasInlinePlan = argv.some((token) => token === "--plan-json" || token === "--plan" || token.startsWith("--plan-json=") || token.startsWith("--plan=")); if (command !== "run" || hasInlinePlan || process.stdin.isTTY) return undefined; const chunks: Buffer[] = []; for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk)); const textValue = Buffer.concat(chunks).toString("utf8"); return textValue.trim() ? textValue : undefined; } export async function runHwpodNode(argv: string[], options: { stdinText?: string; now?: () => string } = {}) { const now = options.now ?? (() => new Date().toISOString()); try { const parsed = parseOptions(argv); const command = parsed._[0] || "help"; if (["help", "--help", "-h"].includes(command)) return result(0, help(), now); if (command === "run") { const plan = planFromParsed(parsed, options.stdinText); return result(0, await executeHwpodNodeOpsPlan(plan, { now }), now); } if (command === "serve") { const server = createHwpodNodeServer({ now }); const host = text(parsed.host) || "127.0.0.1"; const port = numberValue(parsed.port) ?? 19678; await new Promise((resolve) => server.listen(port, host, resolve)); console.log(JSON.stringify({ ok: true, action: "hwpod-node.serve", status: "listening", host, port: server.address()?.port ?? port, observedAt: now() }, null, 2)); return { exitCode: 0, payload: null, keepAlive: true, server }; } if (command === "connect") { const nodeId = text(parsed.nodeId ?? parsed.node) || process.env.HWPOD_NODE_ID || os.hostname(); const connector = connectHwpodNodeWs({ wsUrl: text(parsed.wsUrl), cloudUrl: text(parsed.cloudUrl ?? parsed.apiUrl), token: text(parsed.token) || process.env.HWPOD_NODE_WS_TOKEN || process.env.HWLAB_HWPOD_NODE_WS_TOKEN || "", nodeId, name: text(parsed.name) || nodeId, heartbeatIntervalMs: numberValue(parsed.heartbeatIntervalMs) ?? 15000, reconnectBaseMs: numberValue(parsed.reconnectBaseMs) ?? 1000, reconnectMaxMs: numberValue(parsed.reconnectMaxMs) ?? 30000, now }); console.log(JSON.stringify({ ok: true, action: "hwpod-node.connect", status: "connecting", nodeId, wsUrl: connector.wsUrl, observedAt: now() }, null, 2)); return { exitCode: 0, payload: null, keepAlive: true, connector }; } throw cliError("unsupported_hwpod_node_command", `unsupported hwpod-node command: ${command}`); } catch (error) { return result(1, failure("hwpod-node", error), now); } } export function connectHwpodNodeWs(config: any, options: any = {}) { const now = config.now ?? options.now ?? (() => new Date().toISOString()); const nodeId = requiredText(config.nodeId || process.env.HWPOD_NODE_ID || os.hostname(), "nodeId"); const wsUrl = hwpodNodeWsUrl(config); const heartbeatIntervalMs = Math.max(1000, numberValue(config.heartbeatIntervalMs) ?? 15000); const reconnectBaseMs = Math.max(250, numberValue(config.reconnectBaseMs) ?? 1000); const reconnectMaxMs = Math.max(reconnectBaseMs, numberValue(config.reconnectMaxMs) ?? 30000); const reconnect = config.reconnect !== false; const executePlan = options.executePlan ?? ((plan: any) => executeHwpodNodeOpsPlan(plan, { now })); let socket: WebSocket | null = null; let heartbeatTimer: ReturnType | null = null; let reconnectTimer: ReturnType | null = null; let closed = false; let attempt = 0; let readyResolve: ((value: unknown) => void) | null = null; const ready = new Promise((resolve) => { readyResolve = resolve; }); function connect() { if (closed) return; socket = new WebSocket(wsUrl); socket.addEventListener("open", () => { logWsEvent("open"); attempt = 0; sendJson({ type: "register", nodeId, name: text(config.name) || nodeId, capabilities: ["hwpod-node-ops", "cmd.run", "workspace.ls", "workspace.cat", "workspace.rg", "workspace.apply-patch", "debug.build", "debug.download", "debug.reset"], labels: { platform: process.platform, arch: process.arch, hostname: os.hostname(), version: NODE_VERSION, startedAt: now() } }); sendHeartbeat(); heartbeatTimer = setInterval(sendHeartbeat, heartbeatIntervalMs); }); socket.addEventListener("message", (event: MessageEvent) => { void handleWsMessage(event.data).catch((error) => { logWsEvent("message_error", { message: error?.message ?? String(error), code: error?.code ?? "hwpod_node_ws_message_failed" }); sendJson({ type: "hwpod-node-error", nodeId, error: { message: error?.message ?? String(error), code: error?.code ?? "hwpod_node_ws_message_failed" }, observedAt: now() }); }); }); socket.addEventListener("close", (event) => { logWsEvent("close", { code: event.code, reason: event.reason || null, reconnect }); scheduleReconnect(); }); socket.addEventListener("error", () => logWsEvent("error", { readyState: socket?.readyState ?? null })); } function scheduleReconnect() { clearTimers(); if (closed || !reconnect) return; attempt += 1; const delayMs = Math.min(reconnectMaxMs, reconnectBaseMs * 2 ** Math.min(attempt, 8)); reconnectTimer = setTimeout(connect, delayMs); } async function handleWsMessage(raw: any) { const textValue = typeof raw === "string" ? raw : await new Response(raw).text(); const message = JSON.parse(textValue); if (message?.type === "ack" && message.requestId === "register" && readyResolve) { logWsEvent("registered", { ok: message.ok === true, message: text(message.message) || null }); readyResolve(message); readyResolve = null; return; } if (message?.type !== "hwpod-node-ops") return; const requestId = text(message.requestId) || "req_unknown"; let resultPayload; try { resultPayload = await executePlan(message.plan); } catch (error) { resultPayload = failure("hwpod-node.ws.run", error); } sendJson({ type: "hwpod-node-ops-result", nodeId, requestId, result: resultPayload, observedAt: now() }); } function sendHeartbeat() { sendJson({ type: "heartbeat", nodeId, at: now() }); } function sendJson(value: any) { if (!socket || socket.readyState !== WebSocket.OPEN) return false; socket.send(JSON.stringify(value)); return true; } function clearTimers() { if (heartbeatTimer !== null) clearInterval(heartbeatTimer); if (reconnectTimer !== null) clearTimeout(reconnectTimer); heartbeatTimer = null; reconnectTimer = null; } function close() { closed = true; clearTimers(); socket?.close(); } function logWsEvent(event: string, extra: Record = {}) { process.stderr.write(`${JSON.stringify({ event: `hwpod-node.ws.${event}`, nodeId, wsUrl: redactUrlToken(wsUrl), observedAt: now(), ...extra })}\n`); } connect(); return { wsUrl, nodeId, ready, close }; } function redactUrlToken(urlValue: string) { const parsed = new URL(urlValue); if (parsed.searchParams.has("token")) parsed.searchParams.set("token", ""); return parsed.toString(); } function hwpodNodeWsUrl(config: any) { const explicit = text(config.wsUrl); if (explicit) return appendToken(explicit, text(config.token)); const cloudUrl = text(config.cloudUrl).replace(/\/+$/u, ""); if (!cloudUrl) throw cliError("hwpod_node_ws_url_required", "connect requires --ws-url or --cloud-url"); const parsed = new URL(cloudUrl); parsed.protocol = parsed.protocol === "https:" ? "wss:" : "ws:"; parsed.pathname = "/v1/hwpod-node/ws"; parsed.search = ""; return appendToken(parsed.toString(), text(config.token)); } function appendToken(urlValue: string, token: string) { if (!token) return urlValue; const parsed = new URL(urlValue); parsed.searchParams.set("token", token); return parsed.toString(); } export function createHwpodNodeServer(options: any = {}) { const now = options.now ?? (() => new Date().toISOString()); return createServer(async (request, response) => { try { const url = new URL(request.url || "/", "http://hwpod-node.local"); if (request.method === "GET" && (url.pathname === "/health/live" || url.pathname === "/v1/hwpod-node-ops")) { return sendJson(response, 200, { ok: true, status: "ready", serviceId: "hwpod-node", contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION, version: NODE_VERSION, nodeRole: "single-host-hwpod-node", acceptedInput: "hwpod-node-ops", specAuthority: "none", nodeId: process.env.HWPOD_NODE_ID || os.hostname(), observedAt: now() }); } if (request.method === "POST" && url.pathname === "/v1/hwpod-node-ops") { const body = await readBody(request, options.bodyLimitBytes); const plan = body ? JSON.parse(body) : {}; return sendJson(response, 200, await executeHwpodNodeOpsPlan(plan, { now })); } return sendJson(response, 404, { ok: false, error: { code: "not_found", message: "hwpod-node route not found" } }); } catch (error) { return sendJson(response, 500, failure("hwpod-node.http", error)); } }); } export async function executeHwpodNodeOpsPlan(plan: any, options: any = {}) { const now = options.now ?? (() => new Date().toISOString()); if (!plan || typeof plan !== "object" || Array.isArray(plan)) throw cliError("invalid_hwpod_node_ops_plan", "plan must be a JSON object"); if (plan.contractVersion !== HWPOD_NODE_OPS_CONTRACT_VERSION) throw cliError("invalid_hwpod_node_ops_contract", `contractVersion must be ${HWPOD_NODE_OPS_CONTRACT_VERSION}`); if (!Array.isArray(plan.ops) || plan.ops.length === 0) throw cliError("invalid_hwpod_node_ops", "ops must be a non-empty array"); const results = []; for (const op of plan.ops) { results.push(await executeOp(op, { plan, now })); } const failed = results.some((item) => item.ok === false); return { ok: !failed, status: failed ? "failed" : "completed", contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION, serviceId: "hwpod-node", nodeVersion: NODE_VERSION, nodeRole: "single-host-hwpod-node", acceptedInput: "hwpod-node-ops", specAuthority: "none", planId: plan.planId ?? null, hwpodId: plan.hwpodId ?? null, nodeId: plan.nodeId ?? process.env.HWPOD_NODE_ID ?? os.hostname(), results, observedAt: now() }; } async function executeOp(op: any, context: any) { const opId = text(op?.opId) || "op_unknown"; const name = text(op?.op); const args = op?.args && typeof op.args === "object" && !Array.isArray(op.args) ? op.args : {}; try { if (name === "node.health") return opOk(opId, name, { platform: process.platform, arch: process.arch, hostname: os.hostname(), cwd: process.cwd() }); if (name === "node.version") return opOk(opId, name, { version: NODE_VERSION }); if (name === "node.inventory") return opOk(opId, name, await nodeInventory(args)); if (name === "workspace.ls") return opOk(opId, name, await workspaceLs(args)); if (name === "workspace.cat") return opOk(opId, name, await workspaceCat(args)); if (name === "workspace.rg") return opOk(opId, name, await workspaceRg(args)); if (name === "workspace.apply-patch") return opOk(opId, name, await workspaceApplyPatch(args)); if (name === "cmd.run") return opOk(opId, name, await cmdRun(args)); if (["debug.build", "debug.download", "debug.reset"].includes(name)) { const output = await debugCommand(name, args); return output.ok ? opOk(opId, name, output) : opFailed(opId, name, output, "hwpod_node_command_failed", `${name} node-side command exited with ${output.exitCode}`); } if (["io.uart.read", "io.uart.write", "io.uart.jsonrpc"].includes(name)) { return opBlocked(opId, name, "hwpod_node_op_not_configured", `${name} requires node-side tool binding; the thin node contract is present but this local executor has no binding yet`); } return opBlocked(opId, name, "unsupported_hwpod_node_op", `unsupported hwpod-node op: ${name}`); } catch (error) { return opBlocked(opId, name, error?.code || "hwpod_node_op_failed", error?.message || String(error)); } } async function nodeInventory(args: any) { const root = workspaceRoot(args); const info = await stat(root).catch(() => null); return { workspaceRoot: root, workspaceExists: Boolean(info), workspaceIsDirectory: info?.isDirectory?.() ?? false }; } async function workspaceLs(args: any) { const target = resolveWorkspacePath(args, text(args.path) || "."); const entries = await readdir(target, { withFileTypes: true }); return { path: target, entries: entries.map((entry) => ({ name: entry.name, type: entry.isDirectory() ? "dir" : entry.isFile() ? "file" : "other" })) }; } async function workspaceCat(args: any) { const target = resolveWorkspacePath(args, requiredText(args.path, "path")); const maxBytes = numberValue(args.maxBytes) ?? 65536; const content = await readFile(target, "utf8"); const truncated = Buffer.byteLength(content, "utf8") > maxBytes; return { path: target, content: truncated ? content.slice(0, maxBytes) : content, truncated }; } async function workspaceRg(args: any) { const pattern = requiredText(args.pattern, "pattern"); const cwd = workspaceRoot(args); const rgPath = text(args.path) || "."; const command = ["rg", "--line-number", "--color", "never", ...(args.ignoreCase === true ? ["--ignore-case"] : []), pattern, rgPath]; const output = await spawnOutput(command, { cwd, timeoutMs: numberValue(args.timeoutMs) ?? 10000 }); return { cwd, command, ...output }; } async function workspaceApplyPatch(args: any) { const patch = text(args.patch) || (text(args.patchBase64) ? Buffer.from(text(args.patchBase64), "base64").toString("utf8") : ""); if (!patch) throw cliError("patch_required", "patch or patchBase64 is required"); const cwd = workspaceRoot(args); return { cwd, changes: await applyPatchEnvelope(cwd, patch) }; } async function debugCommand(op: string, args: any) { const command = text(args.commandLine) || text(args.command); if (!command) throw cliError("hwpod_node_op_not_configured", `${op} requires an explicit node-side command binding in the compiled hwpod-node-ops plan`); const cwd = workspaceRoot(args); const output = await spawnShellOutput(command, { cwd, timeoutMs: numberValue(args.timeoutMs) ?? 120000 }); return { cwd, command, bindingSource: "hwpod-node-ops.command", ...output }; } async function applyPatchEnvelope(root: string, patch: string) { const lines = patch.replace(/\r\n?/gu, "\n").split("\n"); while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop(); if (lines[0] !== "*** Begin Patch" || lines[lines.length - 1] !== "*** End Patch") { throw cliError("invalid_apply_patch_envelope", "patch must start with *** Begin Patch and end with *** End Patch"); } const changes = []; for (let index = 1; index < lines.length - 1;) { const header = lines[index]; if (header.startsWith("*** Add File: ")) { const relativePath = header.slice("*** Add File: ".length).trim(); const filePath = resolvePatchFile(root, relativePath); const addLines = []; index += 1; while (index < lines.length - 1 && !lines[index].startsWith("*** ")) { if (!lines[index].startsWith("+")) throw cliError("invalid_add_file_hunk", `add file lines must start with + for ${relativePath}`); addLines.push(lines[index].slice(1)); index += 1; } await mkdir(path.dirname(filePath), { recursive: true }); await writeFile(filePath, `${addLines.join("\n")}\n`, "utf8"); changes.push({ action: "add", path: relativePath, lines: addLines.length }); continue; } if (header.startsWith("*** Delete File: ")) { const relativePath = header.slice("*** Delete File: ".length).trim(); await rm(resolvePatchFile(root, relativePath), { force: false }); changes.push({ action: "delete", path: relativePath }); index += 1; continue; } if (header.startsWith("*** Update File: ")) { const relativePath = header.slice("*** Update File: ".length).trim(); const filePath = resolvePatchFile(root, relativePath); const hunks = []; index += 1; while (index < lines.length - 1 && !lines[index].startsWith("*** ")) { if (!lines[index].startsWith("@@")) throw cliError("invalid_update_hunk", `update hunk must start with @@ for ${relativePath}`); index += 1; const hunkLines = []; while (index < lines.length - 1 && !lines[index].startsWith("@@") && !lines[index].startsWith("*** ")) { const prefix = lines[index][0]; if (![" ", "-", "+"].includes(prefix)) throw cliError("invalid_update_hunk_line", `hunk lines must start with space, -, or + for ${relativePath}`); hunkLines.push(lines[index]); index += 1; } hunks.push(hunkLines); } const applied = await applyUpdateHunks(filePath, hunks, relativePath); changes.push({ action: "update", path: relativePath, hunks: hunks.length, replacements: applied }); continue; } throw cliError("unsupported_apply_patch_operation", `unsupported patch operation: ${header}`); } return changes; } async function applyUpdateHunks(filePath: string, hunks: string[][], relativePath: string) { const original = await readFile(filePath, "utf8"); const hadFinalNewline = original.endsWith("\n"); let fileLines = original.replace(/\r\n?/gu, "\n").split("\n"); if (hadFinalNewline) fileLines.pop(); let searchStart = 0; for (const hunk of hunks) { const oldLines = hunk.filter((line) => line[0] !== "+").map((line) => line.slice(1)); const newLines = hunk.filter((line) => line[0] !== "-").map((line) => line.slice(1)); const at = findLineSequence(fileLines, oldLines, searchStart); if (at < 0) throw cliError("apply_patch_context_not_found", `patch context not found for ${relativePath}`); fileLines = [...fileLines.slice(0, at), ...newLines, ...fileLines.slice(at + oldLines.length)]; searchStart = at + newLines.length; } await writeFile(filePath, `${fileLines.join("\n")}${hadFinalNewline ? "\n" : ""}`, "utf8"); return hunks.length; } function findLineSequence(lines: string[], sequence: string[], start: number) { if (sequence.length === 0) return start; for (let index = start; index <= lines.length - sequence.length; index += 1) { if (sequence.every((line, offset) => lines[index + offset] === line)) return index; } return -1; } function resolvePatchFile(root: string, relativePath: string) { if (!relativePath || path.isAbsolute(relativePath)) throw cliError("invalid_patch_path", "patch file paths must be relative", { path: relativePath }); return resolveWorkspacePath({ workspacePath: root }, relativePath); } async function cmdRun(args: any) { const command = requiredText(args.command, "command"); const argv = Array.isArray(args.argv) ? args.argv.map(String) : []; const cwd = workspaceRoot(args); const output = await spawnOutput([command, ...argv], { cwd, timeoutMs: numberValue(args.timeoutMs) ?? 30000 }); return { cwd, command: [command, ...argv], ...output }; } async function spawnShellOutput(command: string, { cwd, timeoutMs }: any) { const shellCommand = process.platform === "win32" ? ["cmd.exe", "/d", "/s", "/c", command] : ["sh", "-lc", command]; const output = await spawnOutput(shellCommand, { cwd, timeoutMs }); return { shell: shellCommand.slice(0, -1), exitCode: output.exitCode, stdout: output.stdout, stderr: output.stderr, ok: output.ok }; } async function spawnOutput(command: string[], { cwd, stdinText = "", timeoutMs }: any) { const proc = Bun.spawn(command, { cwd, stdin: stdinText ? "pipe" : "ignore", stdout: "pipe", stderr: "pipe" }); if (stdinText && proc.stdin) { proc.stdin.write(stdinText); proc.stdin.end(); } const timeout = setTimeout(() => proc.kill(), timeoutMs); try { const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]); return { exitCode, stdout, stderr, ok: exitCode === 0 }; } finally { clearTimeout(timeout); } } function workspaceRoot(args: any) { return path.resolve(text(args.workspacePath) || process.env.HWPOD_WORKSPACE_PATH || process.cwd()); } function resolveWorkspacePath(args: any, relativePath: string) { const root = workspaceRoot(args); const target = path.resolve(root, relativePath); if (target !== root && !target.startsWith(`${root}${path.sep}`)) { throw cliError("workspace_path_outside_root", "workspace path must stay inside workspacePath", { workspacePath: root, path: relativePath }); } return target; } function planFromParsed(parsed: ParsedArgs, stdinText?: string) { if (parsed.planJson) return JSON.parse(String(parsed.planJson)); if (parsed.plan) return JSON.parse(String(parsed.plan)); if (stdinText) return JSON.parse(stdinText); throw cliError("hwpod_node_plan_required", "pass --plan-json or pipe a hwpod-node-ops plan to hwpod-node run"); } function help() { return { ok: true, action: "hwpod-node.help", status: "succeeded", contractVersion: HWPOD_NODE_OPS_CONTRACT_VERSION, version: NODE_VERSION, role: "Single host-side executor for stable hwpod-node-ops; hwpod-spec, hwpod-cli, hwpod-ctl, and hwpod-compiler stay in the Code Agent workspace.", usage: [ "bun tools/hwpod-node.ts run --plan-json '{...}'", "bun tools/hwpod-node.ts serve --host 127.0.0.1 --port 19678", "bun tools/hwpod-node.ts connect --cloud-url http://74.48.78.17:19667 --node-id node-d601-f103-v2" ] }; } function result(exitCode: number, payload: any, now: () => string) { return { exitCode, payload: { ...payload, observedAt: payload?.observedAt ?? now() } }; } function opOk(opId: string, op: string, output: any) { return { opId, op, ok: true, status: "completed", output }; } function opBlocked(opId: string, op: string, code: string, summary: string) { return { opId, op, ok: false, status: "blocked", blocker: { code, layer: "hwpod-node", retryable: true, summary } }; } function opFailed(opId: string, op: string, output: any, code: string, summary: string) { return { opId, op, ok: false, status: "failed", output, blocker: { code, layer: "hwpod-node", retryable: false, summary } }; } function failure(action: string, error: any) { return { ok: false, action, status: "failed", error: { code: error?.code || "hwpod_node_error", message: error?.message || String(error), details: error?.details || undefined } }; } function parseOptions(argv: string[]): ParsedArgs { const parsed: ParsedArgs = { _: [] }; for (let index = 0; index < argv.length; index += 1) { const token = argv[index]; if (token === "--") { parsed._.push(...argv.slice(index + 1)); break; } if (!token.startsWith("-") || token === "-") { parsed._.push(token); continue; } if (token === "-h") { parsed.h = true; parsed.help = true; continue; } const [rawKey, rawInline] = token.slice(2).split(/=(.*)/su, 2); const key = rawKey.replace(/-([a-z0-9])/giu, (_match, char) => char.toUpperCase()); if (BOOLEAN_OPTIONS.has(key)) { parsed[key] = rawInline === undefined || rawInline === "" ? true : /^(?:1|true|yes|on)$/iu.test(rawInline); continue; } if (rawInline !== undefined && rawInline !== "") { parsed[key] = rawInline; continue; } const next = argv[index + 1]; if (next === undefined || next.startsWith("--")) { parsed[key] = true; } else { parsed[key] = next; index += 1; } } return parsed; } function readBody(request: any, limitBytes = DEFAULT_BODY_LIMIT_BYTES) { const limit = Number.isInteger(limitBytes) && limitBytes > 0 ? limitBytes : DEFAULT_BODY_LIMIT_BYTES; return new Promise((resolve, reject) => { let body = ""; request.setEncoding("utf8"); request.on("data", (chunk: string) => { body += chunk; if (Buffer.byteLength(body, "utf8") > limit) request.destroy(new Error(`request body exceeds ${limit} bytes`)); }); request.on("end", () => resolve(body)); request.on("error", reject); }); } function sendJson(response: any, statusCode: number, body: any) { response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" }); response.end(`${JSON.stringify(body)}\n`); } function cliError(code: string, message: string, details: Record = {}) { const error = new Error(message) as Error & { code?: string; details?: Record }; error.code = code; error.details = details; return error; } function requiredText(value: unknown, name: string) { const normalized = text(value); if (!normalized) throw cliError("required_option_missing", `${name} is required`, { name }); return normalized; } function text(value: unknown) { return typeof value === "string" && value.trim() ? value.trim() : ""; } function numberValue(value: unknown) { const parsed = Number.parseInt(String(value ?? ""), 10); return Number.isInteger(parsed) ? parsed : undefined; }