{"version":3,"file":"createExtraKeyHandler-DwvmPya4.mjs","names":[],"sources":["../src/lib/pooling/showCombinedLogs.ts","../src/lib/pooling/keymap.ts","../src/lib/pooling/replayFileTailToStdout.ts","../src/lib/pooling/workerIds.ts","../src/lib/pooling/installInteractiveSwitcher.ts","../src/lib/pooling/dashboardPlugin.ts","../src/lib/pooling/uiPlugins.ts","../src/lib/pooling/createExtraKeyHandler.ts"],"sourcesContent":["/* eslint-disable no-continue, no-control-regex */\nimport { readFileSync } from 'node:fs';\n\nimport type { WorkerLogPaths } from '@transcend-io/utils';\n\n/**\n * Log locations\n */\nexport type LogLocation = 'out' | 'err' | 'structured' | 'warn' | 'info';\n\n/**\n * Which logs to show in the combined output.\n * Can include 'out' (stdout), 'err' (stderr), 'structured' (\n */\nexport type WhichLogs = Array<LogLocation>;\n\n/**\n * Show combined logs from all worker processes.\n *\n * @param slotLogPaths - Map of worker IDs to their log file paths.\n * @param whichList - one or more sources to include (e.g., ['err','out'])\n * @param filterLevel - 'error', 'warn', or 'all' to filter log levels.\n */\nexport function showCombinedLogs(\n  slotLogPaths: Map<number, WorkerLogPaths | undefined>,\n  whichList: WhichLogs,\n  filterLevel: 'error' | 'warn' | 'all',\n): void {\n  process.stdout.write('\\x1b[2J\\x1b[H');\n\n  const isError = (t: string): boolean =>\n    /\\b(ERROR|uncaughtException|unhandledRejection)\\b/i.test(t);\n  const isWarnTag = (t: string): boolean => /\\b(WARN|WARNING)\\b/i.test(t);\n\n  const lines: string[] = [];\n\n  for (const [, paths] of slotLogPaths) {\n    if (!paths) continue;\n\n    const files: Array<{\n      /** Absolute file path to read from */\n      path: string;\n      /**   Source type for this file, used for classification */\n      src: LogLocation;\n    }> = [];\n    for (const which of whichList) {\n      if (which === 'out' && paths.outPath) {\n        files.push({ path: paths.outPath, src: 'out' });\n      }\n      if (which === 'err' && paths.errPath) {\n        files.push({ path: paths.errPath, src: 'err' });\n      }\n      if (which === 'structured' && paths.structuredPath) {\n        files.push({ path: paths.structuredPath, src: 'structured' });\n      }\n      if (paths.warnPath && which === 'warn') {\n        files.push({ path: paths.warnPath, src: 'warn' });\n      }\n      if (paths.infoPath && which === 'info') {\n        files.push({ path: paths.infoPath, src: 'info' });\n      }\n    }\n\n    for (const { path, src } of files) {\n      let text = '';\n      try {\n        text = readFileSync(path, 'utf8');\n      } catch {\n        continue;\n      }\n\n      for (const ln of text.split('\\n')) {\n        if (!ln) continue;\n\n        const clean = ln.replace(/\\x1B\\[[0-9;]*m/g, '');\n\n        if (filterLevel === 'all') {\n          lines.push(ln);\n          continue;\n        }\n\n        if (filterLevel === 'error') {\n          if (isError(clean)) lines.push(ln);\n          continue;\n        }\n\n        // filterLevel === 'warn'\n        // Accept:\n        //  - explicit WARN tag anywhere\n        //  - OR lines from stderr that are NOT explicit errors (many warn libs print to stderr)\n        //  - OR lines containing the word \"warning\" (common in some libs)\n        if (isWarnTag(clean) || (src === 'err' && !isError(clean))) {\n          lines.push(ln);\n          continue;\n        }\n      }\n    }\n  }\n\n  // simple time-sort; each worker often prefixes ISO timestamps\n  lines.sort((a, b) => {\n    const ta = a.match(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/)?.[0] ?? '';\n    const tb = b.match(/\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/)?.[0] ?? '';\n    return ta.localeCompare(tb);\n  });\n\n  process.stdout.write(`${lines.join('\\n')}\\n`);\n  process.stdout.write('\\nPress Esc/Ctrl+] to return to dashboard.\\n');\n}\n/* eslint-enable no-continue, no-control-regex */\n","import type * as readline from 'node:readline';\n\n/**\n * Map a key press to an action in the interactive dashboard.\n */\nexport type Action =\n  | {\n      /** Indicates attaching to a session by id. */\n      type: 'ATTACH';\n      /** The id of the session to attach to. */\n      id: number;\n    }\n  | {\n      /** Indicates cycling through sessions. */\n      type: 'CYCLE';\n      /** The direction to cycle: +1 for next, -1 for previous. */\n      delta: number;\n    }\n  | {\n      /** Indicates detaching from the current session. */\n      type: 'DETACH';\n    }\n  | {\n      /** Indicates the Ctrl+C key combination was pressed. */\n      type: 'CTRL_C';\n    }\n  | {\n      /** Indicates the Ctrl+D key combination was pressed. */\n      type: 'CTRL_D';\n    }\n  | {\n      /** Indicates quitting the dashboard. */\n      type: 'QUIT';\n    }\n  | {\n      /** Forwards an unhandled key sequence. */\n      type: 'FORWARD';\n      /** The key sequence to forward. */\n      sequence: string;\n    };\n\n/**\n * Map a key press to an action in the interactive dashboard.\n *\n * @param str - The string representation of the key press.\n * @param key - The key object containing details about the key press.\n * @param mode - The current mode of the dashboard, either 'dashboard' or 'attached'.\n * @returns An Action object representing the mapped action, or null if no action is mapped.\n */\nexport function keymap(\n  str: string,\n  key: readline.Key,\n  mode: 'dashboard' | 'attached',\n): Action | null {\n  if (key.ctrl && key.name === 'c') return { type: 'CTRL_C' };\n\n  if (mode === 'dashboard') {\n    if (key.name && /^[0-9]$/.test(key.name)) {\n      return { type: 'ATTACH', id: Number(key.name) };\n    }\n    if (key.name === 'tab' && !key.shift) return { type: 'CYCLE', delta: +1 };\n    if (key.name === 'tab' && key.shift) return { type: 'CYCLE', delta: -1 };\n    if (key.name === 'q') return { type: 'QUIT' };\n    return null;\n  }\n\n  // attached\n  if (key.name === 'escape' || (key.ctrl && key.name === ']')) {\n    return { type: 'DETACH' };\n  }\n  if (key.ctrl && key.name === 'd') return { type: 'CTRL_D' };\n\n  const sequence = key.sequence ?? str ?? '';\n  return sequence ? { type: 'FORWARD', sequence } : null;\n}\n","import { createReadStream, statSync } from 'node:fs';\n\n/**\n * Replay the tail of a file to stdout.\n *\n * @param path - The absolute path to the file to read.\n * @param maxBytes - The maximum number of bytes to read from the end of the file.\n * @param write - A function to write the output to stdout.\n */\nexport async function replayFileTailToStdout(\n  path: string,\n  maxBytes: number,\n  write: (s: string) => void,\n): Promise<void> {\n  await new Promise<void>((resolve) => {\n    try {\n      const st = statSync(path);\n      const start = Math.max(0, st.size - maxBytes);\n      const stream = createReadStream(path, { start, encoding: 'utf8' });\n      stream.on('data', (chunk) => write(chunk as string));\n      stream.on('end', () => resolve());\n      stream.on('error', () => resolve());\n    } catch {\n      resolve();\n    }\n  });\n}\n","import type { ChildProcess } from 'node:child_process';\n\n/**\n * Get the sorted list of worker IDs from a map of ChildProcess instances.\n *\n * @param m - Map of worker IDs to ChildProcess instances.\n * @returns Sorted array of worker IDs.\n */\nexport function getWorkerIds(m: Map<number, ChildProcess>): number[] {\n  return [...m.keys()].sort((a, b) => a - b);\n}\n\n/**\n * Cycles through an array of numeric IDs, returning the next ID based on a delta.\n *\n * If the `current` ID is not provided or not found in the array, the first ID is used as the starting point.\n * The function then moves forward or backward in the array by `delta` positions, wrapping around if necessary.\n *\n * @param ids - Array of numeric IDs to cycle through.\n * @param current - The current ID to start cycling from. If `null` or not found, starts from the first ID.\n * @param delta - The number of positions to move forward (positive) or backward (negative) in the array.\n * @returns The next ID in the array after cycling, or `null` if the array is empty.\n */\nexport function cycleWorkers(ids: number[], current: number | null, delta: number): number | null {\n  if (!ids.length) return null;\n  const cur = current == null ? ids[0] : current;\n  let i = ids.indexOf(cur);\n  if (i === -1) i = 0;\n  i = (i + delta + ids.length) % ids.length;\n  return ids[i]!;\n}\n","import type { ChildProcess } from 'node:child_process';\nimport * as readline from 'node:readline';\n\nimport type { WorkerLogPaths } from '@transcend-io/utils';\n\nimport { DEBUG } from '../../constants.js';\nimport { keymap } from './keymap.js';\nimport { replayFileTailToStdout } from './replayFileTailToStdout.js';\nimport type { WhichLogs } from './showCombinedLogs.js';\nimport { cycleWorkers, getWorkerIds } from './workerIds.js';\n\n/**\n * Key action types for the interactive switcher\n */\nexport type InteractiveDashboardMode = 'dashboard' | 'attached';\n\nexport interface SwitcherPorts {\n  /** Standard input stream */\n  stdin: NodeJS.ReadStream;\n  /** Standard output stream */\n  stdout: NodeJS.WriteStream;\n  /** Standard error stream */\n  stderr: NodeJS.WriteStream;\n}\n\n/**\n * Install an interactive switcher for managing worker processes.\n *\n * @param opts - Options for the switcher\n * @returns A cleanup function to remove the switcher\n */\nexport function installInteractiveSwitcher(opts: {\n  /** Registry of live workers by id */\n  workers: Map<number, ChildProcess>;\n  /** Hooks */\n  onAttach?: (id: number) => void;\n  /** Optional detach handler */\n  onDetach?: () => void;\n  /** Optional Ctrl+C handler for parent graceful shutdown in dashboard */\n  onCtrlC?: () => void; // parent graceful shutdown in dashboard\n  /** Provide log paths so we can replay the tail on attach */\n  getLogPaths?: (id: number) => WorkerLogPaths | undefined;\n  /** How many bytes to replay from the end of each file (default 200 KB) */\n  replayBytes?: number;\n  /** Which logs to replay first (default ['out','err']) */\n  replayWhich?: WhichLogs;\n  /** Print a small banner/clear screen before replaying (optional) */\n  onEnterAttachScreen?: (id: number) => void;\n  /** Optional stdio ports for testing; defaults to process stdio */\n  ports?: SwitcherPorts;\n}): () => void {\n  const {\n    workers,\n    onAttach,\n    onDetach,\n    onCtrlC,\n    getLogPaths,\n    replayBytes = 200 * 1024,\n    replayWhich = ['out', 'err'],\n    onEnterAttachScreen,\n    ports,\n  } = opts;\n\n  const stdin = ports?.stdin ?? process.stdin;\n  const stdout = ports?.stdout ?? process.stdout;\n  const stderr = ports?.stderr ?? process.stderr;\n\n  const d = (...a: unknown[]): void => {\n    if (DEBUG) {\n      try {\n        (ports?.stderr ?? process.stderr).write(`[keys] ${a.map(String).join(' ')}\\n`);\n      } catch {\n        // noop\n      }\n    }\n  };\n\n  if (!stdin.isTTY) {\n    // Not a TTY; return a no-op cleanup\n    return () => {\n      // noop\n    };\n  }\n\n  readline.emitKeypressEvents(stdin);\n  stdin.setRawMode?.(true);\n\n  let mode: InteractiveDashboardMode = 'dashboard';\n  let focus: number | null = null;\n\n  // live mirroring handlers while attached\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  let outHandler: ((chunk: any) => void) | null = null;\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  let errHandler: ((chunk: any) => void) | null = null;\n\n  /**\n   * Cycle through worker IDs, wrapping around.\n   *\n   * @param id - The current worker ID to start cycling from.\n   * @returns The next worker ID after cycling, or null if no workers are available.\n   */\n  async function replayLogs(id: number): Promise<void> {\n    if (!getLogPaths) return;\n    const paths = getLogPaths(id);\n    if (!paths) return;\n\n    const toReplay: string[] = [];\n    for (const which of replayWhich) {\n      if (which === 'out') toReplay.push(paths.outPath);\n      if (which === 'err') toReplay.push(paths.errPath);\n      if (which === 'structured') toReplay.push(paths.structuredPath);\n    }\n\n    if (toReplay.length) {\n      stdout.write('\\n------------ replay ------------\\n');\n      for (const p of toReplay) {\n        stdout.write(`\\n--- ${p} (last ~${Math.floor(replayBytes / 1024)}KB) ---\\n`);\n        await replayFileTailToStdout(p, replayBytes, (s) => stdout.write(s));\n      }\n      stdout.write('\\n--------------------------------\\n\\n');\n    }\n  }\n\n  const attach = async (id: number): Promise<void> => {\n    d('attach()', `id=${id}`); // at function entry\n\n    const w = workers.get(id);\n    if (!w) return;\n\n    // Detach any previous focus\n    if (mode === 'attached') detach();\n\n    mode = 'attached';\n    focus = id;\n\n    // UX: clear + banner\n    onEnterAttachScreen?.(id);\n\n    onAttach?.(id); // prints “Attached to worker …” and clears\n    await replayLogs(id); // now the tail stays visible\n\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    outHandler = (chunk: any) => stdout.write(chunk);\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    errHandler = (chunk: any) => stderr.write(chunk);\n    w.stdout?.on('data', outHandler);\n    w.stderr?.on('data', errHandler);\n\n    // auto-detach if child exits\n    const onExit = (): void => {\n      if (focus === id) detach();\n    };\n    w.once('exit', onExit);\n  };\n\n  const detach = (): void => {\n    d('detach()', `id=${focus}`); // at function entry\n\n    if (focus == null) return;\n    const id = focus;\n    const w = workers.get(id);\n    if (w) {\n      if (outHandler) w.stdout?.off('data', outHandler);\n      if (errHandler) w.stderr?.off('data', errHandler);\n    }\n    outHandler = null;\n    errHandler = null;\n    focus = null;\n    mode = 'dashboard';\n    onDetach?.();\n  };\n\n  const onKey = (str: string, key: readline.Key): void => {\n    d(\n      'keypress',\n      JSON.stringify({\n        str,\n        name: key.name,\n        seq: key.sequence,\n        ctrl: key.ctrl,\n        meta: key.meta,\n        shift: key.shift,\n        mode,\n      }),\n    );\n    const act = keymap(str, key, mode);\n    d('mapped', JSON.stringify(act));\n\n    if (!act) return;\n\n    // eslint-disable-next-line default-case\n    switch (act.type) {\n      case 'CTRL_C': {\n        d('CTRL_C');\n        if (mode === 'attached' && focus != null) {\n          const w = workers.get(focus);\n          try {\n            w?.kill('SIGINT');\n          } catch {\n            // noop\n          }\n          // optional: auto-detach so second Ctrl+C exits parent\n          detach();\n          return;\n        }\n        onCtrlC?.();\n        return;\n      }\n\n      case 'ATTACH': {\n        d('ATTACH', `id=${act.id}`, `has=${workers.has(act.id)}`);\n\n        if (mode !== 'dashboard') return;\n        // eslint-disable-next-line no-void\n        if (workers.has(act.id)) void attach(act.id);\n        return;\n      }\n\n      case 'CYCLE': {\n        d('CYCLE', `delta=${act.delta}`);\n        if (mode !== 'dashboard') return;\n        const next = cycleWorkers(getWorkerIds(workers), focus, act.delta);\n        // eslint-disable-next-line no-void\n        if (next != null) void attach(next);\n        return;\n      }\n\n      case 'QUIT': {\n        if (mode !== 'dashboard') return;\n        onCtrlC?.();\n        return;\n      }\n\n      case 'DETACH': {\n        d('DETACH');\n        if (mode === 'attached') detach();\n        return;\n      }\n\n      case 'CTRL_D': {\n        if (mode === 'attached' && focus != null) {\n          const w = workers.get(focus);\n          try {\n            w?.stdin?.end();\n          } catch {\n            // noop\n          }\n        }\n        return;\n      }\n\n      case 'FORWARD': {\n        if (mode === 'attached' && focus != null) {\n          const w = workers.get(focus);\n          try {\n            w?.stdin?.write(act.sequence);\n          } catch {\n            // noop\n          }\n        }\n      }\n    }\n  };\n\n  // Raw bytes fallback (usually not hit because keypress handles it)\n  const onData = (chunk: Buffer): void => {\n    if (mode === 'attached' && focus != null) {\n      const w = workers.get(focus);\n      try {\n        w?.stdin?.write(chunk);\n      } catch {\n        // noop\n      }\n    }\n  };\n\n  const cleanup = (): void => {\n    stdin.off('keypress', onKey);\n    stdin.off('data', onData);\n    stdin.setRawMode?.(false);\n    stdout.write('\\x1b[?25h');\n  };\n\n  stdin.on('keypress', onKey);\n  stdin.on('data', onData);\n\n  return cleanup;\n}\n","// lib/pooling/dashboardPlugin.ts\nimport * as readline from 'node:readline';\n\nimport type { ObjByString } from '@transcend-io/type-utils';\nimport type { SlotState } from '@transcend-io/utils';\nimport colors from 'colors';\n\n/**\n * A dashboard plugin defines how to render the worker pool UI.\n * Commands can supply a plugin to customize:\n *   - The header block (summary stats, title, etc.)\n *   - Per-worker rows (one line per worker slot)\n *   - Optional extras (artifact exports, breakdowns, footers)\n *\n * @template TTotals - The shape of the aggregate totals object maintained by the command.\n */\nexport interface DashboardPlugin<TTotals, TSlotState extends ObjByString> {\n  /**\n   * Render the header block of the dashboard.\n   *\n   * @param ctx - Context with pool/worker state, totals, and metadata.\n   * @returns An array of strings, each representing one line in the header.\n   */\n  renderHeader: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n\n  /**\n   * Render per-worker rows, usually one line per worker slot.\n   *\n   * @param ctx - Context with pool/worker state, totals, and metadata.\n   * @returns An array of strings, each representing one row in the workers section.\n   */\n  renderWorkers: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n\n  /**\n   * Render any optional extra blocks that appear after the worker rows.\n   * Useful for printing export paths, aggregated metrics, breakdowns, etc.\n   *\n   * @param ctx - Context with pool/worker state, totals, and metadata.\n   * @returns An array of strings, each representing one additional line.\n   */\n  renderExtras?: (ctx: CommonCtx<TTotals, TSlotState>) => string[];\n}\n\n/**\n * Shared context object passed into all render methods of a {@link DashboardPlugin}.\n *\n * @template TTotals - The shape of the aggregate totals object maintained by the command.\n */\nexport type CommonCtx<TTotals, TSlotState extends ObjByString> = {\n  /** Human-readable title for the dashboard (e.g., \"Parallel uploader\"). */\n  title: string;\n\n  /** Number of worker processes spawned in the pool. */\n  poolSize: number;\n\n  /** Logical CPU count, included for informational display. */\n  cpuCount: number;\n\n  /** Total number of \"files\" or logical units the command expects to process. */\n  filesTotal: number;\n\n  /** Count of successfully completed files/tasks. */\n  filesCompleted: number;\n\n  /** Count of failed files/tasks. */\n  filesFailed: number;\n\n  /**\n   * State of each worker slot, keyed by worker id.\n   * Includes busy flag, file label, start time, last log badge, and progress.\n   */\n  workerState: Map<number, SlotState<TSlotState>>;\n\n  /**\n   * Aggregate totals maintained by the command’s hook logic.\n   * Domain-specific metrics (e.g., rows uploaded, bytes processed) can be surfaced here.\n   */\n  totals: TTotals;\n\n  /**\n   * Throughput metrics tracked by the runner:\n   * - successSoFar: convenience alias for completed count\n   * - r10s: completions/sec averaged over last 10s\n   * - r60s: completions/sec averaged over last 60s\n   */\n  throughput: {\n    /** Cumulative count of successful completions so far. */\n    successSoFar: number;\n    /** Recent file-level throughput rate over the last 10 seconds. */\n    r10s: number;\n    /** Recent file-level throughput rate over the last 60 seconds. */\n    r60s: number;\n    /** Recent job/record-level throughput rate over the last 10 seconds. */\n    jobsR10s: number;\n    /** Recent job/record-level throughput rate over the last 60 seconds. */\n    jobsR60s: number;\n  };\n\n  /** True when the pool has fully drained and all workers have exited. */\n  final: boolean;\n\n  /**\n   * Optional export status payload provided by the command.\n   * Useful for rendering artifact paths or \"latest export\" summaries.\n   */\n  exportStatus?: Record<string, unknown>;\n};\n\n/** The most recently rendered frame, cached to suppress flicker from duplicate renders. */\nlet lastFrame = '';\n\n/**\n * Generate the hotkeys hint string that appears at the bottom of the dashboard.\n *\n * @param poolSize - The number of worker slots in the pool.\n * @param final - Whether the run has completed.\n * @returns A dimmed string listing the supported hotkeys for attach/detach/quit.\n */\nexport const hotkeysHint = (poolSize: number, final: boolean): string => {\n  const maxDigit = Math.min(poolSize - 1, 9);\n  const digitRange = poolSize <= 1 ? '0' : `0-${maxDigit}`;\n  const extra = poolSize > 10 ? ' (Tab/Shift+Tab for ≥10)' : '';\n  return final\n    ? colors.dim(\n        'Run complete — digits to view logs • Tab/Shift+Tab cycle • Esc/Ctrl+] detach • q to quit',\n      )\n    : colors.dim(\n        `Hotkeys: [${digitRange}] attach${extra} • e=errors • w=warnings • i=info • l=logs • ` +\n          'Tab/Shift+Tab • Esc/Ctrl+] detach • Ctrl+C exit',\n      );\n};\n\n/**\n * Render the dashboard using a supplied {@link DashboardPlugin}.\n *\n * The frame is composed of:\n *   - Header lines\n *   - A blank separator\n *   - Worker rows\n *   - A blank separator\n *   - Hotkeys hint\n *   - Optional extras (if plugin supplies them)\n *\n * Optimizations:\n *   - Suppresses re-renders if the frame is identical to the previous frame (flicker-free).\n *   - Hides the terminal cursor during live updates, restoring it when final.\n *\n * @param ctx - Shared context containing pool state, worker state, totals, throughput, etc.\n * @param plugin - The plugin that defines how to render the header, workers, and optional extras.\n * @param viewerMode - If true, renders in viewer mode (no ability to switch between files).\n */\nexport function dashboardPlugin<TTotals, TSlotState extends ObjByString>(\n  ctx: CommonCtx<TTotals, TSlotState>,\n  plugin: DashboardPlugin<TTotals, TSlotState>,\n  viewerMode = false,\n): void {\n  const frame = [\n    ...plugin.renderHeader(ctx),\n    '',\n    ...plugin.renderWorkers(ctx),\n    ...(viewerMode ? [] : ['', hotkeysHint(ctx.poolSize, ctx.final)]),\n    ...(plugin.renderExtras ? [''].concat(plugin.renderExtras(ctx)) : []),\n  ].join('\\n');\n\n  // Skip duplicate renders during live runs to avoid flicker.\n  if (!ctx.final && frame === lastFrame) return;\n  lastFrame = frame;\n\n  if (!ctx.final) {\n    // Hide cursor and repaint in place\n    process.stdout.write('\\x1b[?25l');\n    readline.cursorTo(process.stdout, 0, 0);\n    readline.clearScreenDown(process.stdout);\n  } else {\n    // Restore cursor on final render\n    process.stdout.write('\\x1b[?25h');\n  }\n  process.stdout.write(`${frame}\\n`);\n}\n","import { basename } from 'node:path';\n\nimport type { ObjByString } from '@transcend-io/type-utils';\nimport colors from 'colors';\n\nimport type { CommonCtx } from './dashboardPlugin.js';\n\n/**\n * Progress snapshot for a worker slot in the chunk-csv command.\n */\nexport type ChunkSlotProgress = {\n  /** Absolute path of the file being processed by this worker. */\n  filePath?: string;\n  /** Number of rows processed so far in this file. */\n  processed?: number;\n  /** Optional total number of rows in the file (if known). */\n  total?: number;\n};\n\n/**\n * Format a number safely for display.\n *\n * @param n - The number to format (or `undefined`).\n * @returns A localized string representation, or \"0\".\n */\nexport function fmtNum(n: number | undefined): string {\n  return typeof n === 'number' ? n.toLocaleString() : '0';\n}\n\n/**\n * Draw a horizontal bar of length `width` filled to `pct` percent.\n *\n * @param pct - Percentage 0..100.\n * @param width - Number of characters in the bar.\n * @returns A string like \"████░░░░\".\n */\nexport function pctBar(pct: number, width = 40): string {\n  const clamped = Math.max(0, Math.min(100, Math.floor(pct)));\n  const filled = Math.floor((clamped / 100) * width);\n  return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\n/**\n * Compute pool-wide progress values needed by headers.\n *\n * @param ctx - Dashboard context containing pool state, worker state, totals, etc.\n * @returns An object with `done`, `inProgress`, and `pct` properties.\n */\nexport function poolProgress<TTotals, TSlot extends ObjByString>(\n  ctx: CommonCtx<TTotals, TSlot>,\n): {\n  /** Count of successfully completed files/tasks. */\n  done: number;\n  /** Count of currently in-progress files/tasks. */\n  inProgress: number;\n  /** Percentage of completion (0-100). */\n  pct: number;\n} {\n  const inProgress = [...ctx.workerState.values()].filter((s) => s.busy).length;\n  const done = ctx.filesCompleted + ctx.filesFailed;\n  const pct = ctx.filesTotal === 0 ? 100 : Math.floor((done / Math.max(1, ctx.filesTotal)) * 100);\n  return { done, inProgress, pct };\n}\n\n/**\n * Compose the common header lines (title, pool stats, progress bar, throughput).\n *\n * @param ctx - Dashboard context.\n * @param extraLines - Optional extra lines (e.g., totals block).\n * @returns Header lines.\n */\nexport function makeHeader<TTotals, TSlot extends ObjByString>(\n  ctx: CommonCtx<TTotals, TSlot>,\n  extraLines: string[] = [],\n): string[] {\n  const { title, poolSize, cpuCount, filesTotal, filesCompleted, filesFailed, throughput } = ctx;\n  const { inProgress, pct } = poolProgress(ctx);\n\n  const lines: string[] = [\n    `${colors.bold(title)} — ${poolSize} workers ${colors.dim(`(CPU avail: ${cpuCount})`)}`,\n    `${colors.dim('Files')} ${fmtNum(filesTotal)}  ${colors.dim(\n      'Completed',\n    )} ${fmtNum(filesCompleted)}  ${colors.dim('Failed')} ${\n      filesFailed ? colors.red(fmtNum(filesFailed)) : fmtNum(filesFailed)\n    }  ${colors.dim('In-flight')} ${fmtNum(inProgress)}`,\n    `[${pctBar(pct)}] ${pct}%`,\n  ];\n\n  if (throughput) {\n    const jobsActive = throughput.jobsR10s > 0 || throughput.jobsR60s > 0;\n    const perHour10 = Math.round(\n      (jobsActive ? throughput.jobsR10s : throughput.r10s) * 3600,\n    ).toLocaleString();\n    const perHour60 = Math.round(\n      (jobsActive ? throughput.jobsR60s : throughput.r60s) * 3600,\n    ).toLocaleString();\n    const unit = jobsActive ? 'rec' : 'files';\n    const suffix =\n      ctx.throughput?.successSoFar != null\n        ? `  Newly uploaded: ${fmtNum(ctx.throughput.successSoFar)}`\n        : '';\n    lines.push(\n      colors.cyan(`Throughput: ${perHour10} ${unit}/hr (1h: ${perHour60} ${unit}/hr)${suffix}`),\n    );\n  }\n\n  return extraLines.length ? lines.concat(extraLines) : lines;\n}\n\n/**\n * Render per-worker rows with a compact progress bar and status badge.\n *\n * @param ctx - Dashboard context (slot progress type must have processed/total?).\n * @param getFileLabel - Optional: override how the filename is shown.\n * @returns Array of strings, each representing one worker row.\n */\nexport function makeWorkerRows<TTotals, TSlot extends Omit<ChunkSlotProgress, 'filePath'>>(\n  ctx: CommonCtx<TTotals, TSlot>,\n  getFileLabel: (file: string | null | undefined) => string = (file) =>\n    file ? basename(file) : '-',\n): string[] {\n  const miniWidth = 18;\n\n  return [...ctx.workerState.entries()].map(([id, s]) => {\n    const badge =\n      s.lastLevel === 'error'\n        ? colors.red('ERROR ')\n        : s.lastLevel === 'warn'\n          ? colors.yellow('WARN  ')\n          : s.busy\n            ? colors.green('WORKING')\n            : colors.dim('IDLE   ');\n\n    const fname = getFileLabel(s.file);\n    const elapsed = s.startedAt ? `${Math.floor((Date.now() - s.startedAt) / 1000)}s` : '-';\n\n    const processed = s.progress?.processed ?? 0;\n    const total = s.progress?.total ?? 0;\n    const pctw = total > 0 ? Math.floor((processed / total) * 100) : 0;\n    const mini = total > 0 ? pctBar(pctw, miniWidth) : ' '.repeat(miniWidth);\n    const miniTxt =\n      total > 0\n        ? `${processed.toLocaleString()}/${total.toLocaleString()} (${pctw}%)`\n        : colors.dim('—');\n\n    return `  [w${id}] ${badge} | ${fname} | ${elapsed} | [${mini}] ${miniTxt}`;\n  });\n}\n","import type { ExportStatusMap, SlotPaths } from '@transcend-io/utils';\n\nimport { showCombinedLogs, type LogLocation } from './showCombinedLogs.js';\n\n/** Severity filter applied by the viewer. */\ntype ViewLevel = 'error' | 'warn' | 'all';\n\n/**\n * Options for {@link createExtraKeyHandler}.\n */\nexport type CreateExtraKeyHandlerOpts = {\n  /**\n   * Per-slot log file paths maintained by the runner; used to stream or export logs.\n   */\n  logsBySlot: SlotPaths;\n\n  /**\n   * Request an immediate dashboard repaint (e.g., after updating export status).\n   */\n  repaint: () => void;\n\n  /**\n   * Pause/unpause dashboard repainting. The handler pauses while a viewer is open\n   * to prevent the dashboard from overwriting the viewer output, then resumes on exit.\n   */\n  setPaused: (p: boolean) => void;\n\n  /**\n   * Optional export manager to enable uppercase export keys:\n   * - `E` (errors) • `W` (warnings) • `I` (info) • `A` (all)\n   *\n   * Provide this only if your command supports writing combined log files.\n   */\n  exportMgr?: {\n    /** Destination directory for exported artifacts. */\n    exportsDir: string;\n    /**\n     * Write a combined log file for the selected severity and return the absolute path.\n     *\n     * @param logs - Log paths to combine.\n     * @param which - Severity selection.\n     * @returns Absolute path to the written file.\n     */\n    exportCombinedLogs: (logs: SlotPaths, which: 'error' | 'warn' | 'info' | 'all') => string;\n  };\n\n  /**\n   * Optional “Exports” status map. If provided, the handler updates timestamps\n   * when exports are written so your dashboard panel can reflect “last saved” times.\n   */\n  exportStatus?: ExportStatusMap;\n\n  /**\n   * Optional custom key bindings for command-specific actions.\n   * Each handler receives helpers to print messages and to update the exports panel.\n   *\n   * Example:\n   * ```ts\n   * custom: {\n   *   F: async ({ say, noteExport }) => {\n   *     const p = await writeFailingUpdatesCsv(...);\n   *     say(`Wrote failing updates to: ${p}`);\n   *     noteExport('failuresCsv', p);\n   *   }\n   * }\n   * ```\n   */\n  custom?: Record<\n    string,\n    (ctx: {\n      /** Update {@link exportStatus} (if present) and repaint the dashboard. */\n      noteExport: (slot: keyof ExportStatusMap, absPath: string) => void;\n      /** Print a line to stdout, automatically newline-terminated. */\n      say: (s: string) => void;\n    }) => void | Promise<void>\n  >;\n};\n\n/**\n * Create a keypress handler for interactive viewers/exports.\n * Shared handler for \"extra\" keyboard shortcuts used by the interactive dashboard.\n *\n * It wires:\n * - **Viewers (lowercase):** `e` (errors), `w` (warnings), `i` (info), `l` (all)\n * - **Exports (uppercase, optional):** `E` (errors), `W` (warnings), `I` (info), `A` (all)\n * - **Dismiss:** `Esc` or `Ctrl+]` exits a viewer and returns to the dashboard\n * - **Custom keys (optional):** Provide a `custom` map to handle command-specific bindings\n *\n * Usage (inside `runPool({... extraKeyHandler })`):\n * ```ts\n * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n *   createExtraKeyHandler({ logsBySlot, repaint, setPaused })\n * ```\n *\n * If you also want export hotkeys + an \"Exports\" panel:\n * ```ts\n * extraKeyHandler: ({ logsBySlot, repaint, setPaused }) =>\n *   createExtraKeyHandler({\n *     logsBySlot, repaint, setPaused,\n *     exportMgr,         // enables E/W/I/A\n *     exportStatus,      // keeps panel timestamps up to date\n *     custom: {          // optional, e.g. 'F' to export a CSV\n *       F: async ({ say, noteExport }) => { ... }\n *     }\n *   })\n * ```\n *\n * @param opts - Configuration for viewers, exports, and custom keys.\n * @returns A `(buf: Buffer) => void` handler suitable for `process.stdin.on('data', ...)`.\n */\nexport function createExtraKeyHandler(opts: CreateExtraKeyHandlerOpts): (buf: Buffer) => void {\n  const { logsBySlot, repaint, setPaused, exportMgr, exportStatus, custom } = opts;\n\n  const say = (s: string): void => {\n    process.stdout.write(`${s}\\n`);\n  };\n\n  /**\n   * Record that an export was written and trigger a repaint so the dashboard’s\n   * \"Exports\" panel shows the updated timestamp/path.\n   *\n   * @param slot - Slot name in {@link ExportStatusMap} (e.g., \"error\", \"warn\", etc.).\n   * @param p - Absolute path to the exported file.\n   */\n  const noteExport = (slot: keyof ExportStatusMap, p: string): void => {\n    const now = Date.now();\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const cur: any = exportStatus?.[slot] ?? { path: p };\n    if (exportStatus) {\n      exportStatus[slot] = {\n        path: p || cur.path,\n        savedAt: now,\n        exported: true,\n      };\n      repaint();\n    }\n  };\n\n  let viewing = false; // optional guard to prevent stacking viewers\n\n  /**\n   * Show an inline combined log viewer for the selected sources/level.\n   * Pauses dashboard repaint to keep the viewer visible until the user exits.\n   *\n   * @param sources - Log sources to include (e.g., \"err\", \"warn\", \"info\").\n   * @param level - Severity level to filter by (e.g., \"error\", \"warn\", \"all\").\n   */\n  const view = (sources: LogLocation[], level: ViewLevel): void => {\n    if (viewing) return;\n    viewing = true;\n    setPaused(true);\n\n    // optional UX: clear screen and show a hint\n    process.stdout.write('\\x1b[2J\\x1b[H'); // clear+home\n    process.stdout.write('Combined logs viewer (press Esc or Ctrl+] to return)\\n\\n');\n\n    (async () => {\n      try {\n        await showCombinedLogs(logsBySlot, sources, level);\n        // NOTE: do NOT unpause here; ESC will handle it.\n      } catch {\n        // If showCombinedLogs throws, recover and unpause\n        viewing = false;\n        setPaused(false);\n        repaint();\n      }\n    })();\n  };\n\n  /**\n   * Export combined logs (if an export manager was provided).\n   *\n   * @param which - Severity to export (e.g., \"error\", \"warn\", \"info\", \"all\").\n   * @param label - Human-readable label for the export (e.g., \"error\", \"warn\").\n   */\n  const exportCombined = (which: 'error' | 'warn' | 'info' | 'all', label: string): void => {\n    if (!exportMgr) return;\n    try {\n      const p = exportMgr.exportCombinedLogs(logsBySlot, which);\n      say(`\\nWrote combined ${label} logs to: ${p}`);\n      noteExport(which as keyof ExportStatusMap, p);\n    } catch {\n      say(`\\nFailed to write combined ${label} logs`);\n    }\n  };\n\n  // The keypress handler the runner will attach to stdin.\n  return (buf: Buffer): void => {\n    const s = buf.toString('utf8');\n\n    // Viewers (lowercase)\n    if (s === 'e') {\n      view(['err'], 'error');\n      return;\n    }\n    if (s === 'w') {\n      view(['warn', 'err'], 'warn');\n      return;\n    }\n    if (s === 'i') {\n      view(['info'], 'all');\n      return;\n    }\n    if (s === 'l') {\n      view(['out', 'err', 'structured'], 'all');\n      return;\n    }\n\n    // Exports (uppercase) — enabled only when exportMgr is present\n    if (s === 'E') {\n      exportCombined('error', 'error');\n      return;\n    }\n    if (s === 'W') {\n      exportCombined('warn', 'warn');\n      return;\n    }\n    if (s === 'I') {\n      exportCombined('info', 'info');\n      return;\n    }\n    if (s === 'A') {\n      exportCombined('all', 'ALL');\n      return;\n    }\n\n    // Command-specific bindings\n    const fn = custom?.[s];\n    if (fn) {\n      fn({ noteExport, say });\n      return;\n    }\n\n    // Exit a viewer (Esc / Ctrl+]) — resume dashboard\n    if (s === '\\x1b' || s === '\\x1d') {\n      viewing = false;\n      setPaused(false);\n      repaint();\n    }\n  };\n}\n"],"mappings":"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"}