diff --git a/src/cli/banner.ts b/src/cli/banner.ts index 670a1cddc..05541f1b5 100644 --- a/src/cli/banner.ts +++ b/src/cli/banner.ts @@ -10,6 +10,20 @@ type BannerOptions = TaglineOptions & { let bannerEmitted = false; +const graphemeSegmenter = + typeof Intl !== "undefined" && "Segmenter" in Intl + ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) + : null; + +function splitGraphemes(value: string): string[] { + if (!graphemeSegmenter) return Array.from(value); + try { + return Array.from(graphemeSegmenter.segment(value), (seg) => seg.segment); + } catch { + return Array.from(value); + } +} + const hasJsonFlag = (argv: string[]) => argv.some((arg) => arg === "--json" || arg.startsWith("--json=")); @@ -62,7 +76,7 @@ export function formatCliBannerArt(options: BannerOptions = {}): string { theme.accent("🦞") ); } - return [...line].map(colorChar).join(""); + return splitGraphemes(line).map(colorChar).join(""); }); return colored.join("\n"); diff --git a/src/cli/gateway-cli.ts b/src/cli/gateway-cli.ts index ed853cb08..c040b8feb 100644 --- a/src/cli/gateway-cli.ts +++ b/src/cli/gateway-cli.ts @@ -98,7 +98,15 @@ type GatewayDiscoverOpts = { function parseDiscoverTimeoutMs(raw: unknown, fallbackMs: number): number { if (raw === undefined || raw === null) return fallbackMs; - const value = typeof raw === "string" ? raw.trim() : String(raw); + const value = + typeof raw === "string" + ? raw.trim() + : typeof raw === "number" || typeof raw === "bigint" + ? String(raw) + : null; + if (value === null) { + throw new Error("invalid --timeout"); + } if (!value) return fallbackMs; const parsed = Number.parseInt(value, 10); if (!Number.isFinite(parsed) || parsed <= 0) {