kaizen-cli 0.1.41

Distributable agent observability: real-time-tailable sessions, agile-style retros, and repo-level improvement (Cursor, Claude Code, Codex). SQLite, redact before any sync you enable.
Documentation
export function renderOutput(target, output, renderer = "detail") {
  if (!target) return;
  target.classList.remove("empty");
  const value = output?.kind === "json" ? output.value : output?.value;
  const mode = renderer === "summary" ? "cards" : renderer;
  target.replaceChildren();
  if (mode === "toast") return target.append(card("Done", stringify(value || "Saved")));
  if (mode === "table") return target.append(table(rows(value)));
  if (mode === "tree") return target.append(tree(value));
  if (mode === "cards" || mode === "metrics" || mode === "report") return target.append(...cards(value));
  if (mode === "markdown" || mode === "live") return target.append(pre(stringify(value)));
  target.append(detail(value));
}

function rows(value) {
  if (Array.isArray(value)) return value;
  if (!value || typeof value !== "object") return [{ value }];
  const found = Object.values(value).find(Array.isArray);
  return found || Object.entries(value).map(([key, val]) => ({ key, value: val }));
}

function table(items) {
  if (!items.length) return empty("No rows.");
  const columns = [...new Set(items.flatMap(item => Object.keys(flat(item))).slice(0, 8))];
  const el = document.createElement("table");
  el.append(thead(columns), tbody(items, columns));
  return el;
}

function thead(columns) {
  const head = document.createElement("thead");
  const row = document.createElement("tr");
  columns.forEach(col => row.append(cell("th", col)));
  head.append(row);
  return head;
}

function tbody(items, columns) {
  const body = document.createElement("tbody");
  items.forEach(item => {
    const row = document.createElement("tr");
    const data = flat(item);
    columns.forEach(col => row.append(cell("td", stringify(data[col]))));
    body.append(row);
  });
  return body;
}

function cards(value) {
  const items = rows(value);
  if (!items.length) return [empty("Nothing to show yet.")];
  return items.slice(0, 12).map((item, index) => card(titleFor(item, index), stringify(item)));
}

function detail(value) {
  if (!value || typeof value !== "object") return pre(stringify(value));
  const dl = document.createElement("dl");
  Object.entries(flat(value)).slice(0, 24).forEach(([key, val]) => {
    dl.append(Object.assign(document.createElement("dt"), { textContent: key }));
    dl.append(Object.assign(document.createElement("dd"), { textContent: stringify(val) }));
  });
  return dl;
}

function tree(value) {
  const box = document.createElement("div");
  box.className = "tree";
  treeRows(value).forEach((row, depth) => {
    const span = document.createElement("span");
    span.style.setProperty("--depth", row.depth ?? depth);
    span.textContent = row.label || stringify(row);
    box.append(span);
  });
  return box;
}

function treeRows(value) {
  if (Array.isArray(value)) return value;
  if (typeof value === "string") return value.split("\n").filter(Boolean).map(label => ({ label }));
  return rows(value);
}

function card(title, text) {
  const article = document.createElement("article");
  article.className = "mini-card";
  article.append(Object.assign(document.createElement("b"), { textContent: title }));
  article.append(Object.assign(document.createElement("p"), { textContent: text }));
  return article;
}

function pre(text) {
  const node = document.createElement("pre");
  node.className = "output";
  node.textContent = text;
  return node;
}

function empty(text) {
  const node = document.createElement("p");
  node.className = "empty";
  node.textContent = text;
  return node;
}

function cell(tag, text) {
  return Object.assign(document.createElement(tag), { textContent: text });
}

function flat(value, prefix = "") {
  if (!value || typeof value !== "object" || Array.isArray(value)) return { [prefix || "value"]: value };
  return Object.entries(value).reduce((out, [key, val]) => {
    const name = prefix ? `${prefix}.${key}` : key;
    if (val && typeof val === "object" && !Array.isArray(val)) return { ...out, ...flat(val, name) };
    return { ...out, [name]: val };
  }, {});
}

function titleFor(item, index) {
  return item.title || item.name || item.id || item.key || `Item ${index + 1}`;
}

function stringify(value) {
  if (value === undefined || value === null) return "";
  return typeof value === "string" ? value : JSON.stringify(value, null, 2);
}