Files
english/.opencode/plugin/scout-block/error-formatter.cjs
2026-04-12 01:06:31 +07:00

162 lines
4.2 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* error-formatter.cjs - Rich, actionable error messages for scout-block
*
* Follows CLI UX best practices: Problem + Reason + Solution
* Supports ANSI colors with NO_COLOR env var respect.
*/
const path = require('path');
// ANSI color codes
const COLORS = {
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
bold: '\x1b[1m',
dim: '\x1b[2m',
reset: '\x1b[0m'
};
/**
* Check if terminal supports colors
* Respects NO_COLOR standard and FORCE_COLOR
*
* @returns {boolean}
*/
function supportsColor() {
// Respect NO_COLOR standard (https://no-color.org/)
if (process.env.NO_COLOR !== undefined) return false;
// Respect FORCE_COLOR
if (process.env.FORCE_COLOR !== undefined) return true;
// Check if stderr is TTY (we output errors to stderr)
return process.stderr.isTTY || false;
}
/**
* Apply color to text if supported
*
* @param {string} text - Text to colorize
* @param {string} color - Color name from COLORS
* @returns {string}
*/
function colorize(text, color) {
if (!supportsColor()) return text;
const colorCode = COLORS[color] || '';
return `${colorCode}${text}${COLORS.reset}`;
}
/**
* Get .ckignore config path
*
* @param {string} claudeDir - Path to .claude directory
* @param {string} [configPath] - Explicit config path to prefer
* @returns {string}
*/
function formatConfigPath(claudeDir, configPath) {
if (configPath) {
return configPath;
}
if (claudeDir) {
return path.join(claudeDir, '.ckignore');
}
return '.claude/.ckignore';
}
/**
* Format a blocked path error with actionable guidance
*
* Pattern: What went wrong → Why → How to fix → Where to configure
*
* @param {Object} details - Error details
* @param {string} details.path - The blocked path
* @param {string} details.pattern - The pattern that matched
* @param {string} details.tool - The tool that was blocked
* @param {string} details.claudeDir - Path to .claude directory
* @param {string} [details.configPath] - Explicit config path to edit
* @returns {string}
*/
function formatBlockedError(details) {
const { path: blockedPath, pattern, tool, claudeDir, configPath } = details;
const resolvedConfigPath = formatConfigPath(claudeDir, configPath);
// Truncate path if too long
const displayPath = blockedPath.length > 60
? '...' + blockedPath.slice(-57)
: blockedPath;
const lines = [
'',
colorize('NOTE:', 'cyan') + ' This is not an error - this block is intentional to optimize context.',
'',
colorize('BLOCKED', 'red') + `: Access to '${displayPath}' denied`,
'',
` ${colorize('Pattern:', 'yellow')} ${pattern}`,
` ${colorize('Tool:', 'yellow')} ${tool || 'unknown'}`,
'',
` ${colorize('To allow, add to', 'blue')} ${resolvedConfigPath}:`,
` !${pattern}`,
'',
` ${colorize('Config:', 'dim')} ${resolvedConfigPath}`,
''
];
return lines.join('\n');
}
/**
* Format a simple error message (one line, for piped output)
*
* @param {string} pattern - The pattern that matched
* @param {string} blockedPath - The path that was blocked
* @returns {string}
*/
function formatSimpleError(pattern, blockedPath) {
return `ERROR: Blocked pattern '${pattern}' matched path: ${blockedPath}`;
}
/**
* Format error for machine-readable output (exit code 2)
* Used when stderr is not a TTY
*
* @param {Object} details - Error details
* @returns {string}
*/
function formatMachineError(details) {
const { path: blockedPath, pattern, tool, claudeDir, configPath } = details;
const resolvedConfigPath = formatConfigPath(claudeDir, configPath);
return JSON.stringify({
error: 'BLOCKED',
path: blockedPath,
pattern: pattern,
tool: tool,
config: resolvedConfigPath,
fix: `Add '!${pattern}' to ${resolvedConfigPath} to allow this path`
});
}
/**
* Format a warning message (non-blocking)
*
* @param {string} message - Warning message
* @returns {string}
*/
function formatWarning(message) {
return colorize('WARN:', 'yellow') + ' ' + message;
}
module.exports = {
formatBlockedError,
formatSimpleError,
formatMachineError,
formatWarning,
formatConfigPath,
supportsColor,
colorize,
COLORS
};