UNPKG

@ideascol/cli-maker

Version:
201 lines (200 loc) 8.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.drawBox = drawBox; exports.drawTwoColumnBox = drawTwoColumnBox; exports.infoBar = infoBar; exports.separator = separator; exports.toolCallHeader = toolCallHeader; exports.toolCallArgs = toolCallArgs; exports.toolResultBox = toolResultBox; exports.sectionHeader = sectionHeader; exports.alignedList = alignedList; const colors_1 = require("../colors"); const common_1 = require("../common"); const BORDERS = { single: { tl: '┌', tr: '┐', bl: '└', br: '┘', h: '─', v: '│' }, double: { tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║' }, dashed: { tl: '┌', tr: '┐', bl: '└', br: '┘', h: '╌', v: '╎' }, rounded: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' }, }; /** * Draw a box around content lines with optional title. */ function drawBox(contentLines, opts = {}) { const border = BORDERS[opts.borderStyle ?? 'rounded']; const bColor = opts.borderColor ?? colors_1.Colors.FgGray; const pad = opts.padding ?? 1; const termWidth = process.stdout.columns || 80; const maxW = opts.maxWidth ?? termWidth - 4; const contentWidths = contentLines.map(l => (0, common_1.stripAnsiCodes)(l).length); let innerWidth = Math.max(...contentWidths, 10); if (opts.title) { innerWidth = Math.max(innerWidth, (0, common_1.stripAnsiCodes)(opts.title).length + 4); } innerWidth = Math.min(innerWidth, maxW - pad * 2 - 2); const totalInner = innerWidth + pad * 2; const padStr = ' '.repeat(pad); const output = []; // Top border if (opts.title) { const tColor = opts.titleColor ?? colors_1.Colors.Bright; const titleClean = (0, common_1.stripAnsiCodes)(opts.title); const remaining = Math.max(0, totalInner - titleClean.length - 3); output.push(`${bColor}${border.tl}${border.h} ${colors_1.Colors.Reset}${tColor}${opts.title}${colors_1.Colors.Reset}${bColor} ${border.h.repeat(remaining)}${border.tr}${colors_1.Colors.Reset}`); } else { output.push(`${bColor}${border.tl}${border.h.repeat(totalInner)}${border.tr}${colors_1.Colors.Reset}`); } // Content lines for (const line of contentLines) { const cleanLen = (0, common_1.stripAnsiCodes)(line).length; const rightPad = Math.max(0, innerWidth - cleanLen); output.push(`${bColor}${border.v}${colors_1.Colors.Reset}${padStr}${line}${' '.repeat(rightPad)}${padStr}${bColor}${border.v}${colors_1.Colors.Reset}`); } // Bottom border output.push(`${bColor}${border.bl}${border.h.repeat(totalInner)}${border.br}${colors_1.Colors.Reset}`); return output.join('\n'); } /** * Draw a two-column box panel (like Claude Code's welcome screen). * Spans the full terminal width with a ~45%/55% column split. */ function drawTwoColumnBox(leftLines, rightLines, opts = {}) { const border = BORDERS[opts.borderStyle ?? 'rounded']; const bColor = opts.borderColor ?? colors_1.Colors.FgGray; const pad = opts.padding ?? 1; const termWidth = process.stdout.columns || 80; const maxW = opts.maxWidth ?? termWidth; // Total inner width = maxW - 2 (for left + right border chars) const totalInner = maxW - 2; // Column split: left gets ~45%, right gets ~55% (minus divider and padding) const ratio = opts.leftRatio ?? 0.45; const divider = opts.dividerChar ?? border.v; // Layout: pad + leftW + pad + divider + pad + rightW + pad const overhead = pad * 4 + 1; // 4 pads + 1 divider const usable = totalInner - overhead; const leftW = Math.max(10, Math.floor(usable * ratio)); const rightW = Math.max(10, usable - leftW); const maxRows = Math.max(leftLines.length, rightLines.length); const padStr = ' '.repeat(pad); // Truncate a string (with ANSI) to fit a given visible width const truncate = (s, maxLen) => { const clean = (0, common_1.stripAnsiCodes)(s); if (clean.length <= maxLen) return s; // Simple truncation: walk the string and count visible chars let visible = 0; let i = 0; let inEsc = false; let result = ''; while (i < s.length && visible < maxLen - 1) { if (s[i] === '\x1b') { inEsc = true; result += s[i]; } else if (inEsc) { result += s[i]; if (/[a-zA-Z]/.test(s[i])) inEsc = false; } else { result += s[i]; visible++; } i++; } return result + '\u2026' + colors_1.Colors.Reset; }; const output = []; // Top border with optional title if (opts.title) { const tColor = opts.titleColor ?? colors_1.Colors.Bright; const titleClean = (0, common_1.stripAnsiCodes)(opts.title); const remaining = Math.max(0, totalInner - titleClean.length - 4); output.push(`${bColor}${border.tl}${border.h}${border.h}${border.h} ${colors_1.Colors.Reset}${tColor}${opts.title}${colors_1.Colors.Reset}${bColor} ${border.h.repeat(remaining)}${border.tr}${colors_1.Colors.Reset}`); } else { output.push(`${bColor}${border.tl}${border.h.repeat(totalInner)}${border.tr}${colors_1.Colors.Reset}`); } // Rows for (let i = 0; i < maxRows; i++) { const rawLeft = leftLines[i] ?? ''; const rawRight = rightLines[i] ?? ''; const left = truncate(rawLeft, leftW); const right = truncate(rawRight, rightW); const leftClean = (0, common_1.stripAnsiCodes)(left).length; const rightClean = (0, common_1.stripAnsiCodes)(right).length; const leftPadR = Math.max(0, leftW - leftClean); const rightPadR = Math.max(0, rightW - rightClean); output.push(`${bColor}${border.v}${colors_1.Colors.Reset}${padStr}${left}${' '.repeat(leftPadR)}${padStr}${bColor}${divider}${colors_1.Colors.Reset}${padStr}${right}${' '.repeat(rightPadR)}${padStr}${bColor}${border.v}${colors_1.Colors.Reset}`); } // Bottom border output.push(`${bColor}${border.bl}${border.h.repeat(totalInner)}${border.br}${colors_1.Colors.Reset}`); return output.join('\n'); } /** * Render an info bar (single line with dim background). */ function infoBar(text) { return `${colors_1.Colors.FgGray}${text}${colors_1.Colors.Reset}`; } /** * Render a separator line. */ function separator(char = '─', width) { const w = width ?? (process.stdout.columns || 80) - 2; return `${colors_1.Colors.FgGray}${char.repeat(w)}${colors_1.Colors.Reset}`; } /** * Render a tool call header (Claude-style ⏺ indicator with left accent). */ function toolCallHeader(toolName, description) { return ` ${colors_1.Colors.FgYellow}${colors_1.Colors.Reset} ${colors_1.Colors.Bright}${toolName}${colors_1.Colors.Reset}`; } /** * Render a tool call argument list. */ function toolCallArgs(args) { const lines = []; for (const [key, val] of Object.entries(args)) { const display = typeof val === 'string' ? val : JSON.stringify(val); lines.push(` ${colors_1.Colors.FgGray}${key}:${colors_1.Colors.Reset} ${display}`); } return lines.join('\n'); } /** * Render a tool result box (indented, with subtle border). */ function toolResultBox(result) { const lines = result.split('\n'); const output = []; for (const line of lines) { output.push(` ${colors_1.Colors.FgGray}${colors_1.Colors.Reset} ${line}`); } return output.join('\n'); } /** * Render a section header with aligned items (for /help, /tools). */ function sectionHeader(title) { return `\n ${colors_1.Colors.Bright}${colors_1.Colors.FgCyan}${title}${colors_1.Colors.Reset}`; } /** * Render aligned key-value rows (for /help listing). * Automatically calculates the widest key for alignment. */ function alignedList(items, indent = 4) { const indentStr = ' '.repeat(indent); const maxKeyLen = Math.max(...items.map(i => (0, common_1.stripAnsiCodes)(i.prefix ?? '').length + (0, common_1.stripAnsiCodes)(i.key).length)); const gap = 3; const lines = []; for (const item of items) { const prefix = item.prefix ?? ''; const prefixClean = (0, common_1.stripAnsiCodes)(prefix).length; const keyClean = (0, common_1.stripAnsiCodes)(item.key).length; const keyColor = item.keyColor ?? colors_1.Colors.FgGreen; const padding = ' '.repeat(Math.max(1, maxKeyLen - prefixClean - keyClean + gap)); lines.push(`${indentStr}${prefix}${keyColor}${item.key}${colors_1.Colors.Reset}${padding}${colors_1.Colors.FgGray}${item.value}${colors_1.Colors.Reset}`); } return lines.join('\n'); }