trmnl-cli 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
// src/index.ts
|
|
2
|
+
import { createRequire } from "module";
|
|
4
3
|
import cac from "cac";
|
|
5
4
|
|
|
6
5
|
// src/lib/config.ts
|
|
@@ -815,8 +814,10 @@ async function readStdin2() {
|
|
|
815
814
|
}
|
|
816
815
|
|
|
817
816
|
// src/index.ts
|
|
817
|
+
var require2 = createRequire(import.meta.url);
|
|
818
|
+
var { version } = require2("../package.json");
|
|
818
819
|
var cli = cac("trmnl");
|
|
819
|
-
cli.version(
|
|
820
|
+
cli.version(version);
|
|
820
821
|
registerSendCommand(cli);
|
|
821
822
|
registerValidateCommand(cli);
|
|
822
823
|
registerConfigCommand(cli);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/lib/config.ts","../src/types.ts","../src/lib/logger.ts","../src/commands/config.ts","../src/commands/history.ts","../src/commands/send.ts","../src/lib/validator.ts","../src/lib/webhook.ts","../src/commands/validate.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * trmnl-cli - CLI tool for TRMNL e-ink displays\n * \n * Commands:\n * trmnl send - Send content to TRMNL display\n * trmnl validate - Validate payload without sending\n * trmnl config - Manage CLI configuration\n * trmnl history - View send history\n */\n\nimport cac from 'cac';\nimport { registerConfigCommand } from './commands/config.ts';\nimport { registerHistoryCommand } from './commands/history.ts';\nimport { registerSendCommand } from './commands/send.ts';\nimport { registerValidateCommand } from './commands/validate.ts';\n\nconst cli = cac('trmnl');\n\n// Version from package.json\ncli.version('0.1.0');\n\n// Register commands\nregisterSendCommand(cli);\nregisterValidateCommand(cli);\nregisterConfigCommand(cli);\nregisterHistoryCommand(cli);\n\n// Help text\ncli.help();\n\n// Default action (no command)\ncli.command('').action(() => {\n cli.outputHelp();\n});\n\n// Parse and run\ncli.parse();\n","/**\n * Config file management for ~/.trmnl/config.json\n * Supports multiple plugins with a default\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { Config, Plugin, WebhookTier } from '../types.ts';\nimport { DEFAULT_CONFIG } from '../types.ts';\n\n/** Config directory path */\nexport const CONFIG_DIR = join(homedir(), '.trmnl');\n\n/** Config file path */\nexport const CONFIG_PATH = join(CONFIG_DIR, 'config.json');\n\n/** Legacy TOML config path (for migration) */\nconst LEGACY_CONFIG_PATH = join(CONFIG_DIR, 'config.toml');\n\n/** History file path */\nexport const HISTORY_PATH = join(CONFIG_DIR, 'history.jsonl');\n\n/**\n * Ensure ~/.trmnl directory exists\n */\nexport function ensureConfigDir(): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\n/**\n * Migrate legacy TOML config to JSON (one-time)\n */\nfunction migrateLegacyConfig(): Config | null {\n if (!existsSync(LEGACY_CONFIG_PATH)) {\n return null;\n }\n\n try {\n const content = readFileSync(LEGACY_CONFIG_PATH, 'utf-8');\n const config: Config = { plugins: {}, tier: 'free' };\n\n // Parse legacy TOML (simple parser for our format)\n let webhookUrl: string | undefined;\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n const urlMatch = trimmed.match(/^url\\s*=\\s*\"([^\"]+)\"/);\n if (urlMatch) webhookUrl = urlMatch[1];\n }\n\n if (webhookUrl) {\n config.plugins['default'] = { url: webhookUrl };\n config.defaultPlugin = 'default';\n }\n\n // Remove legacy file after migration\n unlinkSync(LEGACY_CONFIG_PATH);\n console.log('Migrated legacy config.toml to config.json');\n\n return config;\n } catch {\n return null;\n }\n}\n\n/**\n * Load config from file\n */\nexport function loadConfig(): Config {\n ensureConfigDir();\n\n // Try to migrate legacy config first\n const migrated = migrateLegacyConfig();\n if (migrated) {\n saveConfig(migrated);\n return migrated;\n }\n\n if (!existsSync(CONFIG_PATH)) {\n return { ...DEFAULT_CONFIG };\n }\n\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const parsed = JSON.parse(content) as Partial<Config>;\n return {\n plugins: parsed.plugins || {},\n defaultPlugin: parsed.defaultPlugin,\n tier: parsed.tier || 'free',\n history: parsed.history || DEFAULT_CONFIG.history,\n };\n } catch {\n return { ...DEFAULT_CONFIG };\n }\n}\n\n/**\n * Save config to file\n */\nexport function saveConfig(config: Config): void {\n ensureConfigDir();\n const content = JSON.stringify(config, null, 2);\n writeFileSync(CONFIG_PATH, content, 'utf-8');\n}\n\n/**\n * Get a plugin by name (or default if not specified)\n */\nexport function getPlugin(name?: string): { name: string; plugin: Plugin } | null {\n const config = loadConfig();\n \n // If name specified, use that\n if (name) {\n const plugin = config.plugins[name];\n if (plugin) {\n return { name, plugin };\n }\n return null;\n }\n\n // Use default plugin\n const defaultName = config.defaultPlugin;\n if (defaultName && config.plugins[defaultName]) {\n return { name: defaultName, plugin: config.plugins[defaultName] };\n }\n\n // If only one plugin, use it\n const pluginNames = Object.keys(config.plugins);\n if (pluginNames.length === 1) {\n const name = pluginNames[0];\n return { name, plugin: config.plugins[name] };\n }\n\n return null;\n}\n\n/**\n * Add or update a plugin\n */\nexport function setPlugin(name: string, url: string, description?: string): void {\n const config = loadConfig();\n config.plugins[name] = { url, description };\n \n // If no default and this is the first plugin, make it default\n if (!config.defaultPlugin || Object.keys(config.plugins).length === 1) {\n config.defaultPlugin = name;\n }\n \n saveConfig(config);\n}\n\n/**\n * Remove a plugin\n */\nexport function removePlugin(name: string): boolean {\n const config = loadConfig();\n \n if (!config.plugins[name]) {\n return false;\n }\n \n delete config.plugins[name];\n \n // If we removed the default, pick a new one\n if (config.defaultPlugin === name) {\n const remaining = Object.keys(config.plugins);\n config.defaultPlugin = remaining.length > 0 ? remaining[0] : undefined;\n }\n \n saveConfig(config);\n return true;\n}\n\n/**\n * Set the default plugin\n */\nexport function setDefaultPlugin(name: string): boolean {\n const config = loadConfig();\n \n if (!config.plugins[name]) {\n return false;\n }\n \n config.defaultPlugin = name;\n saveConfig(config);\n return true;\n}\n\n/**\n * List all plugins\n */\nexport function listPlugins(): Array<{ name: string; plugin: Plugin; isDefault: boolean }> {\n const config = loadConfig();\n return Object.entries(config.plugins).map(([name, plugin]) => ({\n name,\n plugin,\n isDefault: name === config.defaultPlugin,\n }));\n}\n\n/**\n * Get global tier setting\n */\nexport function getTier(): WebhookTier {\n const config = loadConfig();\n return config.tier || 'free';\n}\n\n/**\n * Set global tier setting\n */\nexport function setTier(tier: WebhookTier): void {\n const config = loadConfig();\n config.tier = tier;\n saveConfig(config);\n}\n\n/**\n * Get webhook URL from environment, plugin name, or default\n */\nexport function getWebhookUrl(pluginName?: string): { url: string; name: string } | null {\n // Environment variable takes highest precedence\n const envUrl = process.env.TRMNL_WEBHOOK;\n if (envUrl) {\n return { url: envUrl, name: '$TRMNL_WEBHOOK' };\n }\n\n // Try to get plugin\n const result = getPlugin(pluginName);\n if (result) {\n return {\n url: result.plugin.url,\n name: result.name,\n };\n }\n\n return null;\n}\n\n/**\n * Get history config\n */\nexport function getHistoryConfig(): { path: string; maxSizeMb: number } {\n const config = loadConfig();\n const historyPath = config.history?.path || DEFAULT_CONFIG.history!.path!;\n const expandedPath = historyPath.startsWith('~') \n ? historyPath.replace('~', homedir())\n : historyPath;\n \n return {\n path: expandedPath,\n maxSizeMb: config.history?.maxSizeMb || DEFAULT_CONFIG.history!.maxSizeMb!,\n };\n}\n","/**\n * TRMNL CLI Types\n */\n\n/** Webhook tier determines payload size limits */\nexport type WebhookTier = 'free' | 'plus';\n\n/** Size limits per tier in bytes */\nexport const TIER_LIMITS: Record<WebhookTier, number> = {\n free: 2048, // 2 KB\n plus: 5120, // 5 KB\n};\n\n/** Plugin configuration */\nexport interface Plugin {\n url: string;\n description?: string;\n}\n\n/** CLI configuration stored in ~/.trmnl/config.json */\nexport interface Config {\n plugins: Record<string, Plugin>;\n defaultPlugin?: string;\n tier?: WebhookTier; // Global tier setting\n history?: {\n path?: string;\n maxSizeMb?: number;\n };\n}\n\n/** Default config values */\nexport const DEFAULT_CONFIG: Config = {\n plugins: {},\n defaultPlugin: undefined,\n tier: 'free',\n history: {\n path: '~/.trmnl/history.jsonl',\n maxSizeMb: 100,\n },\n};\n\n/** Merge variables payload structure */\nexport interface MergeVariables {\n content?: string;\n title?: string;\n text?: string;\n image?: string;\n [key: string]: string | undefined;\n}\n\n/** Webhook request payload */\nexport interface WebhookPayload {\n merge_variables: MergeVariables;\n}\n\n/** History entry stored in JSONL */\nexport interface HistoryEntry {\n timestamp: string;\n plugin: string;\n size_bytes: number;\n tier: WebhookTier;\n payload: WebhookPayload;\n success: boolean;\n status_code?: number;\n response?: string;\n error?: string;\n duration_ms: number;\n}\n\n/** Validation result */\nexport interface ValidationResult {\n valid: boolean;\n size_bytes: number;\n tier: WebhookTier;\n limit_bytes: number;\n remaining_bytes: number;\n percent_used: number;\n warnings: string[];\n errors: string[];\n}\n","/**\n * JSONL history logger for tracking sent payloads\n */\n\nimport { appendFileSync, existsSync, readFileSync, statSync } from 'node:fs';\nimport { ensureConfigDir, getHistoryConfig } from './config.ts';\nimport type { HistoryEntry } from '../types.ts';\n\n/**\n * Append a history entry to the JSONL file\n */\nexport function logEntry(entry: HistoryEntry): void {\n ensureConfigDir();\n const { path } = getHistoryConfig();\n const line = JSON.stringify(entry) + '\\n';\n appendFileSync(path, line, 'utf-8');\n}\n\n/**\n * Read all history entries\n */\nexport function readHistory(): HistoryEntry[] {\n const { path } = getHistoryConfig();\n \n if (!existsSync(path)) {\n return [];\n }\n\n try {\n const content = readFileSync(path, 'utf-8');\n const lines = content.trim().split('\\n').filter(Boolean);\n return lines.map(line => JSON.parse(line) as HistoryEntry);\n } catch {\n return [];\n }\n}\n\n/**\n * Get history entries with filters\n */\nexport interface HistoryFilter {\n last?: number;\n today?: boolean;\n failed?: boolean;\n success?: boolean;\n plugin?: string;\n since?: Date;\n until?: Date;\n}\n\nexport function getHistory(filter: HistoryFilter = {}): HistoryEntry[] {\n let entries = readHistory();\n\n // Filter by plugin\n if (filter.plugin) {\n entries = entries.filter(e => e.plugin === filter.plugin);\n }\n\n // Filter by success/failed\n if (filter.failed) {\n entries = entries.filter(e => !e.success);\n }\n if (filter.success) {\n entries = entries.filter(e => e.success);\n }\n\n // Filter by date\n if (filter.today) {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n entries = entries.filter(e => new Date(e.timestamp) >= today);\n }\n if (filter.since) {\n entries = entries.filter(e => new Date(e.timestamp) >= filter.since!);\n }\n if (filter.until) {\n entries = entries.filter(e => new Date(e.timestamp) <= filter.until!);\n }\n\n // Sort by timestamp descending (most recent first)\n entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n\n // Limit results\n if (filter.last) {\n entries = entries.slice(0, filter.last);\n }\n\n return entries;\n}\n\n/**\n * Format a history entry for display\n */\nexport function formatEntry(entry: HistoryEntry, verbose = false): string {\n const time = new Date(entry.timestamp).toLocaleString();\n const status = entry.success ? '✓' : '✗';\n const sizeKb = (entry.size_bytes / 1024).toFixed(2);\n \n let line = `${status} ${time} | ${entry.plugin} | ${sizeKb} KB | ${entry.duration_ms}ms`;\n \n if (!entry.success && entry.error) {\n line += ` | ${entry.error}`;\n }\n \n if (verbose && entry.payload?.merge_variables?.content) {\n const preview = entry.payload.merge_variables.content.substring(0, 80);\n line += `\\n ${preview}${entry.payload.merge_variables.content.length > 80 ? '...' : ''}`;\n }\n \n return line;\n}\n\n/**\n * Get history file stats\n */\nexport function getHistoryStats(): { entries: number; sizeBytes: number; sizeMb: number } | null {\n const { path } = getHistoryConfig();\n \n if (!existsSync(path)) {\n return null;\n }\n\n try {\n const stats = statSync(path);\n const entries = readHistory().length;\n return {\n entries,\n sizeBytes: stats.size,\n sizeMb: Math.round((stats.size / 1024 / 1024) * 100) / 100,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Get history file path\n */\nexport function getHistoryPath(): string {\n return getHistoryConfig().path;\n}\n","/**\n * trmnl config - Manage CLI configuration\n * trmnl plugin - Manage webhook plugins\n */\n\nimport type { CAC } from 'cac';\nimport {\n CONFIG_PATH,\n getTier,\n listPlugins,\n loadConfig,\n removePlugin,\n setDefaultPlugin,\n setPlugin,\n setTier,\n} from '../lib/config.ts';\nimport { getHistoryPath } from '../lib/logger.ts';\nimport type { WebhookTier } from '../types.ts';\n\nexport function registerConfigCommand(cli: CAC): void {\n // Config show\n cli\n .command('config', 'Show configuration')\n .action(() => {\n const config = loadConfig();\n const plugins = listPlugins();\n\n console.log(`Config file: ${CONFIG_PATH}`);\n console.log('');\n\n console.log('Plugins:');\n if (plugins.length === 0) {\n console.log(' (none configured)');\n console.log('');\n console.log(' Add a plugin:');\n console.log(' trmnl plugin add <name> <url>');\n } else {\n for (const { name, plugin, isDefault } of plugins) {\n const defaultMark = isDefault ? ' (default)' : '';\n console.log(` ${name}${defaultMark}`);\n console.log(` url: ${plugin.url}`);\n if (plugin.description) {\n console.log(` desc: ${plugin.description}`);\n }\n }\n }\n\n console.log('');\n console.log(`Tier: ${config.tier || 'free'}`);\n console.log(` Limit: ${config.tier === 'plus' ? '5 KB' : '2 KB'}`);\n\n console.log('');\n console.log('History:');\n console.log(` path: ${getHistoryPath()}`);\n\n console.log('');\n console.log('Environment:');\n console.log(` TRMNL_WEBHOOK: ${process.env.TRMNL_WEBHOOK || '(not set)'}`);\n });\n\n // Tier command (separate from config)\n cli\n .command('tier [value]', 'Get or set tier (free or plus)')\n .example('trmnl tier # Show current tier')\n .example('trmnl tier plus # Set to plus')\n .example('trmnl tier free # Set to free')\n .action((value?: string) => {\n if (!value) {\n const tier = getTier();\n console.log(`Tier: ${tier}`);\n console.log(`Limit: ${tier === 'plus' ? '5 KB (5,120 bytes)' : '2 KB (2,048 bytes)'}`);\n return;\n }\n\n if (value !== 'free' && value !== 'plus') {\n console.error('Invalid tier. Use \"free\" or \"plus\".');\n process.exit(1);\n }\n\n setTier(value as WebhookTier);\n console.log(`✓ Tier set to: ${value}`);\n });\n\n // Plugin command with action as first arg\n cli\n .command('plugin [action] [name] [url]', 'Manage webhook plugins')\n .option('-d, --desc <description>', 'Plugin description')\n .option('-u, --url <url>', 'Webhook URL (for set action)')\n .option('--default', 'Set as default plugin')\n .example('trmnl plugin # List plugins')\n .example('trmnl plugin add home <url> # Add plugin')\n .example('trmnl plugin rm home # Remove plugin')\n .example('trmnl plugin default home # Set default')\n .example('trmnl plugin set home --url ... # Update plugin')\n .action((action?: string, name?: string, url?: string, options?: { desc?: string; url?: string; default?: boolean }) => {\n // No action = list\n if (!action) {\n showPluginList();\n return;\n }\n\n // Handle actions\n switch (action) {\n case 'add':\n if (!name || !url) {\n console.error('Usage: trmnl plugin add <name> <url>');\n process.exit(1);\n }\n setPlugin(name, url, options?.desc);\n console.log(`✓ Added plugin: ${name}`);\n if (options?.default) {\n setDefaultPlugin(name);\n console.log(`✓ Set as default`);\n }\n break;\n\n case 'rm':\n case 'remove':\n if (!name) {\n console.error('Usage: trmnl plugin rm <name>');\n process.exit(1);\n }\n if (removePlugin(name)) {\n console.log(`✓ Removed plugin: ${name}`);\n } else {\n console.error(`Plugin not found: ${name}`);\n process.exit(1);\n }\n break;\n\n case 'default':\n if (!name) {\n console.error('Usage: trmnl plugin default <name>');\n process.exit(1);\n }\n if (setDefaultPlugin(name)) {\n console.log(`✓ Default plugin: ${name}`);\n } else {\n console.error(`Plugin not found: ${name}`);\n process.exit(1);\n }\n break;\n\n case 'set':\n case 'update':\n if (!name) {\n console.error('Usage: trmnl plugin set <name> [options]');\n process.exit(1);\n }\n const plugins = listPlugins();\n const existing = plugins.find(p => p.name === name);\n if (!existing) {\n console.error(`Plugin not found: ${name}`);\n process.exit(1);\n }\n const newUrl = options?.url || existing.plugin.url;\n const newDesc = options?.desc !== undefined ? options.desc : existing.plugin.description;\n setPlugin(name, newUrl, newDesc);\n console.log(`✓ Updated plugin: ${name}`);\n break;\n\n case 'list':\n showPluginList();\n break;\n\n default:\n console.error(`Unknown action: ${action}`);\n console.log('');\n console.log('Available actions:');\n console.log(' add <name> <url> - Add a plugin');\n console.log(' rm <name> - Remove a plugin');\n console.log(' default <name> - Set default plugin');\n console.log(' set <name> - Update a plugin');\n console.log(' list - List all plugins');\n process.exit(1);\n }\n });\n\n // Plugins alias for list\n cli\n .command('plugins', 'List all plugins')\n .action(() => {\n showPluginList();\n });\n}\n\nfunction showPluginList(): void {\n const plugins = listPlugins();\n\n if (plugins.length === 0) {\n console.log('No plugins configured.');\n console.log('');\n console.log('Add a plugin:');\n console.log(' trmnl plugin add <name> <url>');\n return;\n }\n\n console.log('Plugins:');\n for (const { name, plugin, isDefault } of plugins) {\n const defaultMark = isDefault ? ' ★' : '';\n console.log(` ${name}${defaultMark}`);\n console.log(` ${plugin.url}`);\n if (plugin.description) {\n console.log(` ${plugin.description}`);\n }\n }\n\n console.log('');\n console.log('★ = default plugin');\n}\n","/**\n * trmnl history - View send history\n */\n\nimport { unlinkSync } from 'node:fs';\nimport type { CAC } from 'cac';\nimport { formatEntry, getHistory, getHistoryPath, getHistoryStats, type HistoryFilter } from '../lib/logger.ts';\n\ninterface HistoryOptions {\n last?: number;\n today?: boolean;\n failed?: boolean;\n success?: boolean;\n plugin?: string;\n json?: boolean;\n verbose?: boolean;\n}\n\nexport function registerHistoryCommand(cli: CAC): void {\n cli\n .command('history', 'View send history')\n .option('-n, --last <n>', 'Show last N entries', { default: 10 })\n .option('--today', 'Show only today\\'s entries')\n .option('--failed', 'Show only failed sends')\n .option('--success', 'Show only successful sends')\n .option('-p, --plugin <name>', 'Filter by plugin name')\n .option('--json', 'Output as JSON')\n .option('-v, --verbose', 'Show content preview')\n .example('trmnl history')\n .example('trmnl history --last 20')\n .example('trmnl history --today --failed')\n .example('trmnl history --plugin office')\n .action((options: HistoryOptions) => {\n const filter: HistoryFilter = {\n last: options.last,\n today: options.today,\n failed: options.failed,\n success: options.success,\n plugin: options.plugin,\n };\n\n const entries = getHistory(filter);\n\n if (options.json) {\n console.log(JSON.stringify(entries, null, 2));\n return;\n }\n\n if (entries.length === 0) {\n console.log('No history entries found.');\n console.log(`History file: ${getHistoryPath()}`);\n return;\n }\n\n // Stats header\n const stats = getHistoryStats();\n if (stats) {\n console.log(`History: ${stats.entries} total entries (${stats.sizeMb} MB)`);\n console.log('');\n }\n\n // Filter description\n const filterParts: string[] = [];\n if (options.today) filterParts.push('today');\n if (options.failed) filterParts.push('failed');\n if (options.success) filterParts.push('success');\n if (options.plugin) filterParts.push(`plugin: ${options.plugin}`);\n if (filterParts.length > 0) {\n console.log(`Filter: ${filterParts.join(', ')}`);\n console.log('');\n }\n\n // Entries\n console.log(`Showing ${entries.length} entries (most recent first):`);\n console.log('');\n\n for (const entry of entries) {\n console.log(formatEntry(entry, options.verbose));\n }\n });\n\n // History clear\n cli\n .command('history clear', 'Clear send history')\n .option('--confirm', 'Confirm deletion')\n .action((options: { confirm?: boolean }) => {\n const historyPath = getHistoryPath();\n \n if (!options.confirm) {\n console.log('This will delete all history. Use --confirm to proceed.');\n console.log(`History file: ${historyPath}`);\n return;\n }\n\n try {\n unlinkSync(historyPath);\n console.log('✓ History cleared');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.log('History file does not exist.');\n } else {\n console.error('Error clearing history:', err);\n }\n }\n });\n\n // History stats\n cli\n .command('history stats', 'Show history statistics')\n .action(() => {\n const stats = getHistoryStats();\n \n if (!stats) {\n console.log('No history file found.');\n return;\n }\n\n const entries = getHistory({});\n const successCount = entries.filter(e => e.success).length;\n const failedCount = entries.filter(e => !e.success).length;\n const totalBytes = entries.reduce((sum, e) => sum + e.size_bytes, 0);\n const avgBytes = entries.length > 0 ? Math.round(totalBytes / entries.length) : 0;\n const avgDuration = entries.length > 0 \n ? Math.round(entries.reduce((sum, e) => sum + e.duration_ms, 0) / entries.length) \n : 0;\n\n // Plugin breakdown\n const byPlugin = new Map<string, number>();\n for (const entry of entries) {\n byPlugin.set(entry.plugin, (byPlugin.get(entry.plugin) || 0) + 1);\n }\n\n console.log('History Statistics');\n console.log('');\n console.log(`File: ${getHistoryPath()}`);\n console.log(`Size: ${stats.sizeMb} MB`);\n console.log('');\n console.log(`Total: ${entries.length} sends`);\n console.log(`Success: ${successCount} (${entries.length > 0 ? Math.round(successCount / entries.length * 100) : 0}%)`);\n console.log(`Failed: ${failedCount} (${entries.length > 0 ? Math.round(failedCount / entries.length * 100) : 0}%)`);\n console.log('');\n console.log(`Avg size: ${avgBytes} bytes`);\n console.log(`Avg duration: ${avgDuration}ms`);\n\n if (byPlugin.size > 1) {\n console.log('');\n console.log('By plugin:');\n for (const [plugin, count] of byPlugin.entries()) {\n console.log(` ${plugin}: ${count} sends`);\n }\n }\n\n // Recent activity\n const today = getHistory({ today: true });\n const thisWeek = getHistory({ since: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) });\n console.log('');\n console.log(`Today: ${today.length} sends`);\n console.log(`This week: ${thisWeek.length} sends`);\n });\n}\n","/**\n * trmnl send - Send content to TRMNL display\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { CAC } from 'cac';\nimport { createPayload, formatValidation } from '../lib/validator.ts';\nimport { sendToWebhook } from '../lib/webhook.ts';\n\ninterface SendOptions {\n content?: string;\n file?: string;\n plugin?: string;\n webhook?: string;\n skipValidation?: boolean;\n skipLog?: boolean;\n json?: boolean;\n}\n\nexport function registerSendCommand(cli: CAC): void {\n cli\n .command('send', 'Send content to TRMNL display')\n .option('-c, --content <html>', 'HTML content to send')\n .option('-f, --file <path>', 'Read content from file')\n .option('-p, --plugin <name>', 'Plugin to use (default: default plugin)')\n .option('-w, --webhook <url>', 'Override webhook URL directly')\n .option('--skip-validation', 'Skip payload validation')\n .option('--skip-log', 'Skip history logging')\n .option('--json', 'Output result as JSON')\n .example('trmnl send --content \"<div class=\\\\\"layout\\\\\">Hello</div>\"')\n .example('trmnl send --file ./output.html')\n .example('trmnl send --file ./output.html --plugin office')\n .example('echo \\'{\"merge_variables\":{\"content\":\"...\"}}\\' | trmnl send')\n .action(async (options: SendOptions) => {\n let content: string;\n\n // Get content from options, file, or stdin\n if (options.content) {\n content = options.content;\n } else if (options.file) {\n try {\n content = readFileSync(options.file, 'utf-8');\n } catch (err) {\n console.error(`Error reading file: ${options.file}`);\n process.exit(1);\n }\n } else {\n // Try reading from stdin\n content = await readStdin();\n if (!content) {\n console.error('No content provided. Use --content, --file, or pipe content via stdin.');\n process.exit(1);\n }\n }\n\n // Create payload\n const payload = createPayload(content);\n\n // Send\n const result = await sendToWebhook(payload, {\n plugin: options.plugin,\n webhookUrl: options.webhook,\n skipValidation: options.skipValidation,\n skipLog: options.skipLog,\n });\n\n // Output\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n if (result.success) {\n console.log(`✓ Sent to TRMNL (${result.pluginName})`);\n console.log(` Status: ${result.statusCode}`);\n console.log(` Time: ${result.durationMs}ms`);\n console.log(` Size: ${result.validation.size_bytes} bytes (${result.validation.percent_used}% of limit)`);\n } else {\n console.error('✗ Failed to send');\n console.error(` Error: ${result.error}`);\n if (result.pluginName) {\n console.error(` Plugin: ${result.pluginName}`);\n }\n console.log('');\n console.log('Validation:');\n console.log(formatValidation(result.validation));\n }\n }\n\n process.exit(result.success ? 0 : 1);\n });\n}\n\n/**\n * Read content from stdin (non-blocking check)\n */\nasync function readStdin(): Promise<string> {\n // Check if stdin has data (not a TTY)\n if (process.stdin.isTTY) {\n return '';\n }\n\n const chunks: Buffer[] = [];\n \n return new Promise((resolve) => {\n process.stdin.on('data', (chunk) => chunks.push(chunk));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').trim()));\n process.stdin.on('error', () => resolve(''));\n \n // Timeout to avoid hanging\n setTimeout(() => {\n if (chunks.length === 0) {\n resolve('');\n }\n }, 100);\n });\n}\n","/**\n * Payload validation for TRMNL webhooks\n */\n\nimport { TIER_LIMITS, type ValidationResult, type WebhookPayload, type WebhookTier } from '../types.ts';\n\n/**\n * Validate a webhook payload against size limits\n */\nexport function validatePayload(payload: WebhookPayload, tier: WebhookTier = 'free'): ValidationResult {\n const jsonString = JSON.stringify(payload);\n const sizeBytes = new TextEncoder().encode(jsonString).length;\n const limitBytes = TIER_LIMITS[tier];\n const remainingBytes = limitBytes - sizeBytes;\n const percentUsed = Math.round((sizeBytes / limitBytes) * 1000) / 10;\n\n const warnings: string[] = [];\n const errors: string[] = [];\n\n // Size check\n if (sizeBytes > limitBytes) {\n errors.push(`Payload exceeds ${tier} tier limit: ${sizeBytes} bytes > ${limitBytes} bytes`);\n } else if (percentUsed > 90) {\n warnings.push(`Payload is at ${percentUsed}% of ${tier} tier limit`);\n }\n\n // Content check\n if (!payload.merge_variables) {\n errors.push('Missing merge_variables object');\n } else if (!payload.merge_variables.content && !payload.merge_variables.text) {\n warnings.push('No content or text field in merge_variables');\n }\n\n // HTML sanity checks\n const content = payload.merge_variables?.content || '';\n if (content) {\n // Check for unclosed tags (basic check)\n const openDivs = (content.match(/<div/g) || []).length;\n const closeDivs = (content.match(/<\\/div>/g) || []).length;\n if (openDivs !== closeDivs) {\n warnings.push(`Potential unclosed divs: ${openDivs} open, ${closeDivs} close`);\n }\n\n // Check for common TRMNL patterns - look for 'layout' as a class (may have other classes too)\n const hasLayoutClass = /class=[\"'][^\"']*\\blayout\\b[^\"']*[\"']/.test(content);\n if (!hasLayoutClass) {\n warnings.push('Missing .layout class - TRMNL requires a root layout element');\n }\n }\n\n return {\n valid: errors.length === 0,\n size_bytes: sizeBytes,\n tier,\n limit_bytes: limitBytes,\n remaining_bytes: remainingBytes,\n percent_used: percentUsed,\n warnings,\n errors,\n };\n}\n\n/**\n * Parse content into a webhook payload\n */\nexport function createPayload(content: string): WebhookPayload {\n // Try to parse as JSON first\n try {\n const parsed = JSON.parse(content);\n if (parsed.merge_variables) {\n return parsed as WebhookPayload;\n }\n // If it's just merge_variables content\n return { merge_variables: parsed };\n } catch {\n // Treat as raw HTML content\n return {\n merge_variables: {\n content: content,\n },\n };\n }\n}\n\n/**\n * Format validation result for display\n */\nexport function formatValidation(result: ValidationResult): string {\n const lines: string[] = [];\n \n const status = result.valid ? '✓' : '✗';\n const sizeKb = (result.size_bytes / 1024).toFixed(2);\n const limitKb = (result.limit_bytes / 1024).toFixed(2);\n \n lines.push(`${status} Payload: ${result.size_bytes} bytes (${sizeKb} KB)`);\n lines.push(` Tier: ${result.tier} (limit: ${limitKb} KB)`);\n lines.push(` Used: ${result.percent_used}% (${result.remaining_bytes} bytes remaining)`);\n \n if (result.errors.length > 0) {\n lines.push('');\n lines.push('Errors:');\n for (const error of result.errors) {\n lines.push(` ✗ ${error}`);\n }\n }\n \n if (result.warnings.length > 0) {\n lines.push('');\n lines.push('Warnings:');\n for (const warning of result.warnings) {\n lines.push(` ⚠ ${warning}`);\n }\n }\n \n return lines.join('\\n');\n}\n","/**\n * Webhook sending logic\n */\n\nimport { getTier, getWebhookUrl } from './config.ts';\nimport { logEntry } from './logger.ts';\nimport { validatePayload } from './validator.ts';\nimport type { HistoryEntry, WebhookPayload, WebhookTier } from '../types.ts';\n\nexport interface SendResult {\n success: boolean;\n pluginName: string;\n statusCode?: number;\n response?: string;\n error?: string;\n durationMs: number;\n validation: ReturnType<typeof validatePayload>;\n}\n\nexport interface SendOptions {\n plugin?: string; // Plugin name to use\n webhookUrl?: string; // Direct URL override\n skipValidation?: boolean;\n skipLog?: boolean;\n}\n\n/**\n * Send payload to TRMNL webhook\n */\nexport async function sendToWebhook(\n payload: WebhookPayload,\n options: SendOptions = {}\n): Promise<SendResult> {\n const startTime = Date.now();\n const tier = getTier();\n \n // Resolve webhook URL\n let webhookUrl: string;\n let pluginName: string;\n\n if (options.webhookUrl) {\n // Direct URL override\n webhookUrl = options.webhookUrl;\n pluginName = 'custom';\n } else {\n // Get from config/env\n const resolved = getWebhookUrl(options.plugin);\n if (!resolved) {\n const durationMs = Date.now() - startTime;\n const validation = validatePayload(payload, tier);\n \n let error = 'No webhook URL configured.';\n if (options.plugin) {\n error = `Plugin \"${options.plugin}\" not found.`;\n } else {\n error += ' Add a plugin with: trmnl plugin add <name> <url>';\n }\n \n return {\n success: false,\n pluginName: options.plugin || 'unknown',\n error,\n durationMs,\n validation,\n };\n }\n webhookUrl = resolved.url;\n pluginName = resolved.name;\n }\n\n // Validate payload\n const validation = validatePayload(payload, tier);\n \n if (!options.skipValidation && !validation.valid) {\n const durationMs = Date.now() - startTime;\n const result: SendResult = {\n success: false,\n pluginName,\n error: validation.errors.join('; '),\n durationMs,\n validation,\n };\n \n // Log failed validation\n if (!options.skipLog) {\n logEntry(createHistoryEntry(payload, result, tier, pluginName));\n }\n \n return result;\n }\n \n // Send request\n try {\n const response = await fetch(webhookUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n \n const durationMs = Date.now() - startTime;\n const responseText = await response.text();\n \n const result: SendResult = {\n success: response.ok,\n pluginName,\n statusCode: response.status,\n response: responseText,\n durationMs,\n validation,\n };\n \n if (!response.ok) {\n result.error = `HTTP ${response.status}: ${responseText}`;\n }\n \n // Log the send\n if (!options.skipLog) {\n logEntry(createHistoryEntry(payload, result, tier, pluginName));\n }\n \n return result;\n \n } catch (err) {\n const durationMs = Date.now() - startTime;\n const error = err instanceof Error ? err.message : 'Unknown error';\n \n const result: SendResult = {\n success: false,\n pluginName,\n error,\n durationMs,\n validation,\n };\n \n // Log the error\n if (!options.skipLog) {\n logEntry(createHistoryEntry(payload, result, tier, pluginName));\n }\n \n return result;\n }\n}\n\n/**\n * Create a history entry from send result\n */\nfunction createHistoryEntry(\n payload: WebhookPayload,\n result: SendResult,\n tier: WebhookTier,\n pluginName: string\n): HistoryEntry {\n return {\n timestamp: new Date().toISOString(),\n plugin: pluginName,\n size_bytes: result.validation.size_bytes,\n tier,\n payload,\n success: result.success,\n status_code: result.statusCode,\n response: result.response,\n error: result.error,\n duration_ms: result.durationMs,\n };\n}\n","/**\n * trmnl validate - Validate payload without sending\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { CAC } from 'cac';\nimport { getTier } from '../lib/config.ts';\nimport { createPayload, formatValidation, validatePayload } from '../lib/validator.ts';\nimport type { WebhookTier } from '../types.ts';\n\ninterface ValidateOptions {\n content?: string;\n file?: string;\n tier?: WebhookTier;\n json?: boolean;\n}\n\nexport function registerValidateCommand(cli: CAC): void {\n cli\n .command('validate', 'Validate payload without sending')\n .option('-c, --content <html>', 'HTML content to validate')\n .option('-f, --file <path>', 'Read content from file')\n .option('-t, --tier <tier>', 'Override tier (free or plus)')\n .option('--json', 'Output result as JSON')\n .example('trmnl validate --file ./output.html')\n .example('trmnl validate --content \"<div>...</div>\" --tier plus')\n .action(async (options: ValidateOptions) => {\n let content: string;\n\n // Get content from options, file, or stdin\n if (options.content) {\n content = options.content;\n } else if (options.file) {\n try {\n content = readFileSync(options.file, 'utf-8');\n } catch (err) {\n console.error(`Error reading file: ${options.file}`);\n process.exit(1);\n }\n } else {\n // Try reading from stdin\n content = await readStdin();\n if (!content) {\n console.error('No content provided. Use --content, --file, or pipe content via stdin.');\n process.exit(1);\n }\n }\n\n // Use explicit tier or global config\n const tier = options.tier || getTier();\n\n // Create payload and validate\n const payload = createPayload(content);\n const result = validatePayload(payload, tier);\n\n // Output\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(formatValidation(result));\n }\n\n process.exit(result.valid ? 0 : 1);\n });\n}\n\n/**\n * Read content from stdin (non-blocking check)\n */\nasync function readStdin(): Promise<string> {\n if (process.stdin.isTTY) {\n return '';\n }\n\n const chunks: Buffer[] = [];\n \n return new Promise((resolve) => {\n process.stdin.on('data', (chunk) => chunks.push(chunk));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').trim()));\n process.stdin.on('error', () => resolve(''));\n \n setTimeout(() => {\n if (chunks.length === 0) {\n resolve('');\n }\n }, 100);\n });\n}\n"],"mappings":";;;AAWA,OAAO,SAAS;;;ACNhB,SAAS,YAAY,WAAW,cAAc,eAAe,kBAAkB;AAC/E,SAAS,eAAe;AACxB,SAAS,YAAY;;;ACCd,IAAM,cAA2C;AAAA,EACtD,MAAM;AAAA;AAAA,EACN,MAAM;AAAA;AACR;AAoBO,IAAM,iBAAyB;AAAA,EACpC,SAAS,CAAC;AAAA,EACV,eAAe;AAAA,EACf,MAAM;AAAA,EACN,SAAS;AAAA,IACP,MAAM;AAAA,IACN,WAAW;AAAA,EACb;AACF;;;AD3BO,IAAM,aAAa,KAAK,QAAQ,GAAG,QAAQ;AAG3C,IAAM,cAAc,KAAK,YAAY,aAAa;AAGzD,IAAM,qBAAqB,KAAK,YAAY,aAAa;AAGlD,IAAM,eAAe,KAAK,YAAY,eAAe;AAKrD,SAAS,kBAAwB;AACtC,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,cAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACF;AAKA,SAAS,sBAAqC;AAC5C,MAAI,CAAC,WAAW,kBAAkB,GAAG;AACnC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,oBAAoB,OAAO;AACxD,UAAM,SAAiB,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO;AAGnD,QAAI;AAEJ,eAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,YAAM,UAAU,KAAK,KAAK;AAC1B,YAAM,WAAW,QAAQ,MAAM,sBAAsB;AACrD,UAAI,SAAU,cAAa,SAAS,CAAC;AAAA,IACvC;AAEA,QAAI,YAAY;AACd,aAAO,QAAQ,SAAS,IAAI,EAAE,KAAK,WAAW;AAC9C,aAAO,gBAAgB;AAAA,IACzB;AAGA,eAAW,kBAAkB;AAC7B,YAAQ,IAAI,4CAA4C;AAExD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,aAAqB;AACnC,kBAAgB;AAGhB,QAAM,WAAW,oBAAoB;AACrC,MAAI,UAAU;AACZ,eAAW,QAAQ;AACnB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,aAAa,OAAO;AACjD,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO;AAAA,MACL,SAAS,OAAO,WAAW,CAAC;AAAA,MAC5B,eAAe,OAAO;AAAA,MACtB,MAAM,OAAO,QAAQ;AAAA,MACrB,SAAS,OAAO,WAAW,eAAe;AAAA,IAC5C;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AACF;AAKO,SAAS,WAAW,QAAsB;AAC/C,kBAAgB;AAChB,QAAM,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC;AAC9C,gBAAc,aAAa,SAAS,OAAO;AAC7C;AAKO,SAAS,UAAU,MAAwD;AAChF,QAAM,SAAS,WAAW;AAG1B,MAAI,MAAM;AACR,UAAM,SAAS,OAAO,QAAQ,IAAI;AAClC,QAAI,QAAQ;AACV,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAGA,QAAM,cAAc,OAAO;AAC3B,MAAI,eAAe,OAAO,QAAQ,WAAW,GAAG;AAC9C,WAAO,EAAE,MAAM,aAAa,QAAQ,OAAO,QAAQ,WAAW,EAAE;AAAA,EAClE;AAGA,QAAM,cAAc,OAAO,KAAK,OAAO,OAAO;AAC9C,MAAI,YAAY,WAAW,GAAG;AAC5B,UAAMA,QAAO,YAAY,CAAC;AAC1B,WAAO,EAAE,MAAAA,OAAM,QAAQ,OAAO,QAAQA,KAAI,EAAE;AAAA,EAC9C;AAEA,SAAO;AACT;AAKO,SAAS,UAAU,MAAc,KAAa,aAA4B;AAC/E,QAAM,SAAS,WAAW;AAC1B,SAAO,QAAQ,IAAI,IAAI,EAAE,KAAK,YAAY;AAG1C,MAAI,CAAC,OAAO,iBAAiB,OAAO,KAAK,OAAO,OAAO,EAAE,WAAW,GAAG;AACrE,WAAO,gBAAgB;AAAA,EACzB;AAEA,aAAW,MAAM;AACnB;AAKO,SAAS,aAAa,MAAuB;AAClD,QAAM,SAAS,WAAW;AAE1B,MAAI,CAAC,OAAO,QAAQ,IAAI,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,QAAQ,IAAI;AAG1B,MAAI,OAAO,kBAAkB,MAAM;AACjC,UAAM,YAAY,OAAO,KAAK,OAAO,OAAO;AAC5C,WAAO,gBAAgB,UAAU,SAAS,IAAI,UAAU,CAAC,IAAI;AAAA,EAC/D;AAEA,aAAW,MAAM;AACjB,SAAO;AACT;AAKO,SAAS,iBAAiB,MAAuB;AACtD,QAAM,SAAS,WAAW;AAE1B,MAAI,CAAC,OAAO,QAAQ,IAAI,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO,gBAAgB;AACvB,aAAW,MAAM;AACjB,SAAO;AACT;AAKO,SAAS,cAA2E;AACzF,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO,QAAQ,OAAO,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,MAAM,OAAO;AAAA,IAC7D;AAAA,IACA;AAAA,IACA,WAAW,SAAS,OAAO;AAAA,EAC7B,EAAE;AACJ;AAKO,SAAS,UAAuB;AACrC,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO,QAAQ;AACxB;AAKO,SAAS,QAAQ,MAAyB;AAC/C,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO;AACd,aAAW,MAAM;AACnB;AAKO,SAAS,cAAc,YAA2D;AAEvF,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,QAAQ;AACV,WAAO,EAAE,KAAK,QAAQ,MAAM,iBAAiB;AAAA,EAC/C;AAGA,QAAM,SAAS,UAAU,UAAU;AACnC,MAAI,QAAQ;AACV,WAAO;AAAA,MACL,KAAK,OAAO,OAAO;AAAA,MACnB,MAAM,OAAO;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,mBAAwD;AACtE,QAAM,SAAS,WAAW;AAC1B,QAAM,cAAc,OAAO,SAAS,QAAQ,eAAe,QAAS;AACpE,QAAM,eAAe,YAAY,WAAW,GAAG,IAC3C,YAAY,QAAQ,KAAK,QAAQ,CAAC,IAClC;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,WAAW,OAAO,SAAS,aAAa,eAAe,QAAS;AAAA,EAClE;AACF;;;AE5PA,SAAS,gBAAgB,cAAAC,aAAY,gBAAAC,eAAc,gBAAgB;AAO5D,SAAS,SAAS,OAA2B;AAClD,kBAAgB;AAChB,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAClC,QAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,iBAAe,MAAM,MAAM,OAAO;AACpC;AAKO,SAAS,cAA8B;AAC5C,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAElC,MAAI,CAACC,YAAW,IAAI,GAAG;AACrB,WAAO,CAAC;AAAA,EACV;AAEA,MAAI;AACF,UAAM,UAAUC,cAAa,MAAM,OAAO;AAC1C,UAAM,QAAQ,QAAQ,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AACvD,WAAO,MAAM,IAAI,UAAQ,KAAK,MAAM,IAAI,CAAiB;AAAA,EAC3D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAeO,SAAS,WAAW,SAAwB,CAAC,GAAmB;AACrE,MAAI,UAAU,YAAY;AAG1B,MAAI,OAAO,QAAQ;AACjB,cAAU,QAAQ,OAAO,OAAK,EAAE,WAAW,OAAO,MAAM;AAAA,EAC1D;AAGA,MAAI,OAAO,QAAQ;AACjB,cAAU,QAAQ,OAAO,OAAK,CAAC,EAAE,OAAO;AAAA,EAC1C;AACA,MAAI,OAAO,SAAS;AAClB,cAAU,QAAQ,OAAO,OAAK,EAAE,OAAO;AAAA,EACzC;AAGA,MAAI,OAAO,OAAO;AAChB,UAAM,QAAQ,oBAAI,KAAK;AACvB,UAAM,SAAS,GAAG,GAAG,GAAG,CAAC;AACzB,cAAU,QAAQ,OAAO,OAAK,IAAI,KAAK,EAAE,SAAS,KAAK,KAAK;AAAA,EAC9D;AACA,MAAI,OAAO,OAAO;AAChB,cAAU,QAAQ,OAAO,OAAK,IAAI,KAAK,EAAE,SAAS,KAAK,OAAO,KAAM;AAAA,EACtE;AACA,MAAI,OAAO,OAAO;AAChB,cAAU,QAAQ,OAAO,OAAK,IAAI,KAAK,EAAE,SAAS,KAAK,OAAO,KAAM;AAAA,EACtE;AAGA,UAAQ,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;AAGxF,MAAI,OAAO,MAAM;AACf,cAAU,QAAQ,MAAM,GAAG,OAAO,IAAI;AAAA,EACxC;AAEA,SAAO;AACT;AAKO,SAAS,YAAY,OAAqB,UAAU,OAAe;AACxE,QAAM,OAAO,IAAI,KAAK,MAAM,SAAS,EAAE,eAAe;AACtD,QAAM,SAAS,MAAM,UAAU,WAAM;AACrC,QAAM,UAAU,MAAM,aAAa,MAAM,QAAQ,CAAC;AAElD,MAAI,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,MAAM,MAAM,MAAM,MAAM,SAAS,MAAM,WAAW;AAEpF,MAAI,CAAC,MAAM,WAAW,MAAM,OAAO;AACjC,YAAQ,MAAM,MAAM,KAAK;AAAA,EAC3B;AAEA,MAAI,WAAW,MAAM,SAAS,iBAAiB,SAAS;AACtD,UAAM,UAAU,MAAM,QAAQ,gBAAgB,QAAQ,UAAU,GAAG,EAAE;AACrE,YAAQ;AAAA,KAAQ,OAAO,GAAG,MAAM,QAAQ,gBAAgB,QAAQ,SAAS,KAAK,QAAQ,EAAE;AAAA,EAC1F;AAEA,SAAO;AACT;AAKO,SAAS,kBAAiF;AAC/F,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAElC,MAAI,CAACD,YAAW,IAAI,GAAG;AACrB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,QAAQ,SAAS,IAAI;AAC3B,UAAM,UAAU,YAAY,EAAE;AAC9B,WAAO;AAAA,MACL;AAAA,MACA,WAAW,MAAM;AAAA,MACjB,QAAQ,KAAK,MAAO,MAAM,OAAO,OAAO,OAAQ,GAAG,IAAI;AAAA,IACzD;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,iBAAyB;AACvC,SAAO,iBAAiB,EAAE;AAC5B;;;ACzHO,SAAS,sBAAsBE,MAAgB;AAEpD,EAAAA,KACG,QAAQ,UAAU,oBAAoB,EACtC,OAAO,MAAM;AACZ,UAAM,SAAS,WAAW;AAC1B,UAAM,UAAU,YAAY;AAE5B,YAAQ,IAAI,gBAAgB,WAAW,EAAE;AACzC,YAAQ,IAAI,EAAE;AAEd,YAAQ,IAAI,UAAU;AACtB,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAI,qBAAqB;AACjC,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,iBAAiB;AAC7B,cAAQ,IAAI,mCAAmC;AAAA,IACjD,OAAO;AACL,iBAAW,EAAE,MAAM,QAAQ,UAAU,KAAK,SAAS;AACjD,cAAM,cAAc,YAAY,eAAe;AAC/C,gBAAQ,IAAI,KAAK,IAAI,GAAG,WAAW,EAAE;AACrC,gBAAQ,IAAI,YAAY,OAAO,GAAG,EAAE;AACpC,YAAI,OAAO,aAAa;AACtB,kBAAQ,IAAI,aAAa,OAAO,WAAW,EAAE;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,SAAS,OAAO,QAAQ,MAAM,EAAE;AAC5C,YAAQ,IAAI,YAAY,OAAO,SAAS,SAAS,SAAS,MAAM,EAAE;AAElE,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,UAAU;AACtB,YAAQ,IAAI,WAAW,eAAe,CAAC,EAAE;AAEzC,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,cAAc;AAC1B,YAAQ,IAAI,oBAAoB,QAAQ,IAAI,iBAAiB,WAAW,EAAE;AAAA,EAC5E,CAAC;AAGH,EAAAA,KACG,QAAQ,gBAAgB,gCAAgC,EACxD,QAAQ,wCAAwC,EAChD,QAAQ,kCAAkC,EAC1C,QAAQ,kCAAkC,EAC1C,OAAO,CAAC,UAAmB;AAC1B,QAAI,CAAC,OAAO;AACV,YAAM,OAAO,QAAQ;AACrB,cAAQ,IAAI,SAAS,IAAI,EAAE;AAC3B,cAAQ,IAAI,UAAU,SAAS,SAAS,uBAAuB,oBAAoB,EAAE;AACrF;AAAA,IACF;AAEA,QAAI,UAAU,UAAU,UAAU,QAAQ;AACxC,cAAQ,MAAM,qCAAqC;AACnD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,KAAoB;AAC5B,YAAQ,IAAI,uBAAkB,KAAK,EAAE;AAAA,EACvC,CAAC;AAGH,EAAAA,KACG,QAAQ,gCAAgC,wBAAwB,EAChE,OAAO,4BAA4B,oBAAoB,EACvD,OAAO,mBAAmB,8BAA8B,EACxD,OAAO,aAAa,uBAAuB,EAC3C,QAAQ,iDAAiD,EACzD,QAAQ,+CAA+C,EACvD,QAAQ,kDAAkD,EAC1D,QAAQ,gDAAgD,EACxD,QAAQ,kDAAkD,EAC1D,OAAO,CAAC,QAAiB,MAAe,KAAc,YAAiE;AAEtH,QAAI,CAAC,QAAQ;AACX,qBAAe;AACf;AAAA,IACF;AAGA,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,YAAI,CAAC,QAAQ,CAAC,KAAK;AACjB,kBAAQ,MAAM,sCAAsC;AACpD,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,kBAAU,MAAM,KAAK,SAAS,IAAI;AAClC,gBAAQ,IAAI,wBAAmB,IAAI,EAAE;AACrC,YAAI,SAAS,SAAS;AACpB,2BAAiB,IAAI;AACrB,kBAAQ,IAAI,uBAAkB;AAAA,QAChC;AACA;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AACH,YAAI,CAAC,MAAM;AACT,kBAAQ,MAAM,+BAA+B;AAC7C,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,YAAI,aAAa,IAAI,GAAG;AACtB,kBAAQ,IAAI,0BAAqB,IAAI,EAAE;AAAA,QACzC,OAAO;AACL,kBAAQ,MAAM,qBAAqB,IAAI,EAAE;AACzC,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA;AAAA,MAEF,KAAK;AACH,YAAI,CAAC,MAAM;AACT,kBAAQ,MAAM,oCAAoC;AAClD,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,YAAI,iBAAiB,IAAI,GAAG;AAC1B,kBAAQ,IAAI,0BAAqB,IAAI,EAAE;AAAA,QACzC,OAAO;AACL,kBAAQ,MAAM,qBAAqB,IAAI,EAAE;AACzC,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AACH,YAAI,CAAC,MAAM;AACT,kBAAQ,MAAM,0CAA0C;AACxD,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM,UAAU,YAAY;AAC5B,cAAM,WAAW,QAAQ,KAAK,OAAK,EAAE,SAAS,IAAI;AAClD,YAAI,CAAC,UAAU;AACb,kBAAQ,MAAM,qBAAqB,IAAI,EAAE;AACzC,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM,SAAS,SAAS,OAAO,SAAS,OAAO;AAC/C,cAAM,UAAU,SAAS,SAAS,SAAY,QAAQ,OAAO,SAAS,OAAO;AAC7E,kBAAU,MAAM,QAAQ,OAAO;AAC/B,gBAAQ,IAAI,0BAAqB,IAAI,EAAE;AACvC;AAAA,MAEF,KAAK;AACH,uBAAe;AACf;AAAA,MAEF;AACE,gBAAQ,MAAM,mBAAmB,MAAM,EAAE;AACzC,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,oBAAoB;AAChC,gBAAQ,IAAI,oCAAoC;AAChD,gBAAQ,IAAI,uCAAuC;AACnD,gBAAQ,IAAI,0CAA0C;AACtD,gBAAQ,IAAI,uCAAuC;AACnD,gBAAQ,IAAI,wCAAwC;AACpD,gBAAQ,KAAK,CAAC;AAAA,IAClB;AAAA,EACF,CAAC;AAGH,EAAAA,KACG,QAAQ,WAAW,kBAAkB,EACrC,OAAO,MAAM;AACZ,mBAAe;AAAA,EACjB,CAAC;AACL;AAEA,SAAS,iBAAuB;AAC9B,QAAM,UAAU,YAAY;AAE5B,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,wBAAwB;AACpC,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,eAAe;AAC3B,YAAQ,IAAI,iCAAiC;AAC7C;AAAA,EACF;AAEA,UAAQ,IAAI,UAAU;AACtB,aAAW,EAAE,MAAM,QAAQ,UAAU,KAAK,SAAS;AACjD,UAAM,cAAc,YAAY,YAAO;AACvC,YAAQ,IAAI,KAAK,IAAI,GAAG,WAAW,EAAE;AACrC,YAAQ,IAAI,OAAO,OAAO,GAAG,EAAE;AAC/B,QAAI,OAAO,aAAa;AACtB,cAAQ,IAAI,OAAO,OAAO,WAAW,EAAE;AAAA,IACzC;AAAA,EACF;AAEA,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,yBAAoB;AAClC;;;AC7MA,SAAS,cAAAC,mBAAkB;AAcpB,SAAS,uBAAuBC,MAAgB;AACrD,EAAAA,KACG,QAAQ,WAAW,mBAAmB,EACtC,OAAO,kBAAkB,uBAAuB,EAAE,SAAS,GAAG,CAAC,EAC/D,OAAO,WAAW,2BAA4B,EAC9C,OAAO,YAAY,wBAAwB,EAC3C,OAAO,aAAa,4BAA4B,EAChD,OAAO,uBAAuB,uBAAuB,EACrD,OAAO,UAAU,gBAAgB,EACjC,OAAO,iBAAiB,sBAAsB,EAC9C,QAAQ,eAAe,EACvB,QAAQ,yBAAyB,EACjC,QAAQ,gCAAgC,EACxC,QAAQ,+BAA+B,EACvC,OAAO,CAAC,YAA4B;AACnC,UAAM,SAAwB;AAAA,MAC5B,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,IAClB;AAEA,UAAM,UAAU,WAAW,MAAM;AAEjC,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC5C;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAI,2BAA2B;AACvC,cAAQ,IAAI,iBAAiB,eAAe,CAAC,EAAE;AAC/C;AAAA,IACF;AAGA,UAAM,QAAQ,gBAAgB;AAC9B,QAAI,OAAO;AACT,cAAQ,IAAI,YAAY,MAAM,OAAO,mBAAmB,MAAM,MAAM,MAAM;AAC1E,cAAQ,IAAI,EAAE;AAAA,IAChB;AAGA,UAAM,cAAwB,CAAC;AAC/B,QAAI,QAAQ,MAAO,aAAY,KAAK,OAAO;AAC3C,QAAI,QAAQ,OAAQ,aAAY,KAAK,QAAQ;AAC7C,QAAI,QAAQ,QAAS,aAAY,KAAK,SAAS;AAC/C,QAAI,QAAQ,OAAQ,aAAY,KAAK,WAAW,QAAQ,MAAM,EAAE;AAChE,QAAI,YAAY,SAAS,GAAG;AAC1B,cAAQ,IAAI,WAAW,YAAY,KAAK,IAAI,CAAC,EAAE;AAC/C,cAAQ,IAAI,EAAE;AAAA,IAChB;AAGA,YAAQ,IAAI,WAAW,QAAQ,MAAM,+BAA+B;AACpE,YAAQ,IAAI,EAAE;AAEd,eAAW,SAAS,SAAS;AAC3B,cAAQ,IAAI,YAAY,OAAO,QAAQ,OAAO,CAAC;AAAA,IACjD;AAAA,EACF,CAAC;AAGH,EAAAA,KACG,QAAQ,iBAAiB,oBAAoB,EAC7C,OAAO,aAAa,kBAAkB,EACtC,OAAO,CAAC,YAAmC;AAC1C,UAAM,cAAc,eAAe;AAEnC,QAAI,CAAC,QAAQ,SAAS;AACpB,cAAQ,IAAI,yDAAyD;AACrE,cAAQ,IAAI,iBAAiB,WAAW,EAAE;AAC1C;AAAA,IACF;AAEA,QAAI;AACF,MAAAC,YAAW,WAAW;AACtB,cAAQ,IAAI,wBAAmB;AAAA,IACjC,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,UAAU;AACpD,gBAAQ,IAAI,8BAA8B;AAAA,MAC5C,OAAO;AACL,gBAAQ,MAAM,2BAA2B,GAAG;AAAA,MAC9C;AAAA,IACF;AAAA,EACF,CAAC;AAGH,EAAAD,KACG,QAAQ,iBAAiB,yBAAyB,EAClD,OAAO,MAAM;AACZ,UAAM,QAAQ,gBAAgB;AAE9B,QAAI,CAAC,OAAO;AACV,cAAQ,IAAI,wBAAwB;AACpC;AAAA,IACF;AAEA,UAAM,UAAU,WAAW,CAAC,CAAC;AAC7B,UAAM,eAAe,QAAQ,OAAO,OAAK,EAAE,OAAO,EAAE;AACpD,UAAM,cAAc,QAAQ,OAAO,OAAK,CAAC,EAAE,OAAO,EAAE;AACpD,UAAM,aAAa,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AACnE,UAAM,WAAW,QAAQ,SAAS,IAAI,KAAK,MAAM,aAAa,QAAQ,MAAM,IAAI;AAChF,UAAM,cAAc,QAAQ,SAAS,IACjC,KAAK,MAAM,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,aAAa,CAAC,IAAI,QAAQ,MAAM,IAC9E;AAGJ,UAAM,WAAW,oBAAI,IAAoB;AACzC,eAAW,SAAS,SAAS;AAC3B,eAAS,IAAI,MAAM,SAAS,SAAS,IAAI,MAAM,MAAM,KAAK,KAAK,CAAC;AAAA,IAClE;AAEA,YAAQ,IAAI,oBAAoB;AAChC,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,aAAa,eAAe,CAAC,EAAE;AAC3C,YAAQ,IAAI,aAAa,MAAM,MAAM,KAAK;AAC1C,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,aAAa,QAAQ,MAAM,QAAQ;AAC/C,YAAQ,IAAI,aAAa,YAAY,KAAK,QAAQ,SAAS,IAAI,KAAK,MAAM,eAAe,QAAQ,SAAS,GAAG,IAAI,CAAC,IAAI;AACtH,YAAQ,IAAI,aAAa,WAAW,KAAK,QAAQ,SAAS,IAAI,KAAK,MAAM,cAAc,QAAQ,SAAS,GAAG,IAAI,CAAC,IAAI;AACpH,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,iBAAiB,QAAQ,QAAQ;AAC7C,YAAQ,IAAI,iBAAiB,WAAW,IAAI;AAE5C,QAAI,SAAS,OAAO,GAAG;AACrB,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,YAAY;AACxB,iBAAW,CAAC,QAAQ,KAAK,KAAK,SAAS,QAAQ,GAAG;AAChD,gBAAQ,IAAI,KAAK,MAAM,KAAK,KAAK,QAAQ;AAAA,MAC3C;AAAA,IACF;AAGA,UAAM,QAAQ,WAAW,EAAE,OAAO,KAAK,CAAC;AACxC,UAAM,WAAW,WAAW,EAAE,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,GAAI,EAAE,CAAC;AACrF,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,cAAc,MAAM,MAAM,QAAQ;AAC9C,YAAQ,IAAI,cAAc,SAAS,MAAM,QAAQ;AAAA,EACnD,CAAC;AACL;;;AC3JA,SAAS,gBAAAE,qBAAoB;;;ACKtB,SAAS,gBAAgB,SAAyB,OAAoB,QAA0B;AACrG,QAAM,aAAa,KAAK,UAAU,OAAO;AACzC,QAAM,YAAY,IAAI,YAAY,EAAE,OAAO,UAAU,EAAE;AACvD,QAAM,aAAa,YAAY,IAAI;AACnC,QAAM,iBAAiB,aAAa;AACpC,QAAM,cAAc,KAAK,MAAO,YAAY,aAAc,GAAI,IAAI;AAElE,QAAM,WAAqB,CAAC;AAC5B,QAAM,SAAmB,CAAC;AAG1B,MAAI,YAAY,YAAY;AAC1B,WAAO,KAAK,mBAAmB,IAAI,gBAAgB,SAAS,YAAY,UAAU,QAAQ;AAAA,EAC5F,WAAW,cAAc,IAAI;AAC3B,aAAS,KAAK,iBAAiB,WAAW,QAAQ,IAAI,aAAa;AAAA,EACrE;AAGA,MAAI,CAAC,QAAQ,iBAAiB;AAC5B,WAAO,KAAK,gCAAgC;AAAA,EAC9C,WAAW,CAAC,QAAQ,gBAAgB,WAAW,CAAC,QAAQ,gBAAgB,MAAM;AAC5E,aAAS,KAAK,6CAA6C;AAAA,EAC7D;AAGA,QAAM,UAAU,QAAQ,iBAAiB,WAAW;AACpD,MAAI,SAAS;AAEX,UAAM,YAAY,QAAQ,MAAM,OAAO,KAAK,CAAC,GAAG;AAChD,UAAM,aAAa,QAAQ,MAAM,UAAU,KAAK,CAAC,GAAG;AACpD,QAAI,aAAa,WAAW;AAC1B,eAAS,KAAK,4BAA4B,QAAQ,UAAU,SAAS,QAAQ;AAAA,IAC/E;AAGA,UAAM,iBAAiB,uCAAuC,KAAK,OAAO;AAC1E,QAAI,CAAC,gBAAgB;AACnB,eAAS,KAAK,8DAA8D;AAAA,IAC9E;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,WAAW;AAAA,IACzB,YAAY;AAAA,IACZ;AAAA,IACA,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACF;AAKO,SAAS,cAAc,SAAiC;AAE7D,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,QAAI,OAAO,iBAAiB;AAC1B,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,iBAAiB,OAAO;AAAA,EACnC,QAAQ;AAEN,WAAO;AAAA,MACL,iBAAiB;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAKO,SAAS,iBAAiB,QAAkC;AACjE,QAAM,QAAkB,CAAC;AAEzB,QAAM,SAAS,OAAO,QAAQ,WAAM;AACpC,QAAM,UAAU,OAAO,aAAa,MAAM,QAAQ,CAAC;AACnD,QAAM,WAAW,OAAO,cAAc,MAAM,QAAQ,CAAC;AAErD,QAAM,KAAK,GAAG,MAAM,aAAa,OAAO,UAAU,WAAW,MAAM,MAAM;AACzE,QAAM,KAAK,WAAW,OAAO,IAAI,YAAY,OAAO,MAAM;AAC1D,QAAM,KAAK,WAAW,OAAO,YAAY,MAAM,OAAO,eAAe,mBAAmB;AAExF,MAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,SAAS;AACpB,eAAW,SAAS,OAAO,QAAQ;AACjC,YAAM,KAAK,YAAO,KAAK,EAAE;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,WAAW;AACtB,eAAW,WAAW,OAAO,UAAU;AACrC,YAAM,KAAK,YAAO,OAAO,EAAE;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACtFA,eAAsB,cACpB,SACA,UAAuB,CAAC,GACH;AACrB,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,OAAO,QAAQ;AAGrB,MAAI;AACJ,MAAI;AAEJ,MAAI,QAAQ,YAAY;AAEtB,iBAAa,QAAQ;AACrB,iBAAa;AAAA,EACf,OAAO;AAEL,UAAM,WAAW,cAAc,QAAQ,MAAM;AAC7C,QAAI,CAAC,UAAU;AACb,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,YAAMC,cAAa,gBAAgB,SAAS,IAAI;AAEhD,UAAI,QAAQ;AACZ,UAAI,QAAQ,QAAQ;AAClB,gBAAQ,WAAW,QAAQ,MAAM;AAAA,MACnC,OAAO;AACL,iBAAS;AAAA,MACX;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY,QAAQ,UAAU;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,YAAAA;AAAA,MACF;AAAA,IACF;AACA,iBAAa,SAAS;AACtB,iBAAa,SAAS;AAAA,EACxB;AAGA,QAAM,aAAa,gBAAgB,SAAS,IAAI;AAEhD,MAAI,CAAC,QAAQ,kBAAkB,CAAC,WAAW,OAAO;AAChD,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,SAAqB;AAAA,MACzB,SAAS;AAAA,MACT;AAAA,MACA,OAAO,WAAW,OAAO,KAAK,IAAI;AAAA,MAClC;AAAA,MACA;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,SAAS;AACpB,eAAS,mBAAmB,SAAS,QAAQ,MAAM,UAAU,CAAC;AAAA,IAChE;AAEA,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,YAAY;AAAA,MACvC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAED,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,eAAe,MAAM,SAAS,KAAK;AAEzC,UAAM,SAAqB;AAAA,MACzB,SAAS,SAAS;AAAA,MAClB;AAAA,MACA,YAAY,SAAS;AAAA,MACrB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO,QAAQ,QAAQ,SAAS,MAAM,KAAK,YAAY;AAAA,IACzD;AAGA,QAAI,CAAC,QAAQ,SAAS;AACpB,eAAS,mBAAmB,SAAS,QAAQ,MAAM,UAAU,CAAC;AAAA,IAChE;AAEA,WAAO;AAAA,EAET,SAAS,KAAK;AACZ,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,QAAQ,eAAe,QAAQ,IAAI,UAAU;AAEnD,UAAM,SAAqB;AAAA,MACzB,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,SAAS;AACpB,eAAS,mBAAmB,SAAS,QAAQ,MAAM,UAAU,CAAC;AAAA,IAChE;AAEA,WAAO;AAAA,EACT;AACF;AAKA,SAAS,mBACP,SACA,QACA,MACA,YACc;AACd,SAAO;AAAA,IACL,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,QAAQ;AAAA,IACR,YAAY,OAAO,WAAW;AAAA,IAC9B;AAAA,IACA;AAAA,IACA,SAAS,OAAO;AAAA,IAChB,aAAa,OAAO;AAAA,IACpB,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,EACtB;AACF;;;AFnJO,SAAS,oBAAoBC,MAAgB;AAClD,EAAAA,KACG,QAAQ,QAAQ,+BAA+B,EAC/C,OAAO,wBAAwB,sBAAsB,EACrD,OAAO,qBAAqB,wBAAwB,EACpD,OAAO,uBAAuB,yCAAyC,EACvE,OAAO,uBAAuB,+BAA+B,EAC7D,OAAO,qBAAqB,yBAAyB,EACrD,OAAO,cAAc,sBAAsB,EAC3C,OAAO,UAAU,uBAAuB,EACxC,QAAQ,4DAA4D,EACpE,QAAQ,iCAAiC,EACzC,QAAQ,iDAAiD,EACzD,QAAQ,2DAA6D,EACrE,OAAO,OAAO,YAAyB;AACtC,QAAI;AAGJ,QAAI,QAAQ,SAAS;AACnB,gBAAU,QAAQ;AAAA,IACpB,WAAW,QAAQ,MAAM;AACvB,UAAI;AACF,kBAAUC,cAAa,QAAQ,MAAM,OAAO;AAAA,MAC9C,SAAS,KAAK;AACZ,gBAAQ,MAAM,uBAAuB,QAAQ,IAAI,EAAE;AACnD,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,OAAO;AAEL,gBAAU,MAAM,UAAU;AAC1B,UAAI,CAAC,SAAS;AACZ,gBAAQ,MAAM,wEAAwE;AACtF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,UAAU,cAAc,OAAO;AAGrC,UAAM,SAAS,MAAM,cAAc,SAAS;AAAA,MAC1C,QAAQ,QAAQ;AAAA,MAChB,YAAY,QAAQ;AAAA,MACpB,gBAAgB,QAAQ;AAAA,MACxB,SAAS,QAAQ;AAAA,IACnB,CAAC;AAGD,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,OAAO;AACL,UAAI,OAAO,SAAS;AAClB,gBAAQ,IAAI,yBAAoB,OAAO,UAAU,GAAG;AACpD,gBAAQ,IAAI,aAAa,OAAO,UAAU,EAAE;AAC5C,gBAAQ,IAAI,WAAW,OAAO,UAAU,IAAI;AAC5C,gBAAQ,IAAI,WAAW,OAAO,WAAW,UAAU,WAAW,OAAO,WAAW,YAAY,aAAa;AAAA,MAC3G,OAAO;AACL,gBAAQ,MAAM,uBAAkB;AAChC,gBAAQ,MAAM,YAAY,OAAO,KAAK,EAAE;AACxC,YAAI,OAAO,YAAY;AACrB,kBAAQ,MAAM,aAAa,OAAO,UAAU,EAAE;AAAA,QAChD;AACA,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,aAAa;AACzB,gBAAQ,IAAI,iBAAiB,OAAO,UAAU,CAAC;AAAA,MACjD;AAAA,IACF;AAEA,YAAQ,KAAK,OAAO,UAAU,IAAI,CAAC;AAAA,EACrC,CAAC;AACL;AAKA,eAAe,YAA6B;AAE1C,MAAI,QAAQ,MAAM,OAAO;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,SAAmB,CAAC;AAE1B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAU,OAAO,KAAK,KAAK,CAAC;AACtD,YAAQ,MAAM,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,CAAC,CAAC;AACrF,YAAQ,MAAM,GAAG,SAAS,MAAM,QAAQ,EAAE,CAAC;AAG3C,eAAW,MAAM;AACf,UAAI,OAAO,WAAW,GAAG;AACvB,gBAAQ,EAAE;AAAA,MACZ;AAAA,IACF,GAAG,GAAG;AAAA,EACR,CAAC;AACH;;;AG9GA,SAAS,gBAAAC,qBAAoB;AAatB,SAAS,wBAAwBC,MAAgB;AACtD,EAAAA,KACG,QAAQ,YAAY,kCAAkC,EACtD,OAAO,wBAAwB,0BAA0B,EACzD,OAAO,qBAAqB,wBAAwB,EACpD,OAAO,qBAAqB,8BAA8B,EAC1D,OAAO,UAAU,uBAAuB,EACxC,QAAQ,qCAAqC,EAC7C,QAAQ,uDAAuD,EAC/D,OAAO,OAAO,YAA6B;AAC1C,QAAI;AAGJ,QAAI,QAAQ,SAAS;AACnB,gBAAU,QAAQ;AAAA,IACpB,WAAW,QAAQ,MAAM;AACvB,UAAI;AACF,kBAAUC,cAAa,QAAQ,MAAM,OAAO;AAAA,MAC9C,SAAS,KAAK;AACZ,gBAAQ,MAAM,uBAAuB,QAAQ,IAAI,EAAE;AACnD,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,OAAO;AAEL,gBAAU,MAAMC,WAAU;AAC1B,UAAI,CAAC,SAAS;AACZ,gBAAQ,MAAM,wEAAwE;AACtF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,OAAO,QAAQ,QAAQ,QAAQ;AAGrC,UAAM,UAAU,cAAc,OAAO;AACrC,UAAM,SAAS,gBAAgB,SAAS,IAAI;AAG5C,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,OAAO;AACL,cAAQ,IAAI,iBAAiB,MAAM,CAAC;AAAA,IACtC;AAEA,YAAQ,KAAK,OAAO,QAAQ,IAAI,CAAC;AAAA,EACnC,CAAC;AACL;AAKA,eAAeA,aAA6B;AAC1C,MAAI,QAAQ,MAAM,OAAO;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,SAAmB,CAAC;AAE1B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAU,OAAO,KAAK,KAAK,CAAC;AACtD,YAAQ,MAAM,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,CAAC,CAAC;AACrF,YAAQ,MAAM,GAAG,SAAS,MAAM,QAAQ,EAAE,CAAC;AAE3C,eAAW,MAAM;AACf,UAAI,OAAO,WAAW,GAAG;AACvB,gBAAQ,EAAE;AAAA,MACZ;AAAA,IACF,GAAG,GAAG;AAAA,EACR,CAAC;AACH;;;ATtEA,IAAM,MAAM,IAAI,OAAO;AAGvB,IAAI,QAAQ,OAAO;AAGnB,oBAAoB,GAAG;AACvB,wBAAwB,GAAG;AAC3B,sBAAsB,GAAG;AACzB,uBAAuB,GAAG;AAG1B,IAAI,KAAK;AAGT,IAAI,QAAQ,EAAE,EAAE,OAAO,MAAM;AAC3B,MAAI,WAAW;AACjB,CAAC;AAGD,IAAI,MAAM;","names":["name","existsSync","readFileSync","existsSync","readFileSync","cli","unlinkSync","cli","unlinkSync","readFileSync","validation","cli","readFileSync","readFileSync","cli","readFileSync","readStdin"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/lib/config.ts","../src/types.ts","../src/lib/logger.ts","../src/commands/config.ts","../src/commands/history.ts","../src/commands/send.ts","../src/lib/validator.ts","../src/lib/webhook.ts","../src/commands/validate.ts"],"sourcesContent":["/**\n * trmnl-cli - CLI tool for TRMNL e-ink displays\n *\n * Commands:\n * trmnl send - Send content to TRMNL display\n * trmnl validate - Validate payload without sending\n * trmnl config - Manage CLI configuration\n * trmnl history - View send history\n */\n\nimport { createRequire } from \"node:module\";\nimport cac from \"cac\";\nimport { registerConfigCommand } from \"./commands/config.ts\";\nimport { registerHistoryCommand } from \"./commands/history.ts\";\nimport { registerSendCommand } from \"./commands/send.ts\";\nimport { registerValidateCommand } from \"./commands/validate.ts\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\");\n\nconst cli = cac(\"trmnl\");\n\ncli.version(version);\n\n// Register commands\nregisterSendCommand(cli);\nregisterValidateCommand(cli);\nregisterConfigCommand(cli);\nregisterHistoryCommand(cli);\n\n// Help text\ncli.help();\n\n// Default action (no command)\ncli.command(\"\").action(() => {\n cli.outputHelp();\n});\n\n// Parse and run\ncli.parse();\n","/**\n * Config file management for ~/.trmnl/config.json\n * Supports multiple plugins with a default\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { Config, Plugin, WebhookTier } from '../types.ts';\nimport { DEFAULT_CONFIG } from '../types.ts';\n\n/** Config directory path */\nexport const CONFIG_DIR = join(homedir(), '.trmnl');\n\n/** Config file path */\nexport const CONFIG_PATH = join(CONFIG_DIR, 'config.json');\n\n/** Legacy TOML config path (for migration) */\nconst LEGACY_CONFIG_PATH = join(CONFIG_DIR, 'config.toml');\n\n/** History file path */\nexport const HISTORY_PATH = join(CONFIG_DIR, 'history.jsonl');\n\n/**\n * Ensure ~/.trmnl directory exists\n */\nexport function ensureConfigDir(): void {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n}\n\n/**\n * Migrate legacy TOML config to JSON (one-time)\n */\nfunction migrateLegacyConfig(): Config | null {\n if (!existsSync(LEGACY_CONFIG_PATH)) {\n return null;\n }\n\n try {\n const content = readFileSync(LEGACY_CONFIG_PATH, 'utf-8');\n const config: Config = { plugins: {}, tier: 'free' };\n\n // Parse legacy TOML (simple parser for our format)\n let webhookUrl: string | undefined;\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n const urlMatch = trimmed.match(/^url\\s*=\\s*\"([^\"]+)\"/);\n if (urlMatch) webhookUrl = urlMatch[1];\n }\n\n if (webhookUrl) {\n config.plugins['default'] = { url: webhookUrl };\n config.defaultPlugin = 'default';\n }\n\n // Remove legacy file after migration\n unlinkSync(LEGACY_CONFIG_PATH);\n console.log('Migrated legacy config.toml to config.json');\n\n return config;\n } catch {\n return null;\n }\n}\n\n/**\n * Load config from file\n */\nexport function loadConfig(): Config {\n ensureConfigDir();\n\n // Try to migrate legacy config first\n const migrated = migrateLegacyConfig();\n if (migrated) {\n saveConfig(migrated);\n return migrated;\n }\n\n if (!existsSync(CONFIG_PATH)) {\n return { ...DEFAULT_CONFIG };\n }\n\n try {\n const content = readFileSync(CONFIG_PATH, 'utf-8');\n const parsed = JSON.parse(content) as Partial<Config>;\n return {\n plugins: parsed.plugins || {},\n defaultPlugin: parsed.defaultPlugin,\n tier: parsed.tier || 'free',\n history: parsed.history || DEFAULT_CONFIG.history,\n };\n } catch {\n return { ...DEFAULT_CONFIG };\n }\n}\n\n/**\n * Save config to file\n */\nexport function saveConfig(config: Config): void {\n ensureConfigDir();\n const content = JSON.stringify(config, null, 2);\n writeFileSync(CONFIG_PATH, content, 'utf-8');\n}\n\n/**\n * Get a plugin by name (or default if not specified)\n */\nexport function getPlugin(name?: string): { name: string; plugin: Plugin } | null {\n const config = loadConfig();\n \n // If name specified, use that\n if (name) {\n const plugin = config.plugins[name];\n if (plugin) {\n return { name, plugin };\n }\n return null;\n }\n\n // Use default plugin\n const defaultName = config.defaultPlugin;\n if (defaultName && config.plugins[defaultName]) {\n return { name: defaultName, plugin: config.plugins[defaultName] };\n }\n\n // If only one plugin, use it\n const pluginNames = Object.keys(config.plugins);\n if (pluginNames.length === 1) {\n const name = pluginNames[0];\n return { name, plugin: config.plugins[name] };\n }\n\n return null;\n}\n\n/**\n * Add or update a plugin\n */\nexport function setPlugin(name: string, url: string, description?: string): void {\n const config = loadConfig();\n config.plugins[name] = { url, description };\n \n // If no default and this is the first plugin, make it default\n if (!config.defaultPlugin || Object.keys(config.plugins).length === 1) {\n config.defaultPlugin = name;\n }\n \n saveConfig(config);\n}\n\n/**\n * Remove a plugin\n */\nexport function removePlugin(name: string): boolean {\n const config = loadConfig();\n \n if (!config.plugins[name]) {\n return false;\n }\n \n delete config.plugins[name];\n \n // If we removed the default, pick a new one\n if (config.defaultPlugin === name) {\n const remaining = Object.keys(config.plugins);\n config.defaultPlugin = remaining.length > 0 ? remaining[0] : undefined;\n }\n \n saveConfig(config);\n return true;\n}\n\n/**\n * Set the default plugin\n */\nexport function setDefaultPlugin(name: string): boolean {\n const config = loadConfig();\n \n if (!config.plugins[name]) {\n return false;\n }\n \n config.defaultPlugin = name;\n saveConfig(config);\n return true;\n}\n\n/**\n * List all plugins\n */\nexport function listPlugins(): Array<{ name: string; plugin: Plugin; isDefault: boolean }> {\n const config = loadConfig();\n return Object.entries(config.plugins).map(([name, plugin]) => ({\n name,\n plugin,\n isDefault: name === config.defaultPlugin,\n }));\n}\n\n/**\n * Get global tier setting\n */\nexport function getTier(): WebhookTier {\n const config = loadConfig();\n return config.tier || 'free';\n}\n\n/**\n * Set global tier setting\n */\nexport function setTier(tier: WebhookTier): void {\n const config = loadConfig();\n config.tier = tier;\n saveConfig(config);\n}\n\n/**\n * Get webhook URL from environment, plugin name, or default\n */\nexport function getWebhookUrl(pluginName?: string): { url: string; name: string } | null {\n // Environment variable takes highest precedence\n const envUrl = process.env.TRMNL_WEBHOOK;\n if (envUrl) {\n return { url: envUrl, name: '$TRMNL_WEBHOOK' };\n }\n\n // Try to get plugin\n const result = getPlugin(pluginName);\n if (result) {\n return {\n url: result.plugin.url,\n name: result.name,\n };\n }\n\n return null;\n}\n\n/**\n * Get history config\n */\nexport function getHistoryConfig(): { path: string; maxSizeMb: number } {\n const config = loadConfig();\n const historyPath = config.history?.path || DEFAULT_CONFIG.history!.path!;\n const expandedPath = historyPath.startsWith('~') \n ? historyPath.replace('~', homedir())\n : historyPath;\n \n return {\n path: expandedPath,\n maxSizeMb: config.history?.maxSizeMb || DEFAULT_CONFIG.history!.maxSizeMb!,\n };\n}\n","/**\n * TRMNL CLI Types\n */\n\n/** Webhook tier determines payload size limits */\nexport type WebhookTier = 'free' | 'plus';\n\n/** Size limits per tier in bytes */\nexport const TIER_LIMITS: Record<WebhookTier, number> = {\n free: 2048, // 2 KB\n plus: 5120, // 5 KB\n};\n\n/** Plugin configuration */\nexport interface Plugin {\n url: string;\n description?: string;\n}\n\n/** CLI configuration stored in ~/.trmnl/config.json */\nexport interface Config {\n plugins: Record<string, Plugin>;\n defaultPlugin?: string;\n tier?: WebhookTier; // Global tier setting\n history?: {\n path?: string;\n maxSizeMb?: number;\n };\n}\n\n/** Default config values */\nexport const DEFAULT_CONFIG: Config = {\n plugins: {},\n defaultPlugin: undefined,\n tier: 'free',\n history: {\n path: '~/.trmnl/history.jsonl',\n maxSizeMb: 100,\n },\n};\n\n/** Merge variables payload structure */\nexport interface MergeVariables {\n content?: string;\n title?: string;\n text?: string;\n image?: string;\n [key: string]: string | undefined;\n}\n\n/** Webhook request payload */\nexport interface WebhookPayload {\n merge_variables: MergeVariables;\n}\n\n/** History entry stored in JSONL */\nexport interface HistoryEntry {\n timestamp: string;\n plugin: string;\n size_bytes: number;\n tier: WebhookTier;\n payload: WebhookPayload;\n success: boolean;\n status_code?: number;\n response?: string;\n error?: string;\n duration_ms: number;\n}\n\n/** Validation result */\nexport interface ValidationResult {\n valid: boolean;\n size_bytes: number;\n tier: WebhookTier;\n limit_bytes: number;\n remaining_bytes: number;\n percent_used: number;\n warnings: string[];\n errors: string[];\n}\n","/**\n * JSONL history logger for tracking sent payloads\n */\n\nimport { appendFileSync, existsSync, readFileSync, statSync } from 'node:fs';\nimport { ensureConfigDir, getHistoryConfig } from './config.ts';\nimport type { HistoryEntry } from '../types.ts';\n\n/**\n * Append a history entry to the JSONL file\n */\nexport function logEntry(entry: HistoryEntry): void {\n ensureConfigDir();\n const { path } = getHistoryConfig();\n const line = JSON.stringify(entry) + '\\n';\n appendFileSync(path, line, 'utf-8');\n}\n\n/**\n * Read all history entries\n */\nexport function readHistory(): HistoryEntry[] {\n const { path } = getHistoryConfig();\n \n if (!existsSync(path)) {\n return [];\n }\n\n try {\n const content = readFileSync(path, 'utf-8');\n const lines = content.trim().split('\\n').filter(Boolean);\n return lines.map(line => JSON.parse(line) as HistoryEntry);\n } catch {\n return [];\n }\n}\n\n/**\n * Get history entries with filters\n */\nexport interface HistoryFilter {\n last?: number;\n today?: boolean;\n failed?: boolean;\n success?: boolean;\n plugin?: string;\n since?: Date;\n until?: Date;\n}\n\nexport function getHistory(filter: HistoryFilter = {}): HistoryEntry[] {\n let entries = readHistory();\n\n // Filter by plugin\n if (filter.plugin) {\n entries = entries.filter(e => e.plugin === filter.plugin);\n }\n\n // Filter by success/failed\n if (filter.failed) {\n entries = entries.filter(e => !e.success);\n }\n if (filter.success) {\n entries = entries.filter(e => e.success);\n }\n\n // Filter by date\n if (filter.today) {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n entries = entries.filter(e => new Date(e.timestamp) >= today);\n }\n if (filter.since) {\n entries = entries.filter(e => new Date(e.timestamp) >= filter.since!);\n }\n if (filter.until) {\n entries = entries.filter(e => new Date(e.timestamp) <= filter.until!);\n }\n\n // Sort by timestamp descending (most recent first)\n entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n\n // Limit results\n if (filter.last) {\n entries = entries.slice(0, filter.last);\n }\n\n return entries;\n}\n\n/**\n * Format a history entry for display\n */\nexport function formatEntry(entry: HistoryEntry, verbose = false): string {\n const time = new Date(entry.timestamp).toLocaleString();\n const status = entry.success ? '✓' : '✗';\n const sizeKb = (entry.size_bytes / 1024).toFixed(2);\n \n let line = `${status} ${time} | ${entry.plugin} | ${sizeKb} KB | ${entry.duration_ms}ms`;\n \n if (!entry.success && entry.error) {\n line += ` | ${entry.error}`;\n }\n \n if (verbose && entry.payload?.merge_variables?.content) {\n const preview = entry.payload.merge_variables.content.substring(0, 80);\n line += `\\n ${preview}${entry.payload.merge_variables.content.length > 80 ? '...' : ''}`;\n }\n \n return line;\n}\n\n/**\n * Get history file stats\n */\nexport function getHistoryStats(): { entries: number; sizeBytes: number; sizeMb: number } | null {\n const { path } = getHistoryConfig();\n \n if (!existsSync(path)) {\n return null;\n }\n\n try {\n const stats = statSync(path);\n const entries = readHistory().length;\n return {\n entries,\n sizeBytes: stats.size,\n sizeMb: Math.round((stats.size / 1024 / 1024) * 100) / 100,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Get history file path\n */\nexport function getHistoryPath(): string {\n return getHistoryConfig().path;\n}\n","/**\n * trmnl config - Manage CLI configuration\n * trmnl plugin - Manage webhook plugins\n */\n\nimport type { CAC } from 'cac';\nimport {\n CONFIG_PATH,\n getTier,\n listPlugins,\n loadConfig,\n removePlugin,\n setDefaultPlugin,\n setPlugin,\n setTier,\n} from '../lib/config.ts';\nimport { getHistoryPath } from '../lib/logger.ts';\nimport type { WebhookTier } from '../types.ts';\n\nexport function registerConfigCommand(cli: CAC): void {\n // Config show\n cli\n .command('config', 'Show configuration')\n .action(() => {\n const config = loadConfig();\n const plugins = listPlugins();\n\n console.log(`Config file: ${CONFIG_PATH}`);\n console.log('');\n\n console.log('Plugins:');\n if (plugins.length === 0) {\n console.log(' (none configured)');\n console.log('');\n console.log(' Add a plugin:');\n console.log(' trmnl plugin add <name> <url>');\n } else {\n for (const { name, plugin, isDefault } of plugins) {\n const defaultMark = isDefault ? ' (default)' : '';\n console.log(` ${name}${defaultMark}`);\n console.log(` url: ${plugin.url}`);\n if (plugin.description) {\n console.log(` desc: ${plugin.description}`);\n }\n }\n }\n\n console.log('');\n console.log(`Tier: ${config.tier || 'free'}`);\n console.log(` Limit: ${config.tier === 'plus' ? '5 KB' : '2 KB'}`);\n\n console.log('');\n console.log('History:');\n console.log(` path: ${getHistoryPath()}`);\n\n console.log('');\n console.log('Environment:');\n console.log(` TRMNL_WEBHOOK: ${process.env.TRMNL_WEBHOOK || '(not set)'}`);\n });\n\n // Tier command (separate from config)\n cli\n .command('tier [value]', 'Get or set tier (free or plus)')\n .example('trmnl tier # Show current tier')\n .example('trmnl tier plus # Set to plus')\n .example('trmnl tier free # Set to free')\n .action((value?: string) => {\n if (!value) {\n const tier = getTier();\n console.log(`Tier: ${tier}`);\n console.log(`Limit: ${tier === 'plus' ? '5 KB (5,120 bytes)' : '2 KB (2,048 bytes)'}`);\n return;\n }\n\n if (value !== 'free' && value !== 'plus') {\n console.error('Invalid tier. Use \"free\" or \"plus\".');\n process.exit(1);\n }\n\n setTier(value as WebhookTier);\n console.log(`✓ Tier set to: ${value}`);\n });\n\n // Plugin command with action as first arg\n cli\n .command('plugin [action] [name] [url]', 'Manage webhook plugins')\n .option('-d, --desc <description>', 'Plugin description')\n .option('-u, --url <url>', 'Webhook URL (for set action)')\n .option('--default', 'Set as default plugin')\n .example('trmnl plugin # List plugins')\n .example('trmnl plugin add home <url> # Add plugin')\n .example('trmnl plugin rm home # Remove plugin')\n .example('trmnl plugin default home # Set default')\n .example('trmnl plugin set home --url ... # Update plugin')\n .action((action?: string, name?: string, url?: string, options?: { desc?: string; url?: string; default?: boolean }) => {\n // No action = list\n if (!action) {\n showPluginList();\n return;\n }\n\n // Handle actions\n switch (action) {\n case 'add':\n if (!name || !url) {\n console.error('Usage: trmnl plugin add <name> <url>');\n process.exit(1);\n }\n setPlugin(name, url, options?.desc);\n console.log(`✓ Added plugin: ${name}`);\n if (options?.default) {\n setDefaultPlugin(name);\n console.log(`✓ Set as default`);\n }\n break;\n\n case 'rm':\n case 'remove':\n if (!name) {\n console.error('Usage: trmnl plugin rm <name>');\n process.exit(1);\n }\n if (removePlugin(name)) {\n console.log(`✓ Removed plugin: ${name}`);\n } else {\n console.error(`Plugin not found: ${name}`);\n process.exit(1);\n }\n break;\n\n case 'default':\n if (!name) {\n console.error('Usage: trmnl plugin default <name>');\n process.exit(1);\n }\n if (setDefaultPlugin(name)) {\n console.log(`✓ Default plugin: ${name}`);\n } else {\n console.error(`Plugin not found: ${name}`);\n process.exit(1);\n }\n break;\n\n case 'set':\n case 'update':\n if (!name) {\n console.error('Usage: trmnl plugin set <name> [options]');\n process.exit(1);\n }\n const plugins = listPlugins();\n const existing = plugins.find(p => p.name === name);\n if (!existing) {\n console.error(`Plugin not found: ${name}`);\n process.exit(1);\n }\n const newUrl = options?.url || existing.plugin.url;\n const newDesc = options?.desc !== undefined ? options.desc : existing.plugin.description;\n setPlugin(name, newUrl, newDesc);\n console.log(`✓ Updated plugin: ${name}`);\n break;\n\n case 'list':\n showPluginList();\n break;\n\n default:\n console.error(`Unknown action: ${action}`);\n console.log('');\n console.log('Available actions:');\n console.log(' add <name> <url> - Add a plugin');\n console.log(' rm <name> - Remove a plugin');\n console.log(' default <name> - Set default plugin');\n console.log(' set <name> - Update a plugin');\n console.log(' list - List all plugins');\n process.exit(1);\n }\n });\n\n // Plugins alias for list\n cli\n .command('plugins', 'List all plugins')\n .action(() => {\n showPluginList();\n });\n}\n\nfunction showPluginList(): void {\n const plugins = listPlugins();\n\n if (plugins.length === 0) {\n console.log('No plugins configured.');\n console.log('');\n console.log('Add a plugin:');\n console.log(' trmnl plugin add <name> <url>');\n return;\n }\n\n console.log('Plugins:');\n for (const { name, plugin, isDefault } of plugins) {\n const defaultMark = isDefault ? ' ★' : '';\n console.log(` ${name}${defaultMark}`);\n console.log(` ${plugin.url}`);\n if (plugin.description) {\n console.log(` ${plugin.description}`);\n }\n }\n\n console.log('');\n console.log('★ = default plugin');\n}\n","/**\n * trmnl history - View send history\n */\n\nimport { unlinkSync } from 'node:fs';\nimport type { CAC } from 'cac';\nimport { formatEntry, getHistory, getHistoryPath, getHistoryStats, type HistoryFilter } from '../lib/logger.ts';\n\ninterface HistoryOptions {\n last?: number;\n today?: boolean;\n failed?: boolean;\n success?: boolean;\n plugin?: string;\n json?: boolean;\n verbose?: boolean;\n}\n\nexport function registerHistoryCommand(cli: CAC): void {\n cli\n .command('history', 'View send history')\n .option('-n, --last <n>', 'Show last N entries', { default: 10 })\n .option('--today', 'Show only today\\'s entries')\n .option('--failed', 'Show only failed sends')\n .option('--success', 'Show only successful sends')\n .option('-p, --plugin <name>', 'Filter by plugin name')\n .option('--json', 'Output as JSON')\n .option('-v, --verbose', 'Show content preview')\n .example('trmnl history')\n .example('trmnl history --last 20')\n .example('trmnl history --today --failed')\n .example('trmnl history --plugin office')\n .action((options: HistoryOptions) => {\n const filter: HistoryFilter = {\n last: options.last,\n today: options.today,\n failed: options.failed,\n success: options.success,\n plugin: options.plugin,\n };\n\n const entries = getHistory(filter);\n\n if (options.json) {\n console.log(JSON.stringify(entries, null, 2));\n return;\n }\n\n if (entries.length === 0) {\n console.log('No history entries found.');\n console.log(`History file: ${getHistoryPath()}`);\n return;\n }\n\n // Stats header\n const stats = getHistoryStats();\n if (stats) {\n console.log(`History: ${stats.entries} total entries (${stats.sizeMb} MB)`);\n console.log('');\n }\n\n // Filter description\n const filterParts: string[] = [];\n if (options.today) filterParts.push('today');\n if (options.failed) filterParts.push('failed');\n if (options.success) filterParts.push('success');\n if (options.plugin) filterParts.push(`plugin: ${options.plugin}`);\n if (filterParts.length > 0) {\n console.log(`Filter: ${filterParts.join(', ')}`);\n console.log('');\n }\n\n // Entries\n console.log(`Showing ${entries.length} entries (most recent first):`);\n console.log('');\n\n for (const entry of entries) {\n console.log(formatEntry(entry, options.verbose));\n }\n });\n\n // History clear\n cli\n .command('history clear', 'Clear send history')\n .option('--confirm', 'Confirm deletion')\n .action((options: { confirm?: boolean }) => {\n const historyPath = getHistoryPath();\n \n if (!options.confirm) {\n console.log('This will delete all history. Use --confirm to proceed.');\n console.log(`History file: ${historyPath}`);\n return;\n }\n\n try {\n unlinkSync(historyPath);\n console.log('✓ History cleared');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n console.log('History file does not exist.');\n } else {\n console.error('Error clearing history:', err);\n }\n }\n });\n\n // History stats\n cli\n .command('history stats', 'Show history statistics')\n .action(() => {\n const stats = getHistoryStats();\n \n if (!stats) {\n console.log('No history file found.');\n return;\n }\n\n const entries = getHistory({});\n const successCount = entries.filter(e => e.success).length;\n const failedCount = entries.filter(e => !e.success).length;\n const totalBytes = entries.reduce((sum, e) => sum + e.size_bytes, 0);\n const avgBytes = entries.length > 0 ? Math.round(totalBytes / entries.length) : 0;\n const avgDuration = entries.length > 0 \n ? Math.round(entries.reduce((sum, e) => sum + e.duration_ms, 0) / entries.length) \n : 0;\n\n // Plugin breakdown\n const byPlugin = new Map<string, number>();\n for (const entry of entries) {\n byPlugin.set(entry.plugin, (byPlugin.get(entry.plugin) || 0) + 1);\n }\n\n console.log('History Statistics');\n console.log('');\n console.log(`File: ${getHistoryPath()}`);\n console.log(`Size: ${stats.sizeMb} MB`);\n console.log('');\n console.log(`Total: ${entries.length} sends`);\n console.log(`Success: ${successCount} (${entries.length > 0 ? Math.round(successCount / entries.length * 100) : 0}%)`);\n console.log(`Failed: ${failedCount} (${entries.length > 0 ? Math.round(failedCount / entries.length * 100) : 0}%)`);\n console.log('');\n console.log(`Avg size: ${avgBytes} bytes`);\n console.log(`Avg duration: ${avgDuration}ms`);\n\n if (byPlugin.size > 1) {\n console.log('');\n console.log('By plugin:');\n for (const [plugin, count] of byPlugin.entries()) {\n console.log(` ${plugin}: ${count} sends`);\n }\n }\n\n // Recent activity\n const today = getHistory({ today: true });\n const thisWeek = getHistory({ since: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) });\n console.log('');\n console.log(`Today: ${today.length} sends`);\n console.log(`This week: ${thisWeek.length} sends`);\n });\n}\n","/**\n * trmnl send - Send content to TRMNL display\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { CAC } from 'cac';\nimport { createPayload, formatValidation } from '../lib/validator.ts';\nimport { sendToWebhook } from '../lib/webhook.ts';\n\ninterface SendOptions {\n content?: string;\n file?: string;\n plugin?: string;\n webhook?: string;\n skipValidation?: boolean;\n skipLog?: boolean;\n json?: boolean;\n}\n\nexport function registerSendCommand(cli: CAC): void {\n cli\n .command('send', 'Send content to TRMNL display')\n .option('-c, --content <html>', 'HTML content to send')\n .option('-f, --file <path>', 'Read content from file')\n .option('-p, --plugin <name>', 'Plugin to use (default: default plugin)')\n .option('-w, --webhook <url>', 'Override webhook URL directly')\n .option('--skip-validation', 'Skip payload validation')\n .option('--skip-log', 'Skip history logging')\n .option('--json', 'Output result as JSON')\n .example('trmnl send --content \"<div class=\\\\\"layout\\\\\">Hello</div>\"')\n .example('trmnl send --file ./output.html')\n .example('trmnl send --file ./output.html --plugin office')\n .example('echo \\'{\"merge_variables\":{\"content\":\"...\"}}\\' | trmnl send')\n .action(async (options: SendOptions) => {\n let content: string;\n\n // Get content from options, file, or stdin\n if (options.content) {\n content = options.content;\n } else if (options.file) {\n try {\n content = readFileSync(options.file, 'utf-8');\n } catch (err) {\n console.error(`Error reading file: ${options.file}`);\n process.exit(1);\n }\n } else {\n // Try reading from stdin\n content = await readStdin();\n if (!content) {\n console.error('No content provided. Use --content, --file, or pipe content via stdin.');\n process.exit(1);\n }\n }\n\n // Create payload\n const payload = createPayload(content);\n\n // Send\n const result = await sendToWebhook(payload, {\n plugin: options.plugin,\n webhookUrl: options.webhook,\n skipValidation: options.skipValidation,\n skipLog: options.skipLog,\n });\n\n // Output\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n if (result.success) {\n console.log(`✓ Sent to TRMNL (${result.pluginName})`);\n console.log(` Status: ${result.statusCode}`);\n console.log(` Time: ${result.durationMs}ms`);\n console.log(` Size: ${result.validation.size_bytes} bytes (${result.validation.percent_used}% of limit)`);\n } else {\n console.error('✗ Failed to send');\n console.error(` Error: ${result.error}`);\n if (result.pluginName) {\n console.error(` Plugin: ${result.pluginName}`);\n }\n console.log('');\n console.log('Validation:');\n console.log(formatValidation(result.validation));\n }\n }\n\n process.exit(result.success ? 0 : 1);\n });\n}\n\n/**\n * Read content from stdin (non-blocking check)\n */\nasync function readStdin(): Promise<string> {\n // Check if stdin has data (not a TTY)\n if (process.stdin.isTTY) {\n return '';\n }\n\n const chunks: Buffer[] = [];\n \n return new Promise((resolve) => {\n process.stdin.on('data', (chunk) => chunks.push(chunk));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').trim()));\n process.stdin.on('error', () => resolve(''));\n \n // Timeout to avoid hanging\n setTimeout(() => {\n if (chunks.length === 0) {\n resolve('');\n }\n }, 100);\n });\n}\n","/**\n * Payload validation for TRMNL webhooks\n */\n\nimport { TIER_LIMITS, type ValidationResult, type WebhookPayload, type WebhookTier } from '../types.ts';\n\n/**\n * Validate a webhook payload against size limits\n */\nexport function validatePayload(payload: WebhookPayload, tier: WebhookTier = 'free'): ValidationResult {\n const jsonString = JSON.stringify(payload);\n const sizeBytes = new TextEncoder().encode(jsonString).length;\n const limitBytes = TIER_LIMITS[tier];\n const remainingBytes = limitBytes - sizeBytes;\n const percentUsed = Math.round((sizeBytes / limitBytes) * 1000) / 10;\n\n const warnings: string[] = [];\n const errors: string[] = [];\n\n // Size check\n if (sizeBytes > limitBytes) {\n errors.push(`Payload exceeds ${tier} tier limit: ${sizeBytes} bytes > ${limitBytes} bytes`);\n } else if (percentUsed > 90) {\n warnings.push(`Payload is at ${percentUsed}% of ${tier} tier limit`);\n }\n\n // Content check\n if (!payload.merge_variables) {\n errors.push('Missing merge_variables object');\n } else if (!payload.merge_variables.content && !payload.merge_variables.text) {\n warnings.push('No content or text field in merge_variables');\n }\n\n // HTML sanity checks\n const content = payload.merge_variables?.content || '';\n if (content) {\n // Check for unclosed tags (basic check)\n const openDivs = (content.match(/<div/g) || []).length;\n const closeDivs = (content.match(/<\\/div>/g) || []).length;\n if (openDivs !== closeDivs) {\n warnings.push(`Potential unclosed divs: ${openDivs} open, ${closeDivs} close`);\n }\n\n // Check for common TRMNL patterns - look for 'layout' as a class (may have other classes too)\n const hasLayoutClass = /class=[\"'][^\"']*\\blayout\\b[^\"']*[\"']/.test(content);\n if (!hasLayoutClass) {\n warnings.push('Missing .layout class - TRMNL requires a root layout element');\n }\n }\n\n return {\n valid: errors.length === 0,\n size_bytes: sizeBytes,\n tier,\n limit_bytes: limitBytes,\n remaining_bytes: remainingBytes,\n percent_used: percentUsed,\n warnings,\n errors,\n };\n}\n\n/**\n * Parse content into a webhook payload\n */\nexport function createPayload(content: string): WebhookPayload {\n // Try to parse as JSON first\n try {\n const parsed = JSON.parse(content);\n if (parsed.merge_variables) {\n return parsed as WebhookPayload;\n }\n // If it's just merge_variables content\n return { merge_variables: parsed };\n } catch {\n // Treat as raw HTML content\n return {\n merge_variables: {\n content: content,\n },\n };\n }\n}\n\n/**\n * Format validation result for display\n */\nexport function formatValidation(result: ValidationResult): string {\n const lines: string[] = [];\n \n const status = result.valid ? '✓' : '✗';\n const sizeKb = (result.size_bytes / 1024).toFixed(2);\n const limitKb = (result.limit_bytes / 1024).toFixed(2);\n \n lines.push(`${status} Payload: ${result.size_bytes} bytes (${sizeKb} KB)`);\n lines.push(` Tier: ${result.tier} (limit: ${limitKb} KB)`);\n lines.push(` Used: ${result.percent_used}% (${result.remaining_bytes} bytes remaining)`);\n \n if (result.errors.length > 0) {\n lines.push('');\n lines.push('Errors:');\n for (const error of result.errors) {\n lines.push(` ✗ ${error}`);\n }\n }\n \n if (result.warnings.length > 0) {\n lines.push('');\n lines.push('Warnings:');\n for (const warning of result.warnings) {\n lines.push(` ⚠ ${warning}`);\n }\n }\n \n return lines.join('\\n');\n}\n","/**\n * Webhook sending logic\n */\n\nimport { getTier, getWebhookUrl } from './config.ts';\nimport { logEntry } from './logger.ts';\nimport { validatePayload } from './validator.ts';\nimport type { HistoryEntry, WebhookPayload, WebhookTier } from '../types.ts';\n\nexport interface SendResult {\n success: boolean;\n pluginName: string;\n statusCode?: number;\n response?: string;\n error?: string;\n durationMs: number;\n validation: ReturnType<typeof validatePayload>;\n}\n\nexport interface SendOptions {\n plugin?: string; // Plugin name to use\n webhookUrl?: string; // Direct URL override\n skipValidation?: boolean;\n skipLog?: boolean;\n}\n\n/**\n * Send payload to TRMNL webhook\n */\nexport async function sendToWebhook(\n payload: WebhookPayload,\n options: SendOptions = {}\n): Promise<SendResult> {\n const startTime = Date.now();\n const tier = getTier();\n \n // Resolve webhook URL\n let webhookUrl: string;\n let pluginName: string;\n\n if (options.webhookUrl) {\n // Direct URL override\n webhookUrl = options.webhookUrl;\n pluginName = 'custom';\n } else {\n // Get from config/env\n const resolved = getWebhookUrl(options.plugin);\n if (!resolved) {\n const durationMs = Date.now() - startTime;\n const validation = validatePayload(payload, tier);\n \n let error = 'No webhook URL configured.';\n if (options.plugin) {\n error = `Plugin \"${options.plugin}\" not found.`;\n } else {\n error += ' Add a plugin with: trmnl plugin add <name> <url>';\n }\n \n return {\n success: false,\n pluginName: options.plugin || 'unknown',\n error,\n durationMs,\n validation,\n };\n }\n webhookUrl = resolved.url;\n pluginName = resolved.name;\n }\n\n // Validate payload\n const validation = validatePayload(payload, tier);\n \n if (!options.skipValidation && !validation.valid) {\n const durationMs = Date.now() - startTime;\n const result: SendResult = {\n success: false,\n pluginName,\n error: validation.errors.join('; '),\n durationMs,\n validation,\n };\n \n // Log failed validation\n if (!options.skipLog) {\n logEntry(createHistoryEntry(payload, result, tier, pluginName));\n }\n \n return result;\n }\n \n // Send request\n try {\n const response = await fetch(webhookUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n \n const durationMs = Date.now() - startTime;\n const responseText = await response.text();\n \n const result: SendResult = {\n success: response.ok,\n pluginName,\n statusCode: response.status,\n response: responseText,\n durationMs,\n validation,\n };\n \n if (!response.ok) {\n result.error = `HTTP ${response.status}: ${responseText}`;\n }\n \n // Log the send\n if (!options.skipLog) {\n logEntry(createHistoryEntry(payload, result, tier, pluginName));\n }\n \n return result;\n \n } catch (err) {\n const durationMs = Date.now() - startTime;\n const error = err instanceof Error ? err.message : 'Unknown error';\n \n const result: SendResult = {\n success: false,\n pluginName,\n error,\n durationMs,\n validation,\n };\n \n // Log the error\n if (!options.skipLog) {\n logEntry(createHistoryEntry(payload, result, tier, pluginName));\n }\n \n return result;\n }\n}\n\n/**\n * Create a history entry from send result\n */\nfunction createHistoryEntry(\n payload: WebhookPayload,\n result: SendResult,\n tier: WebhookTier,\n pluginName: string\n): HistoryEntry {\n return {\n timestamp: new Date().toISOString(),\n plugin: pluginName,\n size_bytes: result.validation.size_bytes,\n tier,\n payload,\n success: result.success,\n status_code: result.statusCode,\n response: result.response,\n error: result.error,\n duration_ms: result.durationMs,\n };\n}\n","/**\n * trmnl validate - Validate payload without sending\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { CAC } from 'cac';\nimport { getTier } from '../lib/config.ts';\nimport { createPayload, formatValidation, validatePayload } from '../lib/validator.ts';\nimport type { WebhookTier } from '../types.ts';\n\ninterface ValidateOptions {\n content?: string;\n file?: string;\n tier?: WebhookTier;\n json?: boolean;\n}\n\nexport function registerValidateCommand(cli: CAC): void {\n cli\n .command('validate', 'Validate payload without sending')\n .option('-c, --content <html>', 'HTML content to validate')\n .option('-f, --file <path>', 'Read content from file')\n .option('-t, --tier <tier>', 'Override tier (free or plus)')\n .option('--json', 'Output result as JSON')\n .example('trmnl validate --file ./output.html')\n .example('trmnl validate --content \"<div>...</div>\" --tier plus')\n .action(async (options: ValidateOptions) => {\n let content: string;\n\n // Get content from options, file, or stdin\n if (options.content) {\n content = options.content;\n } else if (options.file) {\n try {\n content = readFileSync(options.file, 'utf-8');\n } catch (err) {\n console.error(`Error reading file: ${options.file}`);\n process.exit(1);\n }\n } else {\n // Try reading from stdin\n content = await readStdin();\n if (!content) {\n console.error('No content provided. Use --content, --file, or pipe content via stdin.');\n process.exit(1);\n }\n }\n\n // Use explicit tier or global config\n const tier = options.tier || getTier();\n\n // Create payload and validate\n const payload = createPayload(content);\n const result = validatePayload(payload, tier);\n\n // Output\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(formatValidation(result));\n }\n\n process.exit(result.valid ? 0 : 1);\n });\n}\n\n/**\n * Read content from stdin (non-blocking check)\n */\nasync function readStdin(): Promise<string> {\n if (process.stdin.isTTY) {\n return '';\n }\n\n const chunks: Buffer[] = [];\n \n return new Promise((resolve) => {\n process.stdin.on('data', (chunk) => chunks.push(chunk));\n process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').trim()));\n process.stdin.on('error', () => resolve(''));\n \n setTimeout(() => {\n if (chunks.length === 0) {\n resolve('');\n }\n }, 100);\n });\n}\n"],"mappings":";AAUA,SAAS,qBAAqB;AAC9B,OAAO,SAAS;;;ACNhB,SAAS,YAAY,WAAW,cAAc,eAAe,kBAAkB;AAC/E,SAAS,eAAe;AACxB,SAAS,YAAY;;;ACCd,IAAM,cAA2C;AAAA,EACtD,MAAM;AAAA;AAAA,EACN,MAAM;AAAA;AACR;AAoBO,IAAM,iBAAyB;AAAA,EACpC,SAAS,CAAC;AAAA,EACV,eAAe;AAAA,EACf,MAAM;AAAA,EACN,SAAS;AAAA,IACP,MAAM;AAAA,IACN,WAAW;AAAA,EACb;AACF;;;AD3BO,IAAM,aAAa,KAAK,QAAQ,GAAG,QAAQ;AAG3C,IAAM,cAAc,KAAK,YAAY,aAAa;AAGzD,IAAM,qBAAqB,KAAK,YAAY,aAAa;AAGlD,IAAM,eAAe,KAAK,YAAY,eAAe;AAKrD,SAAS,kBAAwB;AACtC,MAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,cAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AACF;AAKA,SAAS,sBAAqC;AAC5C,MAAI,CAAC,WAAW,kBAAkB,GAAG;AACnC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,oBAAoB,OAAO;AACxD,UAAM,SAAiB,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO;AAGnD,QAAI;AAEJ,eAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,YAAM,UAAU,KAAK,KAAK;AAC1B,YAAM,WAAW,QAAQ,MAAM,sBAAsB;AACrD,UAAI,SAAU,cAAa,SAAS,CAAC;AAAA,IACvC;AAEA,QAAI,YAAY;AACd,aAAO,QAAQ,SAAS,IAAI,EAAE,KAAK,WAAW;AAC9C,aAAO,gBAAgB;AAAA,IACzB;AAGA,eAAW,kBAAkB;AAC7B,YAAQ,IAAI,4CAA4C;AAExD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,aAAqB;AACnC,kBAAgB;AAGhB,QAAM,WAAW,oBAAoB;AACrC,MAAI,UAAU;AACZ,eAAW,QAAQ;AACnB,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,aAAa,OAAO;AACjD,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO;AAAA,MACL,SAAS,OAAO,WAAW,CAAC;AAAA,MAC5B,eAAe,OAAO;AAAA,MACtB,MAAM,OAAO,QAAQ;AAAA,MACrB,SAAS,OAAO,WAAW,eAAe;AAAA,IAC5C;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,GAAG,eAAe;AAAA,EAC7B;AACF;AAKO,SAAS,WAAW,QAAsB;AAC/C,kBAAgB;AAChB,QAAM,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC;AAC9C,gBAAc,aAAa,SAAS,OAAO;AAC7C;AAKO,SAAS,UAAU,MAAwD;AAChF,QAAM,SAAS,WAAW;AAG1B,MAAI,MAAM;AACR,UAAM,SAAS,OAAO,QAAQ,IAAI;AAClC,QAAI,QAAQ;AACV,aAAO,EAAE,MAAM,OAAO;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAGA,QAAM,cAAc,OAAO;AAC3B,MAAI,eAAe,OAAO,QAAQ,WAAW,GAAG;AAC9C,WAAO,EAAE,MAAM,aAAa,QAAQ,OAAO,QAAQ,WAAW,EAAE;AAAA,EAClE;AAGA,QAAM,cAAc,OAAO,KAAK,OAAO,OAAO;AAC9C,MAAI,YAAY,WAAW,GAAG;AAC5B,UAAMA,QAAO,YAAY,CAAC;AAC1B,WAAO,EAAE,MAAAA,OAAM,QAAQ,OAAO,QAAQA,KAAI,EAAE;AAAA,EAC9C;AAEA,SAAO;AACT;AAKO,SAAS,UAAU,MAAc,KAAa,aAA4B;AAC/E,QAAM,SAAS,WAAW;AAC1B,SAAO,QAAQ,IAAI,IAAI,EAAE,KAAK,YAAY;AAG1C,MAAI,CAAC,OAAO,iBAAiB,OAAO,KAAK,OAAO,OAAO,EAAE,WAAW,GAAG;AACrE,WAAO,gBAAgB;AAAA,EACzB;AAEA,aAAW,MAAM;AACnB;AAKO,SAAS,aAAa,MAAuB;AAClD,QAAM,SAAS,WAAW;AAE1B,MAAI,CAAC,OAAO,QAAQ,IAAI,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,QAAQ,IAAI;AAG1B,MAAI,OAAO,kBAAkB,MAAM;AACjC,UAAM,YAAY,OAAO,KAAK,OAAO,OAAO;AAC5C,WAAO,gBAAgB,UAAU,SAAS,IAAI,UAAU,CAAC,IAAI;AAAA,EAC/D;AAEA,aAAW,MAAM;AACjB,SAAO;AACT;AAKO,SAAS,iBAAiB,MAAuB;AACtD,QAAM,SAAS,WAAW;AAE1B,MAAI,CAAC,OAAO,QAAQ,IAAI,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO,gBAAgB;AACvB,aAAW,MAAM;AACjB,SAAO;AACT;AAKO,SAAS,cAA2E;AACzF,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO,QAAQ,OAAO,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,MAAM,OAAO;AAAA,IAC7D;AAAA,IACA;AAAA,IACA,WAAW,SAAS,OAAO;AAAA,EAC7B,EAAE;AACJ;AAKO,SAAS,UAAuB;AACrC,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO,QAAQ;AACxB;AAKO,SAAS,QAAQ,MAAyB;AAC/C,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO;AACd,aAAW,MAAM;AACnB;AAKO,SAAS,cAAc,YAA2D;AAEvF,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,QAAQ;AACV,WAAO,EAAE,KAAK,QAAQ,MAAM,iBAAiB;AAAA,EAC/C;AAGA,QAAM,SAAS,UAAU,UAAU;AACnC,MAAI,QAAQ;AACV,WAAO;AAAA,MACL,KAAK,OAAO,OAAO;AAAA,MACnB,MAAM,OAAO;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,mBAAwD;AACtE,QAAM,SAAS,WAAW;AAC1B,QAAM,cAAc,OAAO,SAAS,QAAQ,eAAe,QAAS;AACpE,QAAM,eAAe,YAAY,WAAW,GAAG,IAC3C,YAAY,QAAQ,KAAK,QAAQ,CAAC,IAClC;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,WAAW,OAAO,SAAS,aAAa,eAAe,QAAS;AAAA,EAClE;AACF;;;AE5PA,SAAS,gBAAgB,cAAAC,aAAY,gBAAAC,eAAc,gBAAgB;AAO5D,SAAS,SAAS,OAA2B;AAClD,kBAAgB;AAChB,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAClC,QAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,iBAAe,MAAM,MAAM,OAAO;AACpC;AAKO,SAAS,cAA8B;AAC5C,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAElC,MAAI,CAACC,YAAW,IAAI,GAAG;AACrB,WAAO,CAAC;AAAA,EACV;AAEA,MAAI;AACF,UAAM,UAAUC,cAAa,MAAM,OAAO;AAC1C,UAAM,QAAQ,QAAQ,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AACvD,WAAO,MAAM,IAAI,UAAQ,KAAK,MAAM,IAAI,CAAiB;AAAA,EAC3D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAeO,SAAS,WAAW,SAAwB,CAAC,GAAmB;AACrE,MAAI,UAAU,YAAY;AAG1B,MAAI,OAAO,QAAQ;AACjB,cAAU,QAAQ,OAAO,OAAK,EAAE,WAAW,OAAO,MAAM;AAAA,EAC1D;AAGA,MAAI,OAAO,QAAQ;AACjB,cAAU,QAAQ,OAAO,OAAK,CAAC,EAAE,OAAO;AAAA,EAC1C;AACA,MAAI,OAAO,SAAS;AAClB,cAAU,QAAQ,OAAO,OAAK,EAAE,OAAO;AAAA,EACzC;AAGA,MAAI,OAAO,OAAO;AAChB,UAAM,QAAQ,oBAAI,KAAK;AACvB,UAAM,SAAS,GAAG,GAAG,GAAG,CAAC;AACzB,cAAU,QAAQ,OAAO,OAAK,IAAI,KAAK,EAAE,SAAS,KAAK,KAAK;AAAA,EAC9D;AACA,MAAI,OAAO,OAAO;AAChB,cAAU,QAAQ,OAAO,OAAK,IAAI,KAAK,EAAE,SAAS,KAAK,OAAO,KAAM;AAAA,EACtE;AACA,MAAI,OAAO,OAAO;AAChB,cAAU,QAAQ,OAAO,OAAK,IAAI,KAAK,EAAE,SAAS,KAAK,OAAO,KAAM;AAAA,EACtE;AAGA,UAAQ,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC;AAGxF,MAAI,OAAO,MAAM;AACf,cAAU,QAAQ,MAAM,GAAG,OAAO,IAAI;AAAA,EACxC;AAEA,SAAO;AACT;AAKO,SAAS,YAAY,OAAqB,UAAU,OAAe;AACxE,QAAM,OAAO,IAAI,KAAK,MAAM,SAAS,EAAE,eAAe;AACtD,QAAM,SAAS,MAAM,UAAU,WAAM;AACrC,QAAM,UAAU,MAAM,aAAa,MAAM,QAAQ,CAAC;AAElD,MAAI,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,MAAM,MAAM,MAAM,MAAM,SAAS,MAAM,WAAW;AAEpF,MAAI,CAAC,MAAM,WAAW,MAAM,OAAO;AACjC,YAAQ,MAAM,MAAM,KAAK;AAAA,EAC3B;AAEA,MAAI,WAAW,MAAM,SAAS,iBAAiB,SAAS;AACtD,UAAM,UAAU,MAAM,QAAQ,gBAAgB,QAAQ,UAAU,GAAG,EAAE;AACrE,YAAQ;AAAA,KAAQ,OAAO,GAAG,MAAM,QAAQ,gBAAgB,QAAQ,SAAS,KAAK,QAAQ,EAAE;AAAA,EAC1F;AAEA,SAAO;AACT;AAKO,SAAS,kBAAiF;AAC/F,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAElC,MAAI,CAACD,YAAW,IAAI,GAAG;AACrB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,QAAQ,SAAS,IAAI;AAC3B,UAAM,UAAU,YAAY,EAAE;AAC9B,WAAO;AAAA,MACL;AAAA,MACA,WAAW,MAAM;AAAA,MACjB,QAAQ,KAAK,MAAO,MAAM,OAAO,OAAO,OAAQ,GAAG,IAAI;AAAA,IACzD;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,iBAAyB;AACvC,SAAO,iBAAiB,EAAE;AAC5B;;;ACzHO,SAAS,sBAAsBE,MAAgB;AAEpD,EAAAA,KACG,QAAQ,UAAU,oBAAoB,EACtC,OAAO,MAAM;AACZ,UAAM,SAAS,WAAW;AAC1B,UAAM,UAAU,YAAY;AAE5B,YAAQ,IAAI,gBAAgB,WAAW,EAAE;AACzC,YAAQ,IAAI,EAAE;AAEd,YAAQ,IAAI,UAAU;AACtB,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAI,qBAAqB;AACjC,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,iBAAiB;AAC7B,cAAQ,IAAI,mCAAmC;AAAA,IACjD,OAAO;AACL,iBAAW,EAAE,MAAM,QAAQ,UAAU,KAAK,SAAS;AACjD,cAAM,cAAc,YAAY,eAAe;AAC/C,gBAAQ,IAAI,KAAK,IAAI,GAAG,WAAW,EAAE;AACrC,gBAAQ,IAAI,YAAY,OAAO,GAAG,EAAE;AACpC,YAAI,OAAO,aAAa;AACtB,kBAAQ,IAAI,aAAa,OAAO,WAAW,EAAE;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,SAAS,OAAO,QAAQ,MAAM,EAAE;AAC5C,YAAQ,IAAI,YAAY,OAAO,SAAS,SAAS,SAAS,MAAM,EAAE;AAElE,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,UAAU;AACtB,YAAQ,IAAI,WAAW,eAAe,CAAC,EAAE;AAEzC,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,cAAc;AAC1B,YAAQ,IAAI,oBAAoB,QAAQ,IAAI,iBAAiB,WAAW,EAAE;AAAA,EAC5E,CAAC;AAGH,EAAAA,KACG,QAAQ,gBAAgB,gCAAgC,EACxD,QAAQ,wCAAwC,EAChD,QAAQ,kCAAkC,EAC1C,QAAQ,kCAAkC,EAC1C,OAAO,CAAC,UAAmB;AAC1B,QAAI,CAAC,OAAO;AACV,YAAM,OAAO,QAAQ;AACrB,cAAQ,IAAI,SAAS,IAAI,EAAE;AAC3B,cAAQ,IAAI,UAAU,SAAS,SAAS,uBAAuB,oBAAoB,EAAE;AACrF;AAAA,IACF;AAEA,QAAI,UAAU,UAAU,UAAU,QAAQ;AACxC,cAAQ,MAAM,qCAAqC;AACnD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,YAAQ,KAAoB;AAC5B,YAAQ,IAAI,uBAAkB,KAAK,EAAE;AAAA,EACvC,CAAC;AAGH,EAAAA,KACG,QAAQ,gCAAgC,wBAAwB,EAChE,OAAO,4BAA4B,oBAAoB,EACvD,OAAO,mBAAmB,8BAA8B,EACxD,OAAO,aAAa,uBAAuB,EAC3C,QAAQ,iDAAiD,EACzD,QAAQ,+CAA+C,EACvD,QAAQ,kDAAkD,EAC1D,QAAQ,gDAAgD,EACxD,QAAQ,kDAAkD,EAC1D,OAAO,CAAC,QAAiB,MAAe,KAAc,YAAiE;AAEtH,QAAI,CAAC,QAAQ;AACX,qBAAe;AACf;AAAA,IACF;AAGA,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,YAAI,CAAC,QAAQ,CAAC,KAAK;AACjB,kBAAQ,MAAM,sCAAsC;AACpD,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,kBAAU,MAAM,KAAK,SAAS,IAAI;AAClC,gBAAQ,IAAI,wBAAmB,IAAI,EAAE;AACrC,YAAI,SAAS,SAAS;AACpB,2BAAiB,IAAI;AACrB,kBAAQ,IAAI,uBAAkB;AAAA,QAChC;AACA;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AACH,YAAI,CAAC,MAAM;AACT,kBAAQ,MAAM,+BAA+B;AAC7C,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,YAAI,aAAa,IAAI,GAAG;AACtB,kBAAQ,IAAI,0BAAqB,IAAI,EAAE;AAAA,QACzC,OAAO;AACL,kBAAQ,MAAM,qBAAqB,IAAI,EAAE;AACzC,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA;AAAA,MAEF,KAAK;AACH,YAAI,CAAC,MAAM;AACT,kBAAQ,MAAM,oCAAoC;AAClD,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,YAAI,iBAAiB,IAAI,GAAG;AAC1B,kBAAQ,IAAI,0BAAqB,IAAI,EAAE;AAAA,QACzC,OAAO;AACL,kBAAQ,MAAM,qBAAqB,IAAI,EAAE;AACzC,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA;AAAA,MAEF,KAAK;AAAA,MACL,KAAK;AACH,YAAI,CAAC,MAAM;AACT,kBAAQ,MAAM,0CAA0C;AACxD,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM,UAAU,YAAY;AAC5B,cAAM,WAAW,QAAQ,KAAK,OAAK,EAAE,SAAS,IAAI;AAClD,YAAI,CAAC,UAAU;AACb,kBAAQ,MAAM,qBAAqB,IAAI,EAAE;AACzC,kBAAQ,KAAK,CAAC;AAAA,QAChB;AACA,cAAM,SAAS,SAAS,OAAO,SAAS,OAAO;AAC/C,cAAM,UAAU,SAAS,SAAS,SAAY,QAAQ,OAAO,SAAS,OAAO;AAC7E,kBAAU,MAAM,QAAQ,OAAO;AAC/B,gBAAQ,IAAI,0BAAqB,IAAI,EAAE;AACvC;AAAA,MAEF,KAAK;AACH,uBAAe;AACf;AAAA,MAEF;AACE,gBAAQ,MAAM,mBAAmB,MAAM,EAAE;AACzC,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,oBAAoB;AAChC,gBAAQ,IAAI,oCAAoC;AAChD,gBAAQ,IAAI,uCAAuC;AACnD,gBAAQ,IAAI,0CAA0C;AACtD,gBAAQ,IAAI,uCAAuC;AACnD,gBAAQ,IAAI,wCAAwC;AACpD,gBAAQ,KAAK,CAAC;AAAA,IAClB;AAAA,EACF,CAAC;AAGH,EAAAA,KACG,QAAQ,WAAW,kBAAkB,EACrC,OAAO,MAAM;AACZ,mBAAe;AAAA,EACjB,CAAC;AACL;AAEA,SAAS,iBAAuB;AAC9B,QAAM,UAAU,YAAY;AAE5B,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,wBAAwB;AACpC,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,eAAe;AAC3B,YAAQ,IAAI,iCAAiC;AAC7C;AAAA,EACF;AAEA,UAAQ,IAAI,UAAU;AACtB,aAAW,EAAE,MAAM,QAAQ,UAAU,KAAK,SAAS;AACjD,UAAM,cAAc,YAAY,YAAO;AACvC,YAAQ,IAAI,KAAK,IAAI,GAAG,WAAW,EAAE;AACrC,YAAQ,IAAI,OAAO,OAAO,GAAG,EAAE;AAC/B,QAAI,OAAO,aAAa;AACtB,cAAQ,IAAI,OAAO,OAAO,WAAW,EAAE;AAAA,IACzC;AAAA,EACF;AAEA,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,yBAAoB;AAClC;;;AC7MA,SAAS,cAAAC,mBAAkB;AAcpB,SAAS,uBAAuBC,MAAgB;AACrD,EAAAA,KACG,QAAQ,WAAW,mBAAmB,EACtC,OAAO,kBAAkB,uBAAuB,EAAE,SAAS,GAAG,CAAC,EAC/D,OAAO,WAAW,2BAA4B,EAC9C,OAAO,YAAY,wBAAwB,EAC3C,OAAO,aAAa,4BAA4B,EAChD,OAAO,uBAAuB,uBAAuB,EACrD,OAAO,UAAU,gBAAgB,EACjC,OAAO,iBAAiB,sBAAsB,EAC9C,QAAQ,eAAe,EACvB,QAAQ,yBAAyB,EACjC,QAAQ,gCAAgC,EACxC,QAAQ,+BAA+B,EACvC,OAAO,CAAC,YAA4B;AACnC,UAAM,SAAwB;AAAA,MAC5B,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ;AAAA,IAClB;AAEA,UAAM,UAAU,WAAW,MAAM;AAEjC,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC5C;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,GAAG;AACxB,cAAQ,IAAI,2BAA2B;AACvC,cAAQ,IAAI,iBAAiB,eAAe,CAAC,EAAE;AAC/C;AAAA,IACF;AAGA,UAAM,QAAQ,gBAAgB;AAC9B,QAAI,OAAO;AACT,cAAQ,IAAI,YAAY,MAAM,OAAO,mBAAmB,MAAM,MAAM,MAAM;AAC1E,cAAQ,IAAI,EAAE;AAAA,IAChB;AAGA,UAAM,cAAwB,CAAC;AAC/B,QAAI,QAAQ,MAAO,aAAY,KAAK,OAAO;AAC3C,QAAI,QAAQ,OAAQ,aAAY,KAAK,QAAQ;AAC7C,QAAI,QAAQ,QAAS,aAAY,KAAK,SAAS;AAC/C,QAAI,QAAQ,OAAQ,aAAY,KAAK,WAAW,QAAQ,MAAM,EAAE;AAChE,QAAI,YAAY,SAAS,GAAG;AAC1B,cAAQ,IAAI,WAAW,YAAY,KAAK,IAAI,CAAC,EAAE;AAC/C,cAAQ,IAAI,EAAE;AAAA,IAChB;AAGA,YAAQ,IAAI,WAAW,QAAQ,MAAM,+BAA+B;AACpE,YAAQ,IAAI,EAAE;AAEd,eAAW,SAAS,SAAS;AAC3B,cAAQ,IAAI,YAAY,OAAO,QAAQ,OAAO,CAAC;AAAA,IACjD;AAAA,EACF,CAAC;AAGH,EAAAA,KACG,QAAQ,iBAAiB,oBAAoB,EAC7C,OAAO,aAAa,kBAAkB,EACtC,OAAO,CAAC,YAAmC;AAC1C,UAAM,cAAc,eAAe;AAEnC,QAAI,CAAC,QAAQ,SAAS;AACpB,cAAQ,IAAI,yDAAyD;AACrE,cAAQ,IAAI,iBAAiB,WAAW,EAAE;AAC1C;AAAA,IACF;AAEA,QAAI;AACF,MAAAC,YAAW,WAAW;AACtB,cAAQ,IAAI,wBAAmB;AAAA,IACjC,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,UAAU;AACpD,gBAAQ,IAAI,8BAA8B;AAAA,MAC5C,OAAO;AACL,gBAAQ,MAAM,2BAA2B,GAAG;AAAA,MAC9C;AAAA,IACF;AAAA,EACF,CAAC;AAGH,EAAAD,KACG,QAAQ,iBAAiB,yBAAyB,EAClD,OAAO,MAAM;AACZ,UAAM,QAAQ,gBAAgB;AAE9B,QAAI,CAAC,OAAO;AACV,cAAQ,IAAI,wBAAwB;AACpC;AAAA,IACF;AAEA,UAAM,UAAU,WAAW,CAAC,CAAC;AAC7B,UAAM,eAAe,QAAQ,OAAO,OAAK,EAAE,OAAO,EAAE;AACpD,UAAM,cAAc,QAAQ,OAAO,OAAK,CAAC,EAAE,OAAO,EAAE;AACpD,UAAM,aAAa,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;AACnE,UAAM,WAAW,QAAQ,SAAS,IAAI,KAAK,MAAM,aAAa,QAAQ,MAAM,IAAI;AAChF,UAAM,cAAc,QAAQ,SAAS,IACjC,KAAK,MAAM,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,aAAa,CAAC,IAAI,QAAQ,MAAM,IAC9E;AAGJ,UAAM,WAAW,oBAAI,IAAoB;AACzC,eAAW,SAAS,SAAS;AAC3B,eAAS,IAAI,MAAM,SAAS,SAAS,IAAI,MAAM,MAAM,KAAK,KAAK,CAAC;AAAA,IAClE;AAEA,YAAQ,IAAI,oBAAoB;AAChC,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,aAAa,eAAe,CAAC,EAAE;AAC3C,YAAQ,IAAI,aAAa,MAAM,MAAM,KAAK;AAC1C,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,aAAa,QAAQ,MAAM,QAAQ;AAC/C,YAAQ,IAAI,aAAa,YAAY,KAAK,QAAQ,SAAS,IAAI,KAAK,MAAM,eAAe,QAAQ,SAAS,GAAG,IAAI,CAAC,IAAI;AACtH,YAAQ,IAAI,aAAa,WAAW,KAAK,QAAQ,SAAS,IAAI,KAAK,MAAM,cAAc,QAAQ,SAAS,GAAG,IAAI,CAAC,IAAI;AACpH,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,iBAAiB,QAAQ,QAAQ;AAC7C,YAAQ,IAAI,iBAAiB,WAAW,IAAI;AAE5C,QAAI,SAAS,OAAO,GAAG;AACrB,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,YAAY;AACxB,iBAAW,CAAC,QAAQ,KAAK,KAAK,SAAS,QAAQ,GAAG;AAChD,gBAAQ,IAAI,KAAK,MAAM,KAAK,KAAK,QAAQ;AAAA,MAC3C;AAAA,IACF;AAGA,UAAM,QAAQ,WAAW,EAAE,OAAO,KAAK,CAAC;AACxC,UAAM,WAAW,WAAW,EAAE,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,GAAI,EAAE,CAAC;AACrF,YAAQ,IAAI,EAAE;AACd,YAAQ,IAAI,cAAc,MAAM,MAAM,QAAQ;AAC9C,YAAQ,IAAI,cAAc,SAAS,MAAM,QAAQ;AAAA,EACnD,CAAC;AACL;;;AC3JA,SAAS,gBAAAE,qBAAoB;;;ACKtB,SAAS,gBAAgB,SAAyB,OAAoB,QAA0B;AACrG,QAAM,aAAa,KAAK,UAAU,OAAO;AACzC,QAAM,YAAY,IAAI,YAAY,EAAE,OAAO,UAAU,EAAE;AACvD,QAAM,aAAa,YAAY,IAAI;AACnC,QAAM,iBAAiB,aAAa;AACpC,QAAM,cAAc,KAAK,MAAO,YAAY,aAAc,GAAI,IAAI;AAElE,QAAM,WAAqB,CAAC;AAC5B,QAAM,SAAmB,CAAC;AAG1B,MAAI,YAAY,YAAY;AAC1B,WAAO,KAAK,mBAAmB,IAAI,gBAAgB,SAAS,YAAY,UAAU,QAAQ;AAAA,EAC5F,WAAW,cAAc,IAAI;AAC3B,aAAS,KAAK,iBAAiB,WAAW,QAAQ,IAAI,aAAa;AAAA,EACrE;AAGA,MAAI,CAAC,QAAQ,iBAAiB;AAC5B,WAAO,KAAK,gCAAgC;AAAA,EAC9C,WAAW,CAAC,QAAQ,gBAAgB,WAAW,CAAC,QAAQ,gBAAgB,MAAM;AAC5E,aAAS,KAAK,6CAA6C;AAAA,EAC7D;AAGA,QAAM,UAAU,QAAQ,iBAAiB,WAAW;AACpD,MAAI,SAAS;AAEX,UAAM,YAAY,QAAQ,MAAM,OAAO,KAAK,CAAC,GAAG;AAChD,UAAM,aAAa,QAAQ,MAAM,UAAU,KAAK,CAAC,GAAG;AACpD,QAAI,aAAa,WAAW;AAC1B,eAAS,KAAK,4BAA4B,QAAQ,UAAU,SAAS,QAAQ;AAAA,IAC/E;AAGA,UAAM,iBAAiB,uCAAuC,KAAK,OAAO;AAC1E,QAAI,CAAC,gBAAgB;AACnB,eAAS,KAAK,8DAA8D;AAAA,IAC9E;AAAA,EACF;AAEA,SAAO;AAAA,IACL,OAAO,OAAO,WAAW;AAAA,IACzB,YAAY;AAAA,IACZ;AAAA,IACA,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACF;AAKO,SAAS,cAAc,SAAiC;AAE7D,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,QAAI,OAAO,iBAAiB;AAC1B,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,iBAAiB,OAAO;AAAA,EACnC,QAAQ;AAEN,WAAO;AAAA,MACL,iBAAiB;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAKO,SAAS,iBAAiB,QAAkC;AACjE,QAAM,QAAkB,CAAC;AAEzB,QAAM,SAAS,OAAO,QAAQ,WAAM;AACpC,QAAM,UAAU,OAAO,aAAa,MAAM,QAAQ,CAAC;AACnD,QAAM,WAAW,OAAO,cAAc,MAAM,QAAQ,CAAC;AAErD,QAAM,KAAK,GAAG,MAAM,aAAa,OAAO,UAAU,WAAW,MAAM,MAAM;AACzE,QAAM,KAAK,WAAW,OAAO,IAAI,YAAY,OAAO,MAAM;AAC1D,QAAM,KAAK,WAAW,OAAO,YAAY,MAAM,OAAO,eAAe,mBAAmB;AAExF,MAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,SAAS;AACpB,eAAW,SAAS,OAAO,QAAQ;AACjC,YAAM,KAAK,YAAO,KAAK,EAAE;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,WAAW;AACtB,eAAW,WAAW,OAAO,UAAU;AACrC,YAAM,KAAK,YAAO,OAAO,EAAE;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACtFA,eAAsB,cACpB,SACA,UAAuB,CAAC,GACH;AACrB,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,OAAO,QAAQ;AAGrB,MAAI;AACJ,MAAI;AAEJ,MAAI,QAAQ,YAAY;AAEtB,iBAAa,QAAQ;AACrB,iBAAa;AAAA,EACf,OAAO;AAEL,UAAM,WAAW,cAAc,QAAQ,MAAM;AAC7C,QAAI,CAAC,UAAU;AACb,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,YAAMC,cAAa,gBAAgB,SAAS,IAAI;AAEhD,UAAI,QAAQ;AACZ,UAAI,QAAQ,QAAQ;AAClB,gBAAQ,WAAW,QAAQ,MAAM;AAAA,MACnC,OAAO;AACL,iBAAS;AAAA,MACX;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY,QAAQ,UAAU;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,YAAAA;AAAA,MACF;AAAA,IACF;AACA,iBAAa,SAAS;AACtB,iBAAa,SAAS;AAAA,EACxB;AAGA,QAAM,aAAa,gBAAgB,SAAS,IAAI;AAEhD,MAAI,CAAC,QAAQ,kBAAkB,CAAC,WAAW,OAAO;AAChD,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,SAAqB;AAAA,MACzB,SAAS;AAAA,MACT;AAAA,MACA,OAAO,WAAW,OAAO,KAAK,IAAI;AAAA,MAClC;AAAA,MACA;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,SAAS;AACpB,eAAS,mBAAmB,SAAS,QAAQ,MAAM,UAAU,CAAC;AAAA,IAChE;AAEA,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,YAAY;AAAA,MACvC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAED,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,eAAe,MAAM,SAAS,KAAK;AAEzC,UAAM,SAAqB;AAAA,MACzB,SAAS,SAAS;AAAA,MAClB;AAAA,MACA,YAAY,SAAS;AAAA,MACrB,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO,QAAQ,QAAQ,SAAS,MAAM,KAAK,YAAY;AAAA,IACzD;AAGA,QAAI,CAAC,QAAQ,SAAS;AACpB,eAAS,mBAAmB,SAAS,QAAQ,MAAM,UAAU,CAAC;AAAA,IAChE;AAEA,WAAO;AAAA,EAET,SAAS,KAAK;AACZ,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAM,QAAQ,eAAe,QAAQ,IAAI,UAAU;AAEnD,UAAM,SAAqB;AAAA,MACzB,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,SAAS;AACpB,eAAS,mBAAmB,SAAS,QAAQ,MAAM,UAAU,CAAC;AAAA,IAChE;AAEA,WAAO;AAAA,EACT;AACF;AAKA,SAAS,mBACP,SACA,QACA,MACA,YACc;AACd,SAAO;AAAA,IACL,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,QAAQ;AAAA,IACR,YAAY,OAAO,WAAW;AAAA,IAC9B;AAAA,IACA;AAAA,IACA,SAAS,OAAO;AAAA,IAChB,aAAa,OAAO;AAAA,IACpB,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,EACtB;AACF;;;AFnJO,SAAS,oBAAoBC,MAAgB;AAClD,EAAAA,KACG,QAAQ,QAAQ,+BAA+B,EAC/C,OAAO,wBAAwB,sBAAsB,EACrD,OAAO,qBAAqB,wBAAwB,EACpD,OAAO,uBAAuB,yCAAyC,EACvE,OAAO,uBAAuB,+BAA+B,EAC7D,OAAO,qBAAqB,yBAAyB,EACrD,OAAO,cAAc,sBAAsB,EAC3C,OAAO,UAAU,uBAAuB,EACxC,QAAQ,4DAA4D,EACpE,QAAQ,iCAAiC,EACzC,QAAQ,iDAAiD,EACzD,QAAQ,2DAA6D,EACrE,OAAO,OAAO,YAAyB;AACtC,QAAI;AAGJ,QAAI,QAAQ,SAAS;AACnB,gBAAU,QAAQ;AAAA,IACpB,WAAW,QAAQ,MAAM;AACvB,UAAI;AACF,kBAAUC,cAAa,QAAQ,MAAM,OAAO;AAAA,MAC9C,SAAS,KAAK;AACZ,gBAAQ,MAAM,uBAAuB,QAAQ,IAAI,EAAE;AACnD,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,OAAO;AAEL,gBAAU,MAAM,UAAU;AAC1B,UAAI,CAAC,SAAS;AACZ,gBAAQ,MAAM,wEAAwE;AACtF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,UAAU,cAAc,OAAO;AAGrC,UAAM,SAAS,MAAM,cAAc,SAAS;AAAA,MAC1C,QAAQ,QAAQ;AAAA,MAChB,YAAY,QAAQ;AAAA,MACpB,gBAAgB,QAAQ;AAAA,MACxB,SAAS,QAAQ;AAAA,IACnB,CAAC;AAGD,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,OAAO;AACL,UAAI,OAAO,SAAS;AAClB,gBAAQ,IAAI,yBAAoB,OAAO,UAAU,GAAG;AACpD,gBAAQ,IAAI,aAAa,OAAO,UAAU,EAAE;AAC5C,gBAAQ,IAAI,WAAW,OAAO,UAAU,IAAI;AAC5C,gBAAQ,IAAI,WAAW,OAAO,WAAW,UAAU,WAAW,OAAO,WAAW,YAAY,aAAa;AAAA,MAC3G,OAAO;AACL,gBAAQ,MAAM,uBAAkB;AAChC,gBAAQ,MAAM,YAAY,OAAO,KAAK,EAAE;AACxC,YAAI,OAAO,YAAY;AACrB,kBAAQ,MAAM,aAAa,OAAO,UAAU,EAAE;AAAA,QAChD;AACA,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,aAAa;AACzB,gBAAQ,IAAI,iBAAiB,OAAO,UAAU,CAAC;AAAA,MACjD;AAAA,IACF;AAEA,YAAQ,KAAK,OAAO,UAAU,IAAI,CAAC;AAAA,EACrC,CAAC;AACL;AAKA,eAAe,YAA6B;AAE1C,MAAI,QAAQ,MAAM,OAAO;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,SAAmB,CAAC;AAE1B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAU,OAAO,KAAK,KAAK,CAAC;AACtD,YAAQ,MAAM,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,CAAC,CAAC;AACrF,YAAQ,MAAM,GAAG,SAAS,MAAM,QAAQ,EAAE,CAAC;AAG3C,eAAW,MAAM;AACf,UAAI,OAAO,WAAW,GAAG;AACvB,gBAAQ,EAAE;AAAA,MACZ;AAAA,IACF,GAAG,GAAG;AAAA,EACR,CAAC;AACH;;;AG9GA,SAAS,gBAAAC,qBAAoB;AAatB,SAAS,wBAAwBC,MAAgB;AACtD,EAAAA,KACG,QAAQ,YAAY,kCAAkC,EACtD,OAAO,wBAAwB,0BAA0B,EACzD,OAAO,qBAAqB,wBAAwB,EACpD,OAAO,qBAAqB,8BAA8B,EAC1D,OAAO,UAAU,uBAAuB,EACxC,QAAQ,qCAAqC,EAC7C,QAAQ,uDAAuD,EAC/D,OAAO,OAAO,YAA6B;AAC1C,QAAI;AAGJ,QAAI,QAAQ,SAAS;AACnB,gBAAU,QAAQ;AAAA,IACpB,WAAW,QAAQ,MAAM;AACvB,UAAI;AACF,kBAAUC,cAAa,QAAQ,MAAM,OAAO;AAAA,MAC9C,SAAS,KAAK;AACZ,gBAAQ,MAAM,uBAAuB,QAAQ,IAAI,EAAE;AACnD,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,OAAO;AAEL,gBAAU,MAAMC,WAAU;AAC1B,UAAI,CAAC,SAAS;AACZ,gBAAQ,MAAM,wEAAwE;AACtF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,OAAO,QAAQ,QAAQ,QAAQ;AAGrC,UAAM,UAAU,cAAc,OAAO;AACrC,UAAM,SAAS,gBAAgB,SAAS,IAAI;AAG5C,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,OAAO;AACL,cAAQ,IAAI,iBAAiB,MAAM,CAAC;AAAA,IACtC;AAEA,YAAQ,KAAK,OAAO,QAAQ,IAAI,CAAC;AAAA,EACnC,CAAC;AACL;AAKA,eAAeA,aAA6B;AAC1C,MAAI,QAAQ,MAAM,OAAO;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,SAAmB,CAAC;AAE1B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAQ,MAAM,GAAG,QAAQ,CAAC,UAAU,OAAO,KAAK,KAAK,CAAC;AACtD,YAAQ,MAAM,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,CAAC,CAAC;AACrF,YAAQ,MAAM,GAAG,SAAS,MAAM,QAAQ,EAAE,CAAC;AAE3C,eAAW,MAAM;AACf,UAAI,OAAO,WAAW,GAAG;AACvB,gBAAQ,EAAE;AAAA,MACZ;AAAA,IACF,GAAG,GAAG;AAAA,EACR,CAAC;AACH;;;ATtEA,IAAMC,WAAU,cAAc,YAAY,GAAG;AAC7C,IAAM,EAAE,QAAQ,IAAIA,SAAQ,iBAAiB;AAE7C,IAAM,MAAM,IAAI,OAAO;AAEvB,IAAI,QAAQ,OAAO;AAGnB,oBAAoB,GAAG;AACvB,wBAAwB,GAAG;AAC3B,sBAAsB,GAAG;AACzB,uBAAuB,GAAG;AAG1B,IAAI,KAAK;AAGT,IAAI,QAAQ,EAAE,EAAE,OAAO,MAAM;AAC3B,MAAI,WAAW;AACjB,CAAC;AAGD,IAAI,MAAM;","names":["name","existsSync","readFileSync","existsSync","readFileSync","cli","unlinkSync","cli","unlinkSync","readFileSync","validation","cli","readFileSync","readFileSync","cli","readFileSync","readStdin","require"]}
|