//#region src/chat/format.ts
/**
* Resolve the user's home directory from env (no static os-module import),
* so this module stays node-free and importable from a browser-context
* renderer (Electron's renderer, Vite) via `zidane/chat/pure`. Reads the
* platform env vars Node/Electron populate; returns '' when unavailable
* (renderer), in which case `compactPath` falls back to the verbatim path.
*/
function resolveHome() {
	const env = globalThis.process?.env;
	return env?.HOME ?? env?.USERPROFILE ?? "";
}
/** Compact token formatter — 12_415 → "12.4k", 1_234_567 → "1.23M". */
function fmtTokens(n) {
	if (n < 1e3) return String(n);
	if (n < 1e6) return `${(n / 1e3).toFixed(n < 1e4 ? 2 : 1)}k`;
	return `${(n / 1e6).toFixed(2)}M`;
}
/** Compact relative-time formatter — "just now / 5m / 3h / 2d". */
function ageString(ts, now = Date.now()) {
	const m = Math.floor((now - ts) / 6e4);
	if (m < 1) return "just now";
	if (m < 60) return `${m}m ago`;
	const h = Math.floor(m / 60);
	if (h < 24) return `${h}h ago`;
	return `${Math.floor(h / 24)}d ago`;
}
/** Six-char short form of a session id for headers and lists. */
function shortId(id) {
	return id.replace(/-/g, "").slice(0, 6);
}
/**
* Single-line preview of a multi-line string, capped at `max` chars and
* ellipsis-terminated when truncated.
*
* Whitespace runs (newlines, tabs, multiple spaces) collapse into one
* space so the rendered output stays on a single visual row no matter
* how the input was shaped. Used by every transcript "preview" surface
* (spawn-start task, `tool: shell (background): <command>`,
* `<task-notification>` summary line, etc.) — without the whitespace
* collapse, a 60-char `slice` on a string with an inline `\n\n` paints
* the second paragraph below the first, producing the visible
* "preview text spills onto multiple lines" bug (and, downstream,
* misaligned spawn markers when the wrapped lines collide with
* other events).
*
* Reserves one slot for the `…` so the displayed width is exactly
* `max` when truncation kicks in.
*/
function previewLine(s, max) {
	const single = s.replace(/\s+/g, " ").trim();
	if (single.length <= max) return single;
	return `${single.slice(0, max - 1)}…`;
}
/**
* Compact human-readable duration formatter shared by background-task
* surfaces (the `<task-notification>` summary, the TUI banner, the
* `shell_kill` tool result, etc.).
*
* Format ladder:
*   - `< 1s`   →  `"Nms"`
*   - `< 10s`  →  `"N.Ns"` (one decimal)
*   - `< 1m`   →  `"Ns"`   (whole seconds)
*   - `< 1h`   →  `"NmNs"` / `"Nm"` when seconds round to 0
*   - `≥ 1h`   →  `"NhNm"` / `"Nh"` when minutes round to 0
*
* Single source of truth so a 60s task renders the same across the
* model-facing XML summary and the user-facing banner. Earlier
* separate formatters disagreed (XML said `"60.0s"`, banner said `"1m"`)
* which was confusing to the user reading both side by side.
*/
function formatDuration(ms) {
	if (ms < 0) ms = 0;
	if (ms < 1e3) return `${ms}ms`;
	if (ms < 6e4) return `${(ms / 1e3).toFixed(ms < 1e4 ? 1 : 0)}s`;
	const minutes = Math.floor(ms / 6e4);
	const seconds = Math.floor(ms % 6e4 / 1e3);
	if (minutes < 60) return seconds > 0 ? `${minutes}m${seconds}s` : `${minutes}m`;
	const hours = Math.floor(minutes / 60);
	const remMinutes = minutes % 60;
	return remMinutes > 0 ? `${hours}h${remMinutes}m` : `${hours}h`;
}
/**
* Status label for a terminated background task — `"exited <code>"`
* for natural exits, `"killed"` (with the signal name when known)
* for our-issued SIGTERMs.
*
* Pulled out as its own function so the `<task-notification>` XML
* summary, the TUI banner header, the `shell_kill` tool result, and
* future surfaces all read the same string.
*/
function formatTaskStatus(info) {
	return info.status === "killed" ? `killed${info.signal ? ` (${info.signal})` : ""}` : `exited ${info.exitCode}`;
}
/**
* One-line summary of a terminated background task — the shape used by
* the `<task-notification>` XML's `<summary>` tag AND the TUI banner's
* `event.text` fallback string. Three dot-separated segments:
*
*   `<command preview · status · duration>`
*
* Centralizes the format so live + replay + wire all agree, and so a
* future cosmetic tweak (separator glyph, segment ordering) lands in
* exactly one place.
*/
function formatTaskSummary(info, maxCommandChars = 80) {
	return `${previewLine(info.command, maxCommandChars)} · ${formatTaskStatus(info)} · ${formatDuration(info.durationMs)}`;
}
/**
* Compact an absolute path for display: replace the user's `$HOME`
* prefix with `~` (so `/Users/yael/Code/zidane` → `~/Code/zidane`),
* and optionally left-truncate with an ellipsis when the result
* still exceeds `maxWidth` (so the path's *tail* — the part the user
* recognizes — stays visible: `…/zidane` rather than `/Users/yaeluil…`).
*
* `maxWidth` is the maximum *display width* in cells. Omit to skip
* truncation. Paths outside `$HOME` are returned verbatim modulo
* truncation. The ellipsis (`…`) counts as one cell.
*
* `home` overrides `os.homedir()` for tests; production callers leave
* it undefined and pay the cheap one-syscall lookup per call.
*/
function compactPath(path, maxWidth, home) {
	const h = home ?? resolveHome();
	let display = path;
	if (h) {
		if (path === h) display = "~";
		else if (path.startsWith(`${h}/`)) display = `~${path.slice(h.length)}`;
	}
	if (maxWidth !== void 0 && maxWidth > 1 && display.length > maxWidth) return `…${display.slice(display.length - maxWidth + 1)}`;
	return display;
}
//#endregion
export { formatTaskStatus as a, shortId as c, formatDuration as i, compactPath as n, formatTaskSummary as o, fmtTokens as r, previewLine as s, ageString as t };

//# sourceMappingURL=format-BNOXpl-1.js.map