UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

452 lines (451 loc) 15.5 kB
import { a as visibleWidth, i as truncateToVisibleWidth, n as splitGraphemes } from "./ansi-BI9w76Cm.js"; import path from "node:path"; import os from "node:os"; //#region packages/terminal-core/src/display-string.ts /** Normalize env/home values and reject shell placeholder strings. */ function normalize$1(value) { const trimmed = value?.trim(); return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : void 0; } /** Run a home resolver defensively because some runtimes throw for missing passwd data. */ function normalizeSafe(fn) { try { return normalize$1(fn()); } catch { return; } } /** Resolve Termux home from its Android prefix layout. */ function resolveTermuxHome(env) { const prefix = normalize$1(env.PREFIX); if (!prefix || !normalize$1(env.ANDROID_DATA)) return; if (!/(?:^|\/)com\.termux\/files\/usr\/?$/u.test(prefix.replace(/\\/gu, "/"))) return; return path.resolve(prefix, "..", "home"); } /** Resolve the underlying OS home before applying OpenClaw overrides. */ function resolveRawOsHomeDir(env, homedir) { return normalize$1(env.HOME) ?? normalize$1(env.USERPROFILE) ?? resolveTermuxHome(env) ?? normalizeSafe(homedir); } /** Resolve raw home with OPENCLAW_HOME tilde expansion. */ function resolveRawHomeDir(env = process.env, homedir = os.homedir) { const explicitHome = normalize$1(env.OPENCLAW_HOME); if (explicitHome) { const fallbackHome = resolveRawOsHomeDir(env, homedir); return fallbackHome ? explicitHome.replace(/^~(?=$|[\\/])/, fallbackHome) : explicitHome; } return resolveRawOsHomeDir(env, homedir); } /** Resolve the effective absolute home directory for display replacement. */ function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) { const raw = resolveRawHomeDir(env, homedir); return raw ? path.resolve(raw) : void 0; } /** Resolve the display prefix that should replace the effective home path. */ function resolveHomeDisplayPrefix() { const home = resolveEffectiveHomeDir(); if (!home) return; return process.env.OPENCLAW_HOME?.trim() ? { home, prefix: "$OPENCLAW_HOME" } : { home, prefix: "~" }; } /** Replace the effective home path with "~" or "$OPENCLAW_HOME" for terminal display. */ function displayString(input) { if (!input) return input; const display = resolveHomeDisplayPrefix(); return display ? input.split(display.home).join(display.prefix) : input; } //#endregion //#region packages/terminal-core/src/table.ts function resolveDefaultBorder(platform, env) { if (platform !== "win32") return "unicode"; const term = env.TERM ?? ""; const termProgram = env.TERM_PROGRAM ?? ""; return Boolean(env.WT_SESSION) || term.includes("xterm") || term.includes("cygwin") || term.includes("msys") || termProgram === "vscode" ? "unicode" : "ascii"; } function repeat(ch, n) { if (n <= 0) return ""; return ch.repeat(n); } function padCell(text, width, align) { const content = visibleWidth(text) > width ? truncateToVisibleWidth(text, width) : text; const w = visibleWidth(content); if (w >= width) return content; const pad = width - w; if (align === "right") return `${repeat(" ", pad)}${content}`; if (align === "center") { const left = Math.floor(pad / 2); const right = pad - left; return `${repeat(" ", left)}${content}${repeat(" ", right)}`; } return `${content}${repeat(" ", pad)}`; } function wrapLine(text, width) { if (width <= 0) return [text]; const ESC = "\x1B"; const SGR_RESET = `${ESC}[0m`; const tokens = []; for (let i = 0; i < text.length;) { if (text[i] === ESC) { if (text[i + 1] === "[") { let j = i + 2; while (j < text.length) { const ch = text[j]; if (ch === "m") break; if (ch && ch >= "0" && ch <= "9") { j += 1; continue; } if (ch === ";") { j += 1; continue; } break; } if (text[j] === "m") { tokens.push({ kind: "ansi", value: text.slice(i, j + 1) }); i = j + 1; continue; } } if (text[i + 1] === "]" && text.slice(i + 2, i + 5) === "8;;") { const st = text.indexOf(`${ESC}\\`, i + 5); if (st >= 0) { tokens.push({ kind: "ansi", value: text.slice(i, st + 2) }); i = st + 2; continue; } } } let nextEsc = text.indexOf(ESC, i); if (nextEsc < 0) nextEsc = text.length; if (nextEsc === i) { tokens.push({ kind: "char", value: ESC }); i += 1; continue; } const plainChunk = text.slice(i, nextEsc); for (const grapheme of splitGraphemes(plainChunk)) tokens.push({ kind: "char", value: grapheme }); i = nextEsc; } const firstCharIndex = tokens.findIndex((t) => t.kind === "char"); if (firstCharIndex < 0) return [text]; let lastCharIndex = -1; for (let i = tokens.length - 1; i >= 0; i -= 1) if (tokens[i]?.kind === "char") { lastCharIndex = i; break; } const prefixAnsi = tokens.slice(0, firstCharIndex).filter((t) => t.kind === "ansi").map((t) => t.value).join(""); const suffixAnsi = tokens.slice(lastCharIndex + 1).filter((t) => t.kind === "ansi").map((t) => t.value).join(""); const coreTokens = tokens.slice(firstCharIndex, lastCharIndex + 1); const lines = []; const isBreakChar = (ch) => ch === " " || ch === " " || ch === "/" || ch === "-" || ch === "_" || ch === "."; const isSpaceChar = (ch) => ch === " " || ch === " "; let skipNextLf = false; const buf = []; let bufVisible = 0; let lastBreakIndex = null; const bufToString = (slice) => (slice ?? buf).map((t) => t.value).join(""); const bufVisibleWidth = (slice) => slice.reduce((acc, t) => acc + (t.kind === "char" ? visibleWidth(t.value) : 0), 0); const parseSgrParams = (value) => { if (!value.startsWith(`${ESC}[`) || !value.endsWith("m")) return null; const raw = value.slice(2, -1); if (!raw) return [0]; const params = raw.split(";").map((part) => part === "" ? 0 : Number(part)); return params.every((param) => Number.isInteger(param)) ? params : null; }; const activeSgrAfter = (tokensValue) => { const active = []; const resetCategoriesFor = (params) => { const categories = /* @__PURE__ */ new Set(); for (const param of params) if (param === 22) categories.add("intensity"); else if (param === 23) categories.add("italic"); else if (param === 24) categories.add("underline"); else if (param === 25) categories.add("blink"); else if (param === 27) categories.add("inverse"); else if (param === 28) categories.add("conceal"); else if (param === 29) categories.add("strike"); else if (param === 39) categories.add("foreground"); else if (param === 49) categories.add("background"); return categories; }; const activeCategoriesFor = (params) => { const categories = /* @__PURE__ */ new Set(); for (let i = 0; i < params.length; i += 1) { const param = params[i] ?? 0; if (param === 1 || param === 2) categories.add("intensity"); else if (param === 3) categories.add("italic"); else if (param === 4) categories.add("underline"); else if (param === 5 || param === 6) categories.add("blink"); else if (param === 7) categories.add("inverse"); else if (param === 8) categories.add("conceal"); else if (param === 9) categories.add("strike"); else if (param >= 30 && param <= 37 || param >= 90 && param <= 97) categories.add("foreground"); else if (param === 38) { categories.add("foreground"); if (params[i + 1] === 2) i += 4; else if (params[i + 1] === 5) i += 2; } else if (param >= 40 && param <= 47 || param >= 100 && param <= 107) categories.add("background"); else if (param === 48) { categories.add("background"); if (params[i + 1] === 2) i += 4; else if (params[i + 1] === 5) i += 2; } } return categories; }; const intersects = (left, right) => { for (const value of left) if (right.has(value)) return true; return false; }; for (const token of tokensValue) { if (token.kind !== "ansi") continue; const params = parseSgrParams(token.value); if (!params) continue; if (params.includes(0)) active.length = 0; const resetCategories = resetCategoriesFor(params); if (resetCategories.size > 0) for (let i = active.length - 1; i >= 0; i -= 1) { const entry = active[i]; if (entry && intersects(entry.categories, resetCategories)) active.splice(i, 1); } const activeCategories = activeCategoriesFor(params); if (activeCategories.size > 0) { for (let i = active.length - 1; i >= 0; i -= 1) { const entry = active[i]; if (entry && intersects(entry.categories, activeCategories)) active.splice(i, 1); } active.push({ value: token.value, categories: activeCategories }); } } return active.map((entry) => entry.value).join(""); }; const pushLine = (value) => { const cleaned = value.replace(/\s+$/, ""); if (visibleWidth(cleaned) === 0) return; lines.push(cleaned); }; const trimLeadingSpaces = (tokensLocal) => { while (true) { const firstCharIndexLocal = tokensLocal.findIndex((token) => token.kind === "char"); if (firstCharIndexLocal < 0) return; const firstChar = tokensLocal[firstCharIndexLocal]; if (!firstChar || !isSpaceChar(firstChar.value)) return; tokensLocal.splice(firstCharIndexLocal, 1); } }; const flushAt = (breakAt) => { if (buf.length === 0) return; if (breakAt == null || breakAt <= 0) { const activeSgr = activeSgrAfter(buf); pushLine(activeSgr ? `${bufToString()}${SGR_RESET}` : bufToString()); buf.length = 0; if (activeSgr) buf.push({ kind: "ansi", value: activeSgr }); bufVisible = 0; lastBreakIndex = null; return; } const left = buf.slice(0, breakAt); const rest = buf.slice(breakAt); const activeSgr = activeSgrAfter(left); pushLine(activeSgr ? `${bufToString(left)}${SGR_RESET}` : bufToString(left)); trimLeadingSpaces(rest); if (activeSgr) rest.unshift({ kind: "ansi", value: activeSgr }); buf.length = 0; buf.push(...rest); bufVisible = bufVisibleWidth(buf); lastBreakIndex = null; }; for (const token of coreTokens) { if (token.kind === "ansi") { buf.push(token); continue; } const ch = token.value; if (skipNextLf) { skipNextLf = false; if (ch === "\n") continue; } if (ch === "\n" || ch === "\r") { flushAt(buf.length); if (ch === "\r") skipNextLf = true; continue; } const charWidth = visibleWidth(ch); if (bufVisible + charWidth > width && bufVisible > 0) flushAt(lastBreakIndex); if (bufVisible === 0 && isSpaceChar(ch)) continue; buf.push(token); bufVisible += charWidth; if (isBreakChar(ch)) lastBreakIndex = buf.length; } flushAt(buf.length); if (!lines.length) return [""]; if (!prefixAnsi && !suffixAnsi) return lines; return lines.map((line) => { if (!line) return line; return `${prefixAnsi}${line}${suffixAnsi}`; }); } function normalizeWidth(n) { if (n == null) return; if (!Number.isFinite(n) || n <= 0) return; return Math.floor(n); } function getTerminalTableWidth(minWidth = 60, fallbackWidth = 120) { return Math.max(minWidth, process.stdout.columns ?? fallbackWidth); } function renderTable(opts) { const rows = opts.rows.map((row) => { const next = {}; for (const [key, value] of Object.entries(row)) next[key] = displayString(value); return next; }); const border = opts.border ?? resolveDefaultBorder(process.platform, process.env); if (border === "none") { const columns = opts.columns; return `${[columns.map((c) => c.header).join(" | "), ...rows.map((r) => columns.map((c) => r[c.key] ?? "").join(" | "))].join("\n")}\n`; } const padding = Math.max(0, opts.padding ?? 1); const columns = opts.columns; const metrics = columns.map((c) => { return { headerW: visibleWidth(c.header), cellW: Math.max(0, ...rows.map((r) => visibleWidth(r[c.key] ?? ""))) }; }); const widths = columns.map((c, i) => { const m = metrics[i]; const base = Math.max(m?.headerW ?? 0, m?.cellW ?? 0) + padding * 2; const capped = c.maxWidth ? Math.min(base, c.maxWidth) : base; return Math.max(c.minWidth ?? 3, capped); }); const maxWidth = normalizeWidth(opts.width); const sepCount = columns.length + 1; const total = widths.reduce((a, b) => a + b, 0) + sepCount; const preferredMinWidths = columns.map((c, i) => Math.max(c.minWidth ?? 3, (metrics[i]?.headerW ?? 0) + padding * 2, 3)); const absoluteMinWidths = columns.map((_c, i) => Math.max((metrics[i]?.headerW ?? 0) + padding * 2, 3)); if (maxWidth && total > maxWidth) { let over = total - maxWidth; const flexOrder = columns.map((_c, i) => ({ i, w: widths[i] ?? 0 })).filter(({ i }) => Boolean(columns[i]?.flex)).toSorted((a, b) => b.w - a.w).map((x) => x.i); const nonFlexOrder = columns.map((_c, i) => ({ i, w: widths[i] ?? 0 })).filter(({ i }) => !columns[i]?.flex).toSorted((a, b) => b.w - a.w).map((x) => x.i); const shrink = (order, minWidths) => { while (over > 0) { let progressed = false; for (const i of order) { if ((widths[i] ?? 0) <= (minWidths[i] ?? 0)) continue; widths[i] = (widths[i] ?? 0) - 1; over -= 1; progressed = true; if (over <= 0) break; } if (!progressed) break; } }; shrink(flexOrder, preferredMinWidths); shrink(flexOrder, absoluteMinWidths); shrink(nonFlexOrder, preferredMinWidths); shrink(nonFlexOrder, absoluteMinWidths); } if (maxWidth) { const sepCountLocal = columns.length + 1; let extra = maxWidth - (widths.reduce((a, b) => a + b, 0) + sepCountLocal); if (extra > 0) { const flexCols = columns.map((c, i) => ({ c, i })).filter(({ c }) => Boolean(c.flex)).map(({ i }) => i); if (flexCols.length > 0) { const caps = columns.map((c) => typeof c.maxWidth === "number" && c.maxWidth > 0 ? Math.floor(c.maxWidth) : Number.POSITIVE_INFINITY); while (extra > 0) { let progressed = false; for (const i of flexCols) { if ((widths[i] ?? 0) >= (caps[i] ?? Number.POSITIVE_INFINITY)) continue; widths[i] = (widths[i] ?? 0) + 1; extra -= 1; progressed = true; if (extra <= 0) break; } if (!progressed) break; } } } } const box = border === "ascii" ? { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|", t: "+", ml: "+", m: "+", mr: "+", b: "+" } : { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", t: "┬", ml: "├", m: "┼", mr: "┤", b: "┴" }; const hLine = (left, mid, right) => `${left}${widths.map((w) => repeat(box.h, w)).join(mid)}${right}`; const contentWidthFor = (i) => Math.max(1, widths[i] - padding * 2); const padStr = repeat(" ", padding); const renderRow = (record, isHeader = false) => { const wrapped = columns.map((c) => isHeader ? c.header : record[c.key] ?? "").map((cell, i) => wrapLine(cell, contentWidthFor(i))); const height = Math.max(...wrapped.map((w) => w.length)); const out = []; for (let li = 0; li < height; li += 1) { const parts = wrapped.map((lines, i) => { return `${padStr}${padCell(lines[li] ?? "", contentWidthFor(i), columns[i]?.align ?? "left")}${padStr}`; }); out.push(`${box.v}${parts.join(box.v)}${box.v}`); } return out; }; const lines = []; lines.push(hLine(box.tl, box.t, box.tr)); lines.push(...renderRow({}, true)); lines.push(hLine(box.ml, box.m, box.mr)); for (const row of rows) lines.push(...renderRow(row, false)); lines.push(hLine(box.bl, box.b, box.br)); return `${lines.join("\n")}\n`; } //#endregion export { renderTable as n, getTerminalTableWidth as t };