Skip to main content
Glama
links.ts11 kB
/** * TUI Link Components * * Terminal link utilities for WP Navigator CLI. * Provides clickable hyperlinks in supported terminals with fallback for others. * * OSC 8 hyperlink support: * - iTerm2 (macOS) * - GNOME Terminal * - Windows Terminal * - VS Code terminal * - Many modern terminals * * @package WP_Navigator_Pro * @since 1.1.0 */ import { colorize, supportsColor } from './components.js'; // ============================================================================= // Types // ============================================================================= export interface LinkOptions { /** Link display text (defaults to URL if not provided) */ text?: string; /** Whether to show URL in parentheses after text (for fallback) */ showUrl?: boolean; } // ============================================================================= // WP Navigator URLs // ============================================================================= /** * Canonical WP Navigator URLs * Single source of truth for all links used in CLI output */ export const WPNAV_URLS = { /** Demo video/walkthrough */ demo: 'https://wpnav.ai/start/demo', /** General help and support */ help: 'https://wpnav.ai/help', /** Technical documentation */ docs: 'https://wpnav.ai/docs', /** Troubleshooting guide */ troubleshoot: 'https://wpnav.ai/troubleshoot', /** CLI documentation */ cliDocs: 'https://wpnav.ai/docs/cli', /** Plugin download */ download: 'https://wpnav.ai/download', /** Getting started guide */ start: 'https://wpnav.ai/start', } as const; export type WpnavUrlKey = keyof typeof WPNAV_URLS; // ============================================================================= // Terminal Link Detection // ============================================================================= /** * Check if terminal supports OSC 8 hyperlinks * * OSC 8 is the standard escape sequence for terminal hyperlinks. * Support varies by terminal, but most modern terminals support it. */ export function supportsHyperlinks(): boolean { // Check for explicit override if (process.env.FORCE_HYPERLINK === '1') return true; if (process.env.FORCE_HYPERLINK === '0') return false; // Not a TTY, no hyperlinks if (!process.stdout.isTTY) return false; // Check TERM_PROGRAM for known supported terminals const termProgram = process.env.TERM_PROGRAM || ''; const termProgramVersion = process.env.TERM_PROGRAM_VERSION || ''; // iTerm2 3.1+ supports hyperlinks if (termProgram === 'iTerm.app') { const version = parseInt(termProgramVersion.split('.')[0] || '0', 10); if (version >= 3) return true; } // VS Code integrated terminal supports hyperlinks if (termProgram === 'vscode') return true; // Windows Terminal supports hyperlinks if (process.env.WT_SESSION) return true; // Check for GNOME Terminal (VTE-based terminals) if (process.env.VTE_VERSION) { const vteVersion = parseInt(process.env.VTE_VERSION || '0', 10); // VTE 0.50+ supports hyperlinks if (vteVersion >= 5000) return true; } // Alacritty supports hyperlinks if (process.env.ALACRITTY_LOG) return true; // Kitty supports hyperlinks if (process.env.KITTY_WINDOW_ID) return true; // WezTerm supports hyperlinks if (process.env.WEZTERM_EXECUTABLE) return true; // Default: assume no hyperlink support for safety // Better to show URL than broken escape sequences return false; } // ============================================================================= // Link Formatting // ============================================================================= /** * Create a terminal hyperlink using OSC 8 escape sequences * * Format: \x1b]8;;URL\x1b\\text\x1b]8;;\x1b\\ * * @param url - The URL to link to * @param text - Display text (defaults to URL) * @returns OSC 8 formatted hyperlink string */ function createOsc8Link(url: string, text: string): string { // OSC 8 format: ESC ] 8 ; ; URL ST text ESC ] 8 ; ; ST // Where ST (String Terminator) is ESC \ or BEL (\x07) // Using ESC \ for better compatibility return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`; } /** * Format a URL as a clickable terminal link (if supported) * * In terminals that support OSC 8 hyperlinks, the text will be clickable. * In other terminals, shows the URL in a readable format. * * @param url - The URL to link to * @param options - Link display options * @returns Formatted link string for terminal output * * @example * // In supported terminal: * link('https://wpnav.ai/docs') * // Clickable "https://wpnav.ai/docs" * * @example * // With custom text: * link('https://wpnav.ai/docs', { text: 'Documentation' }) * // Clickable "Documentation" * * @example * // With URL shown: * link('https://wpnav.ai/docs', { text: 'Docs', showUrl: true }) * // "Docs (https://wpnav.ai/docs)" */ export function link(url: string, options: LinkOptions = {}): string { const { text, showUrl = false } = options; const displayText = text || url; if (supportsHyperlinks()) { // Terminal supports clickable links const hyperlink = createOsc8Link(url, displayText); // Apply color styling to make links visually distinct if (supportsColor()) { // Cyan + underline for links return `\x1b[36m\x1b[4m${hyperlink}\x1b[24m\x1b[0m`; } return hyperlink; } // Fallback: plain text with optional URL if (text && showUrl) { // Show both text and URL for clarity if (supportsColor()) { return `${colorize(displayText, 'cyan')} (${url})`; } return `${displayText} (${url})`; } // Just the URL or text if (supportsColor()) { return colorize(displayText, 'cyan'); } return displayText; } /** * Format a WP Navigator URL using the canonical URLs * * @param key - Key from WPNAV_URLS * @param options - Link display options * @returns Formatted link string * * @example * wpnavLink('docs') * // Clickable link to https://wpnav.ai/docs * * @example * wpnavLink('help', { text: 'Get Help' }) * // Clickable "Get Help" link */ export function wpnavLink(key: WpnavUrlKey, options: LinkOptions = {}): string { const url = WPNAV_URLS[key]; return link(url, options); } // ============================================================================= // Contextual Link Helpers // ============================================================================= /** * Print a demo link with arrow prefix * * @example * demoLink() * // Output: "→ Watch demo: https://wpnav.ai/start/demo" */ export function demoLink(): string { const arrow = supportsColor() ? colorize('→', 'cyan') : '->'; return `${arrow} Watch demo: ${wpnavLink('demo')}`; } /** * Print a help link with arrow prefix * * @example * helpLink() * // Output: "→ Need help? https://wpnav.ai/help" */ export function helpLink(): string { const arrow = supportsColor() ? colorize('→', 'cyan') : '->'; return `${arrow} Need help? ${wpnavLink('help')}`; } /** * Print a docs link with arrow prefix * * @example * docsLink() * // Output: "→ Documentation: https://wpnav.ai/docs" */ export function docsLink(): string { const arrow = supportsColor() ? colorize('→', 'cyan') : '->'; return `${arrow} Documentation: ${wpnavLink('docs')}`; } /** * Print a troubleshooting link with arrow prefix * * @example * troubleshootLink() * // Output: "→ Troubleshooting: https://wpnav.ai/troubleshoot" */ export function troubleshootLink(): string { const arrow = supportsColor() ? colorize('→', 'cyan') : '->'; return `${arrow} Troubleshooting: ${wpnavLink('troubleshoot')}`; } /** * Print CLI documentation link with arrow prefix */ export function cliDocsLink(): string { const arrow = supportsColor() ? colorize('→', 'cyan') : '->'; return `${arrow} CLI docs: ${wpnavLink('cliDocs')}`; } // ============================================================================= // Resource Link Block // ============================================================================= /** * Print a formatted block of resource links * * Used for final summaries and help output. * Ensures no duplicate links are shown. * * @param keys - Which links to include (defaults to all main links) * * @example * resourceLinks() * // Output: * // Resources: * // → Watch demo: https://wpnav.ai/start/demo * // → Need help? https://wpnav.ai/help * // → Documentation: https://wpnav.ai/docs */ export function resourceLinks(keys?: WpnavUrlKey[]): string[] { const defaultKeys: WpnavUrlKey[] = ['demo', 'help', 'docs']; const linksToShow = keys || defaultKeys; // Track shown URLs to prevent duplicates const shown = new Set<string>(); const lines: string[] = []; for (const key of linksToShow) { const url = WPNAV_URLS[key]; if (shown.has(url)) continue; shown.add(url); switch (key) { case 'demo': lines.push(demoLink()); break; case 'help': lines.push(helpLink()); break; case 'docs': lines.push(docsLink()); break; case 'troubleshoot': lines.push(troubleshootLink()); break; case 'cliDocs': lines.push(cliDocsLink()); break; default: { const arrow = supportsColor() ? colorize('→', 'cyan') : '->'; lines.push(`${arrow} ${wpnavLink(key)}`); } } } return lines; } /** * Print resource links block to stderr * * @param title - Optional title (default: "Resources:") * @param keys - Which links to include */ export function printResourceLinks(title = 'Resources:', keys?: WpnavUrlKey[]): void { const arrow = supportsColor() ? colorize('→', 'dim') : ''; console.error(`${arrow} ${title}`); for (const line of resourceLinks(keys)) { console.error(` ${line}`); } } // ============================================================================= // Error Context Links // ============================================================================= /** * Get appropriate help link for an error context * * @param errorType - Type of error for context-specific help * @returns Appropriate help URL */ export function getErrorHelpLink(errorType?: string): string { // Map error types to specific help pages switch (errorType?.toLowerCase()) { case 'connection': case 'network': case 'timeout': case 'auth': case 'authentication': return WPNAV_URLS.troubleshoot; case 'config': case 'validation': case 'manifest': return WPNAV_URLS.docs; default: return WPNAV_URLS.help; } } /** * Format an error message with contextual help link * * @param message - Error message * @param errorType - Type of error for context-specific help * @returns Error message with help link appended */ export function errorWithHelp(message: string, errorType?: string): string { const helpUrl = getErrorHelpLink(errorType); return `${message}\n ${link(helpUrl, { text: 'Get help' })}`; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server