logpare 0.0.1 → 0.0.3
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/LICENSE +21 -0
- package/README.md +309 -16
- package/dist/chunk-JRS36ZWP.js +574 -0
- package/dist/chunk-JRS36ZWP.js.map +1 -0
- package/dist/cli.cjs +729 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +175 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +606 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +259 -0
- package/dist/index.d.ts +259 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -9
- package/index.js +0 -1
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/preprocessing/patterns.ts","../src/preprocessing/default.ts","../src/drain/node.ts","../src/drain/cluster.ts","../src/output/formatter.ts","../src/drain/drain.ts","../src/api.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { compressText } from \"./api.js\";\nimport type { CompressOptions, OutputFormat } from \"./types.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\"));\nconst VERSION = pkg.version as string;\n\nconst HELP = `\nlogpare - Semantic log compression for LLM context windows\n\nUSAGE:\n logpare [options] [file...]\n cat logs.txt | logpare [options]\n\nOPTIONS:\n -f, --format <fmt> Output format: summary, detailed, json (default: summary)\n -o, --output <file> Write output to file instead of stdout\n -d, --depth <n> Parse tree depth (default: 4)\n -t, --threshold <n> Similarity threshold 0.0-1.0 (default: 0.4)\n -c, --max-children <n> Max children per node (default: 100)\n -m, --max-clusters <n> Max total clusters (default: 1000)\n -n, --max-templates <n> Max templates in output (default: 50)\n -h, --help Show this help message\n -v, --version Show version number\n\nEXAMPLES:\n logpare server.log\n cat /var/log/syslog | logpare --format json\n logpare --depth 5 --threshold 0.5 app.log -o templates.txt\n logpare access.log error.log --format detailed\n\nDESCRIPTION:\n logpare uses the Drain algorithm to extract templates from repetitive log\n data, achieving 60-90% token reduction while preserving diagnostic information.\n This is useful for fitting more log context into LLM prompts.\n\n For more information: https://github.com/logpare/logpare\n`;\n\ninterface ParsedArgs {\n format: OutputFormat;\n output: string | undefined;\n depth: number;\n threshold: number;\n maxChildren: number;\n maxClusters: number;\n maxTemplates: number;\n files: string[];\n help: boolean;\n version: boolean;\n}\n\nfunction parseCliArgs(): ParsedArgs {\n const { values, positionals } = parseArgs({\n options: {\n format: { type: \"string\", short: \"f\", default: \"summary\" },\n output: { type: \"string\", short: \"o\" },\n depth: { type: \"string\", short: \"d\", default: \"4\" },\n threshold: { type: \"string\", short: \"t\", default: \"0.4\" },\n \"max-children\": { type: \"string\", short: \"c\", default: \"100\" },\n \"max-clusters\": { type: \"string\", short: \"m\", default: \"1000\" },\n \"max-templates\": { type: \"string\", short: \"n\", default: \"50\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n version: { type: \"boolean\", short: \"v\", default: false },\n },\n allowPositionals: true,\n });\n\n const format = values.format as string;\n if (![\"summary\", \"detailed\", \"json\"].includes(format)) {\n console.error(\n `Error: Invalid format \"${format}\". Use: summary, detailed, json`\n );\n process.exit(1);\n }\n\n const depth = parseInt(values.depth as string, 10);\n const threshold = parseFloat(values.threshold as string);\n const maxChildren = parseInt(values[\"max-children\"] as string, 10);\n const maxClusters = parseInt(values[\"max-clusters\"] as string, 10);\n const maxTemplates = parseInt(values[\"max-templates\"] as string, 10);\n\n if (isNaN(depth) || depth < 1) {\n console.error(\n `Error: Invalid depth \"${values.depth}\". Must be a positive integer.`\n );\n process.exit(1);\n }\n\n if (isNaN(threshold) || threshold < 0 || threshold > 1) {\n console.error(\n `Error: Invalid threshold \"${values.threshold}\". Must be a number between 0.0 and 1.0.`\n );\n process.exit(1);\n }\n\n if (isNaN(maxChildren) || maxChildren < 1) {\n console.error(\n `Error: Invalid max-children \"${values[\"max-children\"]}\". Must be a positive integer.`\n );\n process.exit(1);\n }\n\n if (isNaN(maxClusters) || maxClusters < 1) {\n console.error(\n `Error: Invalid max-clusters \"${values[\"max-clusters\"]}\". Must be a positive integer.`\n );\n process.exit(1);\n }\n\n if (isNaN(maxTemplates) || maxTemplates < 1) {\n console.error(\n `Error: Invalid max-templates \"${values[\"max-templates\"]}\". Must be a positive integer.`\n );\n process.exit(1);\n }\n\n return {\n format: format as OutputFormat,\n output: values.output as string | undefined,\n depth,\n threshold,\n maxChildren,\n maxClusters,\n maxTemplates,\n files: positionals,\n help: values.help as boolean,\n version: values.version as boolean,\n };\n}\n\nfunction readInput(files: string[]): string {\n if (files.length > 0) {\n const contents: string[] = [];\n for (const file of files) {\n if (!existsSync(file)) {\n console.error(`Error: File not found: ${file}`);\n process.exit(1);\n }\n contents.push(readFileSync(file, \"utf-8\"));\n }\n return contents.join(\"\\n\");\n }\n\n // Read from stdin\n if (process.stdin.isTTY) {\n console.error(\n \"Error: No input provided. Provide file(s) or pipe input via stdin.\"\n );\n console.error('Run \"logpare --help\" for usage information.');\n process.exit(1);\n }\n\n return readFileSync(0, \"utf-8\");\n}\n\nfunction main(): void {\n const args = parseCliArgs();\n\n if (args.help) {\n console.log(HELP);\n process.exit(0);\n }\n\n if (args.version) {\n console.log(`logpare v${VERSION}`);\n process.exit(0);\n }\n\n const input = readInput(args.files);\n\n if (!input.trim()) {\n console.error(\"Error: Empty input\");\n process.exit(1);\n }\n\n const options: CompressOptions = {\n format: args.format,\n maxTemplates: args.maxTemplates,\n drain: {\n depth: args.depth,\n simThreshold: args.threshold,\n maxChildren: args.maxChildren,\n maxClusters: args.maxClusters,\n },\n };\n\n const result = compressText(input, options);\n\n const output =\n args.format === \"json\"\n ? JSON.stringify(\n { templates: result.templates, stats: result.stats },\n null,\n 2\n )\n : result.formatted;\n\n if (args.output) {\n writeFileSync(args.output, output, \"utf-8\");\n console.error(`Output written to ${args.output}`);\n } else {\n console.log(output);\n }\n}\n\nmain();\n","/**\n * Built-in regex patterns for common variable types.\n * These are applied in order during preprocessing to mask variables.\n * Order matters: more specific patterns (like timestamps) must run before\n * patterns that could match substrings (like port numbers).\n */\nexport const DEFAULT_PATTERNS: Record<string, RegExp> = {\n // Timestamps (most specific - must run before port to avoid fragmentation)\n isoTimestamp: /\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(?:[.,]\\d+)?(?:Z|[+-]\\d{2}:?\\d{2})?/g,\n unixTimestamp: /\\b\\d{10,13}\\b/g,\n\n // Network addresses\n ipv4: /\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/g,\n ipv6: /\\b[0-9a-fA-F:]{7,39}\\b/g,\n port: /:\\d{2,5}\\b/g,\n\n // Identifiers\n uuid: /\\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\\b/g,\n hexId: /\\b0x[0-9a-fA-F]+\\b/g,\n blockId: /\\bblk_-?\\d+\\b/g,\n\n // Paths and URLs\n filePath: /(?:\\/[\\w.-]+)+/g,\n url: /https?:\\/\\/[^\\s]+/g,\n\n // Numbers (applied last - most aggressive)\n // Matches standalone numbers and numbers with units (e.g., 250ms, 1024KB)\n numbers: /\\b\\d+(?:\\.\\d+)?[a-zA-Z]*\\b/g,\n};\n\n/**\n * Placeholder used when masking variables.\n */\nexport const WILDCARD = '<*>';\n\n/**\n * Apply a set of patterns to mask variables in a line.\n * Patterns are applied in the order provided.\n */\nexport function applyPatterns(\n line: string,\n patterns: Record<string, RegExp>,\n wildcard: string = WILDCARD\n): string {\n let result = line;\n\n for (const pattern of Object.values(patterns)) {\n // Create a new RegExp instance to reset lastIndex for global patterns\n const regex = new RegExp(pattern.source, pattern.flags);\n result = result.replace(regex, wildcard);\n }\n\n return result;\n}\n","import type { ParsingStrategy } from '../types.js';\nimport { DEFAULT_PATTERNS, applyPatterns, WILDCARD } from './patterns.js';\n\n/**\n * Default parsing strategy for log preprocessing.\n */\nexport const defaultStrategy: ParsingStrategy = {\n /**\n * Preprocess a log line by masking common variable patterns.\n */\n preprocess(line: string): string {\n return applyPatterns(line, DEFAULT_PATTERNS, WILDCARD);\n },\n\n /**\n * Tokenize a line by splitting on whitespace.\n */\n tokenize(line: string): string[] {\n return line.split(/\\s+/).filter((token) => token.length > 0);\n },\n\n /**\n * Get similarity threshold for a given tree depth.\n * Uses a constant threshold of 0.4.\n */\n getSimThreshold(_depth: number): number {\n return 0.4;\n },\n};\n\n/**\n * Create a custom parsing strategy by extending the default.\n */\nexport function defineStrategy(\n overrides: Partial<ParsingStrategy> & {\n patterns?: Record<string, RegExp>;\n }\n): ParsingStrategy {\n const { patterns, ...strategyOverrides } = overrides;\n\n return {\n preprocess: strategyOverrides.preprocess ?? ((line: string) => {\n const mergedPatterns = patterns\n ? { ...DEFAULT_PATTERNS, ...patterns }\n : DEFAULT_PATTERNS;\n return applyPatterns(line, mergedPatterns, WILDCARD);\n }),\n\n tokenize: strategyOverrides.tokenize ?? defaultStrategy.tokenize,\n\n getSimThreshold: strategyOverrides.getSimThreshold ?? defaultStrategy.getSimThreshold,\n };\n}\n","import type { LogCluster } from './cluster.js';\n\n/**\n * A node in the Drain parse tree.\n *\n * V8 Optimization: Uses Map<string, DrainNode> instead of plain objects\n * for dynamic children. This prevents V8 \"dictionary mode\" which would\n * cause 10-100x slower property access.\n *\n * V8 Optimization: All properties are initialized in the constructor\n * to ensure monomorphic object shapes.\n */\nexport class DrainNode {\n /** Depth of this node in the tree (0 = root) */\n readonly depth: number;\n\n /**\n * Child nodes keyed by token value.\n * Using Map instead of Object for V8 optimization.\n */\n readonly children: Map<string, DrainNode>;\n\n /**\n * Clusters (templates) stored at this node.\n * Only leaf nodes contain clusters.\n */\n readonly clusters: LogCluster[];\n\n constructor(depth: number) {\n this.depth = depth;\n this.children = new Map();\n this.clusters = [];\n }\n\n /**\n * Get or create a child node for the given key.\n */\n getOrCreateChild(key: string): DrainNode {\n let child = this.children.get(key);\n if (child === undefined) {\n child = new DrainNode(this.depth + 1);\n this.children.set(key, child);\n }\n return child;\n }\n\n /**\n * Check if this node has a child for the given key.\n */\n hasChild(key: string): boolean {\n return this.children.has(key);\n }\n\n /**\n * Get a child node by key, or undefined if not found.\n */\n getChild(key: string): DrainNode | undefined {\n return this.children.get(key);\n }\n\n /**\n * Add a cluster to this node.\n */\n addCluster(cluster: LogCluster): void {\n this.clusters.push(cluster);\n }\n\n /**\n * Get the number of children.\n */\n get childCount(): number {\n return this.children.size;\n }\n\n /**\n * Get the number of clusters.\n */\n get clusterCount(): number {\n return this.clusters.length;\n }\n}\n","import { WILDCARD } from '../preprocessing/patterns.js';\n\n/**\n * Represents a log cluster (template) discovered by the Drain algorithm.\n *\n * V8 Optimization: All properties are initialized in the constructor\n * to ensure monomorphic object shapes for optimal property access.\n */\nexport class LogCluster {\n /** Unique identifier for this cluster */\n readonly id: string;\n\n /** Template tokens (with wildcards for variable positions) */\n readonly tokens: string[];\n\n /** Number of log lines matching this template */\n count: number;\n\n /** Sample variable values from first N matches */\n readonly sampleVariables: string[][];\n\n /** Line index of first occurrence */\n firstSeen: number;\n\n /** Line index of most recent occurrence */\n lastSeen: number;\n\n /** Maximum number of sample variables to store */\n private readonly maxSamples: number;\n\n constructor(id: string, tokens: string[], lineIndex: number, maxSamples: number = 3) {\n this.id = id;\n this.tokens = tokens.slice(); // Defensive copy\n this.count = 1;\n this.sampleVariables = [];\n this.firstSeen = lineIndex;\n this.lastSeen = lineIndex;\n this.maxSamples = maxSamples;\n }\n\n /**\n * Update the cluster with a new matching log line.\n * Returns the variables extracted from this match.\n */\n update(tokens: string[], lineIndex: number): string[] {\n this.count++;\n this.lastSeen = lineIndex;\n\n // Extract variables (positions where template has wildcard)\n const variables: string[] = [];\n for (let i = 0; i < this.tokens.length && i < tokens.length; i++) {\n if (this.tokens[i] === WILDCARD) {\n variables.push(tokens[i] ?? '');\n }\n }\n\n // Store sample if we haven't reached the limit\n if (this.sampleVariables.length < this.maxSamples) {\n this.sampleVariables.push(variables);\n }\n\n return variables;\n }\n\n /**\n * Get the template pattern as a string.\n */\n getPattern(): string {\n return this.tokens.join(' ');\n }\n\n /**\n * Compute similarity between this cluster's template and a set of tokens.\n * Returns a value between 0.0 and 1.0.\n */\n computeSimilarity(tokens: string[]): number {\n // Guard against division by zero\n if (this.tokens.length === 0) {\n return 0;\n }\n\n if (tokens.length !== this.tokens.length) {\n return 0;\n }\n\n let matchCount = 0;\n for (let i = 0; i < this.tokens.length; i++) {\n const templateToken = this.tokens[i];\n const inputToken = tokens[i];\n\n // Wildcards always match\n if (templateToken === WILDCARD) {\n matchCount++;\n } else if (templateToken === inputToken) {\n matchCount++;\n }\n }\n\n return matchCount / this.tokens.length;\n }\n\n /**\n * Merge tokens into the template, converting differing positions to wildcards.\n * Mutates the template tokens in place.\n */\n mergeTokens(tokens: string[]): void {\n for (let i = 0; i < this.tokens.length && i < tokens.length; i++) {\n if (this.tokens[i] !== WILDCARD && this.tokens[i] !== tokens[i]) {\n (this.tokens as string[])[i] = WILDCARD;\n }\n }\n }\n}\n","import type { Template, CompressionResult } from '../types.js';\n\n/**\n * Format templates as a compact summary.\n */\nexport function formatSummary(\n templates: Template[],\n stats: CompressionResult['stats']\n): string {\n const lines: string[] = [];\n\n // Header\n lines.push('=== Log Compression Summary ===');\n lines.push(\n `Input: ${stats.inputLines.toLocaleString()} lines → ${stats.uniqueTemplates} templates ` +\n `(${(stats.compressionRatio * 100).toFixed(1)}% reduction)`\n );\n lines.push('');\n\n if (templates.length === 0) {\n lines.push('No templates discovered.');\n return lines.join('\\n');\n }\n\n // Top templates by frequency\n lines.push('Top templates by frequency:');\n\n const topTemplates = templates.slice(0, 20);\n topTemplates.forEach((template, index) => {\n const count = template.occurrences.toLocaleString();\n lines.push(`${index + 1}. [${count}x] ${template.pattern}`);\n });\n\n if (templates.length > 20) {\n lines.push(`... and ${templates.length - 20} more templates`);\n }\n\n // Rare events section\n const rareTemplates = templates.filter((t) => t.occurrences <= 5);\n if (rareTemplates.length > 0) {\n lines.push('');\n lines.push(`Rare events (≤5 occurrences): ${rareTemplates.length} templates`);\n\n const shownRare = rareTemplates.slice(0, 5);\n for (const template of shownRare) {\n lines.push(`- [${template.occurrences}x] ${template.pattern}`);\n }\n\n if (rareTemplates.length > 5) {\n lines.push(`... and ${rareTemplates.length - 5} more rare templates`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format templates with full details including sample variables.\n */\nexport function formatDetailed(\n templates: Template[],\n stats: CompressionResult['stats']\n): string {\n const lines: string[] = [];\n\n // Header\n lines.push('=== Log Compression Details ===');\n lines.push(\n `Input: ${stats.inputLines.toLocaleString()} lines → ${stats.uniqueTemplates} templates ` +\n `(${(stats.compressionRatio * 100).toFixed(1)}% reduction)`\n );\n lines.push(`Estimated token reduction: ${(stats.estimatedTokenReduction * 100).toFixed(1)}%`);\n lines.push('');\n\n if (templates.length === 0) {\n lines.push('No templates discovered.');\n return lines.join('\\n');\n }\n\n // Each template with samples\n for (const template of templates) {\n lines.push(`=== Template ${template.id} (${template.occurrences.toLocaleString()} occurrences) ===`);\n lines.push(`Pattern: ${template.pattern}`);\n lines.push(`First seen: line ${template.firstSeen + 1}`);\n lines.push(`Last seen: line ${template.lastSeen + 1}`);\n\n if (template.sampleVariables.length > 0) {\n lines.push('Sample variables:');\n for (const vars of template.sampleVariables) {\n if (vars.length > 0) {\n lines.push(` - ${vars.join(', ')}`);\n }\n }\n }\n\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format templates as JSON.\n */\nexport function formatJson(\n templates: Template[],\n stats: CompressionResult['stats']\n): string {\n const output = {\n version: '1.0',\n stats: {\n inputLines: stats.inputLines,\n uniqueTemplates: stats.uniqueTemplates,\n compressionRatio: Math.round(stats.compressionRatio * 1000) / 1000,\n estimatedTokenReduction: Math.round(stats.estimatedTokenReduction * 1000) / 1000,\n },\n templates: templates.map((t) => ({\n id: t.id,\n pattern: t.pattern,\n occurrences: t.occurrences,\n samples: t.sampleVariables,\n firstSeen: t.firstSeen,\n lastSeen: t.lastSeen,\n })),\n };\n\n return JSON.stringify(output, null, 2);\n}\n","import type { DrainOptions, ParsingStrategy, Template, OutputFormat, CompressionResult } from '../types.js';\nimport { defaultStrategy } from '../preprocessing/default.js';\nimport { WILDCARD } from '../preprocessing/patterns.js';\nimport { DrainNode } from './node.js';\nimport { LogCluster } from './cluster.js';\nimport { formatSummary, formatDetailed, formatJson } from '../output/formatter.js';\n\n/**\n * Default configuration values for Drain algorithm.\n */\nconst DEFAULTS = {\n depth: 4,\n simThreshold: 0.4,\n maxChildren: 100,\n maxClusters: 1000,\n maxSamples: 3,\n} as const;\n\n/**\n * Special key used for the wildcard child in the parse tree.\n */\nconst WILDCARD_KEY = '<WILDCARD>';\n\n/**\n * Drain algorithm implementation for log template mining.\n *\n * The algorithm constructs a fixed-depth parse tree to efficiently\n * cluster log messages by their template structure.\n *\n * Tree Structure:\n * - Level 0 (root): Entry point\n * - Level 1: Token count (length of log message)\n * - Level 2: First token of the message\n * - Levels 3+: Subsequent tokens up to configured depth\n * - Leaf: LogCluster containing the template\n */\nexport class Drain {\n private readonly root: DrainNode;\n private readonly clusters: LogCluster[];\n private readonly strategy: ParsingStrategy;\n private readonly depth: number;\n private readonly maxChildren: number;\n private readonly maxClusters: number;\n private readonly maxSamples: number;\n private lineCount: number;\n private nextClusterId: number;\n\n constructor(options: DrainOptions = {}) {\n this.root = new DrainNode(0);\n this.clusters = [];\n this.strategy = options.preprocessing ?? defaultStrategy;\n this.depth = options.depth ?? DEFAULTS.depth;\n this.maxChildren = options.maxChildren ?? DEFAULTS.maxChildren;\n this.maxClusters = options.maxClusters ?? DEFAULTS.maxClusters;\n this.maxSamples = options.maxSamples ?? DEFAULTS.maxSamples;\n this.lineCount = 0;\n this.nextClusterId = 1;\n }\n\n /**\n * Process a single log line.\n */\n addLogLine(line: string): LogCluster | null {\n const lineIndex = this.lineCount++;\n\n // Skip empty lines\n const trimmed = line.trim();\n if (trimmed.length === 0) {\n return null;\n }\n\n // Preprocess and tokenize\n const preprocessed = this.strategy.preprocess(trimmed);\n const tokens = this.strategy.tokenize(preprocessed);\n\n if (tokens.length === 0) {\n return null;\n }\n\n // Search for matching cluster\n const matchedCluster = this.treeSearch(tokens);\n\n if (matchedCluster !== null) {\n // Update existing cluster\n matchedCluster.update(tokens, lineIndex);\n matchedCluster.mergeTokens(tokens);\n return matchedCluster;\n }\n\n // Create new cluster if under limit\n if (this.clusters.length >= this.maxClusters) {\n return null;\n }\n\n return this.createCluster(tokens, lineIndex);\n }\n\n /**\n * Process multiple log lines.\n */\n addLogLines(lines: string[]): void {\n for (const line of lines) {\n this.addLogLine(line);\n }\n }\n\n /**\n * Search the parse tree for a matching cluster.\n */\n private treeSearch(tokens: string[]): LogCluster | null {\n const tokenCount = tokens.length;\n const tokenCountKey = String(tokenCount);\n\n // Level 1: Navigate by token count\n const lengthNode = this.root.getChild(tokenCountKey);\n if (lengthNode === undefined) {\n return null;\n }\n\n // Level 2: Navigate by first token\n const firstToken = tokens[0];\n if (firstToken === undefined) {\n return null;\n }\n\n let currentNode = lengthNode.getChild(firstToken);\n\n // Try wildcard child if exact match not found\n if (currentNode === undefined) {\n currentNode = lengthNode.getChild(WILDCARD_KEY);\n }\n\n if (currentNode === undefined) {\n return null;\n }\n\n // Levels 3+: Navigate by subsequent tokens\n let searchNode: DrainNode = currentNode;\n for (let i = 1; i < Math.min(tokens.length, this.depth); i++) {\n const token = tokens[i];\n if (token === undefined) {\n break;\n }\n\n let nextNode = searchNode.getChild(token);\n\n // Try wildcard child if exact match not found\n if (nextNode === undefined) {\n nextNode = searchNode.getChild(WILDCARD_KEY);\n }\n\n if (nextNode === undefined) {\n break;\n }\n\n searchNode = nextNode;\n }\n\n // Search clusters at this node for best match\n return this.findBestMatch(searchNode, tokens);\n }\n\n /**\n * Find the best matching cluster at a node.\n */\n private findBestMatch(node: DrainNode, tokens: string[]): LogCluster | null {\n let bestCluster: LogCluster | null = null;\n let bestSimilarity = 0;\n\n const threshold = this.strategy.getSimThreshold(node.depth);\n\n for (const cluster of node.clusters) {\n const similarity = cluster.computeSimilarity(tokens);\n\n if (similarity >= threshold && similarity > bestSimilarity) {\n bestSimilarity = similarity;\n bestCluster = cluster;\n }\n }\n\n return bestCluster;\n }\n\n /**\n * Create a new cluster and add it to the tree.\n */\n private createCluster(tokens: string[], lineIndex: number): LogCluster {\n const clusterId = `t${String(this.nextClusterId++).padStart(3, '0')}`;\n const cluster = new LogCluster(clusterId, tokens, lineIndex, this.maxSamples);\n\n // Navigate/create path in tree\n const tokenCount = tokens.length;\n const tokenCountKey = String(tokenCount);\n\n // Level 1: Token count\n const lengthNode = this.root.getOrCreateChild(tokenCountKey);\n\n // Level 2: First token\n const firstToken = tokens[0];\n if (firstToken === undefined) {\n this.clusters.push(cluster);\n return cluster;\n }\n\n // Decide whether to use actual token or wildcard key\n const firstKey = this.shouldUseWildcard(lengthNode, firstToken)\n ? WILDCARD_KEY\n : firstToken;\n\n let currentNode = lengthNode.getOrCreateChild(firstKey);\n\n // Levels 3+: Subsequent tokens\n for (let i = 1; i < Math.min(tokens.length, this.depth); i++) {\n const token = tokens[i];\n if (token === undefined) {\n break;\n }\n\n const key = this.shouldUseWildcard(currentNode, token)\n ? WILDCARD_KEY\n : token;\n\n currentNode = currentNode.getOrCreateChild(key);\n }\n\n // Add cluster to leaf node\n currentNode.addCluster(cluster);\n this.clusters.push(cluster);\n\n return cluster;\n }\n\n /**\n * Determine if we should use a wildcard key for a token.\n * Uses maxChildren limit to prevent tree explosion.\n */\n private shouldUseWildcard(node: DrainNode, token: string): boolean {\n // If token already exists as child, use it\n if (node.hasChild(token)) {\n return false;\n }\n\n // If we have wildcard child and are at capacity, use wildcard\n if (node.hasChild(WILDCARD_KEY) && node.childCount >= this.maxChildren) {\n return true;\n }\n\n // If token looks like a variable (starts with digit, etc.), use wildcard\n if (this.looksLikeVariable(token)) {\n return true;\n }\n\n // Otherwise use the actual token\n return false;\n }\n\n /**\n * Heuristic to detect if a token looks like a variable value.\n */\n private looksLikeVariable(token: string): boolean {\n // Already a wildcard\n if (token === WILDCARD) {\n return true;\n }\n\n // Starts with a digit\n const firstChar = token.charAt(0);\n if (firstChar >= '0' && firstChar <= '9') {\n return true;\n }\n\n // Contains only hex characters (likely an ID)\n if (/^[0-9a-fA-F]+$/.test(token) && token.length > 8) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Get all discovered templates.\n */\n getTemplates(): Template[] {\n return this.clusters.map((cluster) => ({\n id: cluster.id,\n pattern: cluster.getPattern(),\n occurrences: cluster.count,\n sampleVariables: cluster.sampleVariables,\n firstSeen: cluster.firstSeen,\n lastSeen: cluster.lastSeen,\n }));\n }\n\n /**\n * Get compression result with formatted output.\n */\n getResult(format: OutputFormat = 'summary', maxTemplates: number = 50): CompressionResult {\n const templates = this.getTemplates();\n\n // Sort by occurrences (descending)\n templates.sort((a, b) => b.occurrences - a.occurrences);\n\n // Limit templates in output\n const limitedTemplates = templates.slice(0, maxTemplates);\n\n // Calculate stats\n const stats = this.calculateStats(templates);\n\n // Format output\n let formatted: string;\n switch (format) {\n case 'detailed':\n formatted = formatDetailed(limitedTemplates, stats);\n break;\n case 'json':\n formatted = formatJson(limitedTemplates, stats);\n break;\n case 'summary':\n default:\n formatted = formatSummary(limitedTemplates, stats);\n break;\n }\n\n return {\n templates: limitedTemplates,\n stats,\n formatted,\n };\n }\n\n /**\n * Calculate compression statistics.\n */\n private calculateStats(templates: Template[]): CompressionResult['stats'] {\n const inputLines = this.lineCount;\n const uniqueTemplates = templates.length;\n\n // Compression ratio: 1 - (templates / lines)\n const compressionRatio = inputLines > 0\n ? 1 - (uniqueTemplates / inputLines)\n : 0;\n\n // Estimate token reduction using character count proxy\n // Each template is shown once instead of repeated N times\n let originalChars = 0;\n let compressedChars = 0;\n\n for (const template of templates) {\n const patternLength = template.pattern.length;\n // Original: pattern repeated for each occurrence\n originalChars += patternLength * template.occurrences;\n // Compressed: pattern shown once + count indicator\n compressedChars += patternLength + 20; // ~20 chars for \"[Nx] \" prefix\n }\n\n const estimatedTokenReduction = originalChars > 0\n ? 1 - (compressedChars / originalChars)\n : 0;\n\n return {\n inputLines,\n uniqueTemplates,\n compressionRatio: Math.max(0, Math.min(1, compressionRatio)),\n estimatedTokenReduction: Math.max(0, Math.min(1, estimatedTokenReduction)),\n };\n }\n\n /**\n * Get the number of lines processed.\n */\n get totalLines(): number {\n return this.lineCount;\n }\n\n /**\n * Get the number of clusters (templates) discovered.\n */\n get totalClusters(): number {\n return this.clusters.length;\n }\n}\n\n/**\n * Create a new Drain instance with the given options.\n */\nexport function createDrain(options?: DrainOptions): Drain {\n return new Drain(options);\n}\n","import type { CompressOptions, CompressionResult } from './types.js';\nimport { createDrain } from './drain/drain.js';\n\n/**\n * Compress log lines by extracting templates.\n *\n * This is the main entry point for simple use cases.\n * For more control, use `createDrain()` directly.\n *\n * @param lines - Array of log lines to compress\n * @param options - Compression options\n * @returns Compression result with templates and statistics\n *\n * @example\n * ```typescript\n * import { compress } from 'logpare';\n *\n * const logs = [\n * 'Connection from 192.168.1.1 established',\n * 'Connection from 192.168.1.2 established',\n * 'Connection from 10.0.0.1 established',\n * ];\n *\n * const result = compress(logs);\n * console.log(result.formatted);\n * // Output: [3x] Connection from <*> established\n * ```\n */\nexport function compress(\n lines: string[],\n options: CompressOptions = {}\n): CompressionResult {\n const { format = 'summary', maxTemplates = 50, drain: drainOptions } = options;\n\n const drain = createDrain(drainOptions);\n drain.addLogLines(lines);\n\n return drain.getResult(format, maxTemplates);\n}\n\n/**\n * Compress a single string containing multiple log lines.\n *\n * @param text - Raw log text (lines separated by newlines)\n * @param options - Compression options\n * @returns Compression result with templates and statistics\n */\nexport function compressText(\n text: string,\n options: CompressOptions = {}\n): CompressionResult {\n const lines = text.split(/\\r?\\n/);\n return compress(lines, options);\n}\n"],"mappings":";;;;AACA,uBAA0B;AAC1B,qBAAwD;AACxD,sBAA8B;AAC9B,uBAA8B;;;ACEvB,IAAM,mBAA2C;AAAA;AAAA,EAEtD,cAAc;AAAA,EACd,eAAe;AAAA;AAAA,EAGf,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAGN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AAAA;AAAA,EAGT,UAAU;AAAA,EACV,KAAK;AAAA;AAAA;AAAA,EAIL,SAAS;AACX;AAKO,IAAM,WAAW;AAMjB,SAAS,cACd,MACA,UACA,WAAmB,UACX;AACR,MAAI,SAAS;AAEb,aAAW,WAAW,OAAO,OAAO,QAAQ,GAAG;AAE7C,UAAM,QAAQ,IAAI,OAAO,QAAQ,QAAQ,QAAQ,KAAK;AACtD,aAAS,OAAO,QAAQ,OAAO,QAAQ;AAAA,EACzC;AAEA,SAAO;AACT;;;AC/CO,IAAM,kBAAmC;AAAA;AAAA;AAAA;AAAA,EAI9C,WAAW,MAAsB;AAC/B,WAAO,cAAc,MAAM,kBAAkB,QAAQ;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAwB;AAC/B,WAAO,KAAK,MAAM,KAAK,EAAE,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,QAAwB;AACtC,WAAO;AAAA,EACT;AACF;;;AChBO,IAAM,YAAN,MAAM,WAAU;AAAA;AAAA,EAEZ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA,EAET,YAAY,OAAe;AACzB,SAAK,QAAQ;AACb,SAAK,WAAW,oBAAI,IAAI;AACxB,SAAK,WAAW,CAAC;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,KAAwB;AACvC,QAAI,QAAQ,KAAK,SAAS,IAAI,GAAG;AACjC,QAAI,UAAU,QAAW;AACvB,cAAQ,IAAI,WAAU,KAAK,QAAQ,CAAC;AACpC,WAAK,SAAS,IAAI,KAAK,KAAK;AAAA,IAC9B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,KAAsB;AAC7B,WAAO,KAAK,SAAS,IAAI,GAAG;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,KAAoC;AAC3C,WAAO,KAAK,SAAS,IAAI,GAAG;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAA2B;AACpC,SAAK,SAAS,KAAK,OAAO;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,aAAqB;AACvB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,eAAuB;AACzB,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;;;ACxEO,IAAM,aAAN,MAAiB;AAAA;AAAA,EAEb;AAAA;AAAA,EAGA;AAAA;AAAA,EAGT;AAAA;AAAA,EAGS;AAAA;AAAA,EAGT;AAAA;AAAA,EAGA;AAAA;AAAA,EAGiB;AAAA,EAEjB,YAAY,IAAY,QAAkB,WAAmB,aAAqB,GAAG;AACnF,SAAK,KAAK;AACV,SAAK,SAAS,OAAO,MAAM;AAC3B,SAAK,QAAQ;AACb,SAAK,kBAAkB,CAAC;AACxB,SAAK,YAAY;AACjB,SAAK,WAAW;AAChB,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,QAAkB,WAA6B;AACpD,SAAK;AACL,SAAK,WAAW;AAGhB,UAAM,YAAsB,CAAC;AAC7B,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,UAAU,IAAI,OAAO,QAAQ,KAAK;AAChE,UAAI,KAAK,OAAO,CAAC,MAAM,UAAU;AAC/B,kBAAU,KAAK,OAAO,CAAC,KAAK,EAAE;AAAA,MAChC;AAAA,IACF;AAGA,QAAI,KAAK,gBAAgB,SAAS,KAAK,YAAY;AACjD,WAAK,gBAAgB,KAAK,SAAS;AAAA,IACrC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAqB;AACnB,WAAO,KAAK,OAAO,KAAK,GAAG;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB,QAA0B;AAE1C,QAAI,KAAK,OAAO,WAAW,GAAG;AAC5B,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,WAAW,KAAK,OAAO,QAAQ;AACxC,aAAO;AAAA,IACT;AAEA,QAAI,aAAa;AACjB,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,QAAQ,KAAK;AAC3C,YAAM,gBAAgB,KAAK,OAAO,CAAC;AACnC,YAAM,aAAa,OAAO,CAAC;AAG3B,UAAI,kBAAkB,UAAU;AAC9B;AAAA,MACF,WAAW,kBAAkB,YAAY;AACvC;AAAA,MACF;AAAA,IACF;AAEA,WAAO,aAAa,KAAK,OAAO;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,QAAwB;AAClC,aAAS,IAAI,GAAG,IAAI,KAAK,OAAO,UAAU,IAAI,OAAO,QAAQ,KAAK;AAChE,UAAI,KAAK,OAAO,CAAC,MAAM,YAAY,KAAK,OAAO,CAAC,MAAM,OAAO,CAAC,GAAG;AAC/D,QAAC,KAAK,OAAoB,CAAC,IAAI;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACF;;;AC3GO,SAAS,cACd,WACA,OACQ;AACR,QAAM,QAAkB,CAAC;AAGzB,QAAM,KAAK,iCAAiC;AAC5C,QAAM;AAAA,IACJ,UAAU,MAAM,WAAW,eAAe,CAAC,iBAAY,MAAM,eAAe,gBACvE,MAAM,mBAAmB,KAAK,QAAQ,CAAC,CAAC;AAAA,EAC/C;AACA,QAAM,KAAK,EAAE;AAEb,MAAI,UAAU,WAAW,GAAG;AAC1B,UAAM,KAAK,0BAA0B;AACrC,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAGA,QAAM,KAAK,6BAA6B;AAExC,QAAM,eAAe,UAAU,MAAM,GAAG,EAAE;AAC1C,eAAa,QAAQ,CAAC,UAAU,UAAU;AACxC,UAAM,QAAQ,SAAS,YAAY,eAAe;AAClD,UAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,SAAS,OAAO,EAAE;AAAA,EAC5D,CAAC;AAED,MAAI,UAAU,SAAS,IAAI;AACzB,UAAM,KAAK,WAAW,UAAU,SAAS,EAAE,iBAAiB;AAAA,EAC9D;AAGA,QAAM,gBAAgB,UAAU,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC;AAChE,MAAI,cAAc,SAAS,GAAG;AAC5B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,sCAAiC,cAAc,MAAM,YAAY;AAE5E,UAAM,YAAY,cAAc,MAAM,GAAG,CAAC;AAC1C,eAAW,YAAY,WAAW;AAChC,YAAM,KAAK,MAAM,SAAS,WAAW,MAAM,SAAS,OAAO,EAAE;AAAA,IAC/D;AAEA,QAAI,cAAc,SAAS,GAAG;AAC5B,YAAM,KAAK,WAAW,cAAc,SAAS,CAAC,sBAAsB;AAAA,IACtE;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAKO,SAAS,eACd,WACA,OACQ;AACR,QAAM,QAAkB,CAAC;AAGzB,QAAM,KAAK,iCAAiC;AAC5C,QAAM;AAAA,IACJ,UAAU,MAAM,WAAW,eAAe,CAAC,iBAAY,MAAM,eAAe,gBACvE,MAAM,mBAAmB,KAAK,QAAQ,CAAC,CAAC;AAAA,EAC/C;AACA,QAAM,KAAK,+BAA+B,MAAM,0BAA0B,KAAK,QAAQ,CAAC,CAAC,GAAG;AAC5F,QAAM,KAAK,EAAE;AAEb,MAAI,UAAU,WAAW,GAAG;AAC1B,UAAM,KAAK,0BAA0B;AACrC,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAGA,aAAW,YAAY,WAAW;AAChC,UAAM,KAAK,gBAAgB,SAAS,EAAE,KAAK,SAAS,YAAY,eAAe,CAAC,mBAAmB;AACnG,UAAM,KAAK,YAAY,SAAS,OAAO,EAAE;AACzC,UAAM,KAAK,oBAAoB,SAAS,YAAY,CAAC,EAAE;AACvD,UAAM,KAAK,mBAAmB,SAAS,WAAW,CAAC,EAAE;AAErD,QAAI,SAAS,gBAAgB,SAAS,GAAG;AACvC,YAAM,KAAK,mBAAmB;AAC9B,iBAAW,QAAQ,SAAS,iBAAiB;AAC3C,YAAI,KAAK,SAAS,GAAG;AACnB,gBAAM,KAAK,OAAO,KAAK,KAAK,IAAI,CAAC,EAAE;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAKO,SAAS,WACd,WACA,OACQ;AACR,QAAM,SAAS;AAAA,IACb,SAAS;AAAA,IACT,OAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB,iBAAiB,MAAM;AAAA,MACvB,kBAAkB,KAAK,MAAM,MAAM,mBAAmB,GAAI,IAAI;AAAA,MAC9D,yBAAyB,KAAK,MAAM,MAAM,0BAA0B,GAAI,IAAI;AAAA,IAC9E;AAAA,IACA,WAAW,UAAU,IAAI,CAAC,OAAO;AAAA,MAC/B,IAAI,EAAE;AAAA,MACN,SAAS,EAAE;AAAA,MACX,aAAa,EAAE;AAAA,MACf,SAAS,EAAE;AAAA,MACX,WAAW,EAAE;AAAA,MACb,UAAU,EAAE;AAAA,IACd,EAAE;AAAA,EACJ;AAEA,SAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AACvC;;;ACrHA,IAAM,WAAW;AAAA,EACf,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb,aAAa;AAAA,EACb,YAAY;AACd;AAKA,IAAM,eAAe;AAed,IAAM,QAAN,MAAY;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACA;AAAA,EAER,YAAY,UAAwB,CAAC,GAAG;AACtC,SAAK,OAAO,IAAI,UAAU,CAAC;AAC3B,SAAK,WAAW,CAAC;AACjB,SAAK,WAAW,QAAQ,iBAAiB;AACzC,SAAK,QAAQ,QAAQ,SAAS,SAAS;AACvC,SAAK,cAAc,QAAQ,eAAe,SAAS;AACnD,SAAK,cAAc,QAAQ,eAAe,SAAS;AACnD,SAAK,aAAa,QAAQ,cAAc,SAAS;AACjD,SAAK,YAAY;AACjB,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAiC;AAC1C,UAAM,YAAY,KAAK;AAGvB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO;AAAA,IACT;AAGA,UAAM,eAAe,KAAK,SAAS,WAAW,OAAO;AACrD,UAAM,SAAS,KAAK,SAAS,SAAS,YAAY;AAElD,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO;AAAA,IACT;AAGA,UAAM,iBAAiB,KAAK,WAAW,MAAM;AAE7C,QAAI,mBAAmB,MAAM;AAE3B,qBAAe,OAAO,QAAQ,SAAS;AACvC,qBAAe,YAAY,MAAM;AACjC,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,SAAS,UAAU,KAAK,aAAa;AAC5C,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,cAAc,QAAQ,SAAS;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAuB;AACjC,eAAW,QAAQ,OAAO;AACxB,WAAK,WAAW,IAAI;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,QAAqC;AACtD,UAAM,aAAa,OAAO;AAC1B,UAAM,gBAAgB,OAAO,UAAU;AAGvC,UAAM,aAAa,KAAK,KAAK,SAAS,aAAa;AACnD,QAAI,eAAe,QAAW;AAC5B,aAAO;AAAA,IACT;AAGA,UAAM,aAAa,OAAO,CAAC;AAC3B,QAAI,eAAe,QAAW;AAC5B,aAAO;AAAA,IACT;AAEA,QAAI,cAAc,WAAW,SAAS,UAAU;AAGhD,QAAI,gBAAgB,QAAW;AAC7B,oBAAc,WAAW,SAAS,YAAY;AAAA,IAChD;AAEA,QAAI,gBAAgB,QAAW;AAC7B,aAAO;AAAA,IACT;AAGA,QAAI,aAAwB;AAC5B,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO,QAAQ,KAAK,KAAK,GAAG,KAAK;AAC5D,YAAM,QAAQ,OAAO,CAAC;AACtB,UAAI,UAAU,QAAW;AACvB;AAAA,MACF;AAEA,UAAI,WAAW,WAAW,SAAS,KAAK;AAGxC,UAAI,aAAa,QAAW;AAC1B,mBAAW,WAAW,SAAS,YAAY;AAAA,MAC7C;AAEA,UAAI,aAAa,QAAW;AAC1B;AAAA,MACF;AAEA,mBAAa;AAAA,IACf;AAGA,WAAO,KAAK,cAAc,YAAY,MAAM;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,MAAiB,QAAqC;AAC1E,QAAI,cAAiC;AACrC,QAAI,iBAAiB;AAErB,UAAM,YAAY,KAAK,SAAS,gBAAgB,KAAK,KAAK;AAE1D,eAAW,WAAW,KAAK,UAAU;AACnC,YAAM,aAAa,QAAQ,kBAAkB,MAAM;AAEnD,UAAI,cAAc,aAAa,aAAa,gBAAgB;AAC1D,yBAAiB;AACjB,sBAAc;AAAA,MAChB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,QAAkB,WAA+B;AACrE,UAAM,YAAY,IAAI,OAAO,KAAK,eAAe,EAAE,SAAS,GAAG,GAAG,CAAC;AACnE,UAAM,UAAU,IAAI,WAAW,WAAW,QAAQ,WAAW,KAAK,UAAU;AAG5E,UAAM,aAAa,OAAO;AAC1B,UAAM,gBAAgB,OAAO,UAAU;AAGvC,UAAM,aAAa,KAAK,KAAK,iBAAiB,aAAa;AAG3D,UAAM,aAAa,OAAO,CAAC;AAC3B,QAAI,eAAe,QAAW;AAC5B,WAAK,SAAS,KAAK,OAAO;AAC1B,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,KAAK,kBAAkB,YAAY,UAAU,IAC1D,eACA;AAEJ,QAAI,cAAc,WAAW,iBAAiB,QAAQ;AAGtD,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO,QAAQ,KAAK,KAAK,GAAG,KAAK;AAC5D,YAAM,QAAQ,OAAO,CAAC;AACtB,UAAI,UAAU,QAAW;AACvB;AAAA,MACF;AAEA,YAAM,MAAM,KAAK,kBAAkB,aAAa,KAAK,IACjD,eACA;AAEJ,oBAAc,YAAY,iBAAiB,GAAG;AAAA,IAChD;AAGA,gBAAY,WAAW,OAAO;AAC9B,SAAK,SAAS,KAAK,OAAO;AAE1B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,MAAiB,OAAwB;AAEjE,QAAI,KAAK,SAAS,KAAK,GAAG;AACxB,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,SAAS,YAAY,KAAK,KAAK,cAAc,KAAK,aAAa;AACtE,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,kBAAkB,KAAK,GAAG;AACjC,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkB,OAAwB;AAEhD,QAAI,UAAU,UAAU;AACtB,aAAO;AAAA,IACT;AAGA,UAAM,YAAY,MAAM,OAAO,CAAC;AAChC,QAAI,aAAa,OAAO,aAAa,KAAK;AACxC,aAAO;AAAA,IACT;AAGA,QAAI,iBAAiB,KAAK,KAAK,KAAK,MAAM,SAAS,GAAG;AACpD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,eAA2B;AACzB,WAAO,KAAK,SAAS,IAAI,CAAC,aAAa;AAAA,MACrC,IAAI,QAAQ;AAAA,MACZ,SAAS,QAAQ,WAAW;AAAA,MAC5B,aAAa,QAAQ;AAAA,MACrB,iBAAiB,QAAQ;AAAA,MACzB,WAAW,QAAQ;AAAA,MACnB,UAAU,QAAQ;AAAA,IACpB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,SAAuB,WAAW,eAAuB,IAAuB;AACxF,UAAM,YAAY,KAAK,aAAa;AAGpC,cAAU,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW;AAGtD,UAAM,mBAAmB,UAAU,MAAM,GAAG,YAAY;AAGxD,UAAM,QAAQ,KAAK,eAAe,SAAS;AAG3C,QAAI;AACJ,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,oBAAY,eAAe,kBAAkB,KAAK;AAClD;AAAA,MACF,KAAK;AACH,oBAAY,WAAW,kBAAkB,KAAK;AAC9C;AAAA,MACF,KAAK;AAAA,MACL;AACE,oBAAY,cAAc,kBAAkB,KAAK;AACjD;AAAA,IACJ;AAEA,WAAO;AAAA,MACL,WAAW;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,WAAmD;AACxE,UAAM,aAAa,KAAK;AACxB,UAAM,kBAAkB,UAAU;AAGlC,UAAM,mBAAmB,aAAa,IAClC,IAAK,kBAAkB,aACvB;AAIJ,QAAI,gBAAgB;AACpB,QAAI,kBAAkB;AAEtB,eAAW,YAAY,WAAW;AAChC,YAAM,gBAAgB,SAAS,QAAQ;AAEvC,uBAAiB,gBAAgB,SAAS;AAE1C,yBAAmB,gBAAgB;AAAA,IACrC;AAEA,UAAM,0BAA0B,gBAAgB,IAC5C,IAAK,kBAAkB,gBACvB;AAEJ,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,kBAAkB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,gBAAgB,CAAC;AAAA,MAC3D,yBAAyB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,uBAAuB,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,aAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,gBAAwB;AAC1B,WAAO,KAAK,SAAS;AAAA,EACvB;AACF;AAKO,SAAS,YAAY,SAA+B;AACzD,SAAO,IAAI,MAAM,OAAO;AAC1B;;;ACvWO,SAAS,SACd,OACA,UAA2B,CAAC,GACT;AACnB,QAAM,EAAE,SAAS,WAAW,eAAe,IAAI,OAAO,aAAa,IAAI;AAEvE,QAAM,QAAQ,YAAY,YAAY;AACtC,QAAM,YAAY,KAAK;AAEvB,SAAO,MAAM,UAAU,QAAQ,YAAY;AAC7C;AASO,SAAS,aACd,MACA,UAA2B,CAAC,GACT;AACnB,QAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,SAAO,SAAS,OAAO,OAAO;AAChC;;;APrDA;AAQA,IAAM,gBAAY,8BAAQ,+BAAc,YAAY,GAAG,CAAC;AACxD,IAAM,MAAM,KAAK,UAAM,iCAAa,uBAAK,WAAW,MAAM,cAAc,GAAG,OAAO,CAAC;AACnF,IAAM,UAAU,IAAI;AAEpB,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6Cb,SAAS,eAA2B;AAClC,QAAM,EAAE,QAAQ,YAAY,QAAI,4BAAU;AAAA,IACxC,SAAS;AAAA,MACP,QAAQ,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,UAAU;AAAA,MACzD,QAAQ,EAAE,MAAM,UAAU,OAAO,IAAI;AAAA,MACrC,OAAO,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,IAAI;AAAA,MAClD,WAAW,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,MAAM;AAAA,MACxD,gBAAgB,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,MAAM;AAAA,MAC7D,gBAAgB,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,OAAO;AAAA,MAC9D,iBAAiB,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,KAAK;AAAA,MAC7D,MAAM,EAAE,MAAM,WAAW,OAAO,KAAK,SAAS,MAAM;AAAA,MACpD,SAAS,EAAE,MAAM,WAAW,OAAO,KAAK,SAAS,MAAM;AAAA,IACzD;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,SAAS,OAAO;AACtB,MAAI,CAAC,CAAC,WAAW,YAAY,MAAM,EAAE,SAAS,MAAM,GAAG;AACrD,YAAQ;AAAA,MACN,0BAA0B,MAAM;AAAA,IAClC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,SAAS,OAAO,OAAiB,EAAE;AACjD,QAAM,YAAY,WAAW,OAAO,SAAmB;AACvD,QAAM,cAAc,SAAS,OAAO,cAAc,GAAa,EAAE;AACjE,QAAM,cAAc,SAAS,OAAO,cAAc,GAAa,EAAE;AACjE,QAAM,eAAe,SAAS,OAAO,eAAe,GAAa,EAAE;AAEnE,MAAI,MAAM,KAAK,KAAK,QAAQ,GAAG;AAC7B,YAAQ;AAAA,MACN,yBAAyB,OAAO,KAAK;AAAA,IACvC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,SAAS,KAAK,YAAY,KAAK,YAAY,GAAG;AACtD,YAAQ;AAAA,MACN,6BAA6B,OAAO,SAAS;AAAA,IAC/C;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,WAAW,KAAK,cAAc,GAAG;AACzC,YAAQ;AAAA,MACN,gCAAgC,OAAO,cAAc,CAAC;AAAA,IACxD;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,WAAW,KAAK,cAAc,GAAG;AACzC,YAAQ;AAAA,MACN,gCAAgC,OAAO,cAAc,CAAC;AAAA,IACxD;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,YAAY,KAAK,eAAe,GAAG;AAC3C,YAAQ;AAAA,MACN,iCAAiC,OAAO,eAAe,CAAC;AAAA,IAC1D;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,OAAO;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,MAAM,OAAO;AAAA,IACb,SAAS,OAAO;AAAA,EAClB;AACF;AAEA,SAAS,UAAU,OAAyB;AAC1C,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,WAAqB,CAAC;AAC5B,eAAW,QAAQ,OAAO;AACxB,UAAI,KAAC,2BAAW,IAAI,GAAG;AACrB,gBAAQ,MAAM,0BAA0B,IAAI,EAAE;AAC9C,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,eAAS,SAAK,6BAAa,MAAM,OAAO,CAAC;AAAA,IAC3C;AACA,WAAO,SAAS,KAAK,IAAI;AAAA,EAC3B;AAGA,MAAI,QAAQ,MAAM,OAAO;AACvB,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,MAAM,6CAA6C;AAC3D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,aAAO,6BAAa,GAAG,OAAO;AAChC;AAEA,SAAS,OAAa;AACpB,QAAM,OAAO,aAAa;AAE1B,MAAI,KAAK,MAAM;AACb,YAAQ,IAAI,IAAI;AAChB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,KAAK,SAAS;AAChB,YAAQ,IAAI,YAAY,OAAO,EAAE;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,UAAU,KAAK,KAAK;AAElC,MAAI,CAAC,MAAM,KAAK,GAAG;AACjB,YAAQ,MAAM,oBAAoB;AAClC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAA2B;AAAA,IAC/B,QAAQ,KAAK;AAAA,IACb,cAAc,KAAK;AAAA,IACnB,OAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK;AAAA,MACnB,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,SAAS,aAAa,OAAO,OAAO;AAE1C,QAAM,SACJ,KAAK,WAAW,SACZ,KAAK;AAAA,IACH,EAAE,WAAW,OAAO,WAAW,OAAO,OAAO,MAAM;AAAA,IACnD;AAAA,IACA;AAAA,EACF,IACA,OAAO;AAEb,MAAI,KAAK,QAAQ;AACf,sCAAc,KAAK,QAAQ,QAAQ,OAAO;AAC1C,YAAQ,MAAM,qBAAqB,KAAK,MAAM,EAAE;AAAA,EAClD,OAAO;AACL,YAAQ,IAAI,MAAM;AAAA,EACpB;AACF;AAEA,KAAK;","names":[]}
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
compressText
|
|
4
|
+
} from "./chunk-JRS36ZWP.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { parseArgs } from "util";
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
13
|
+
var VERSION = pkg.version;
|
|
14
|
+
var HELP = `
|
|
15
|
+
logpare - Semantic log compression for LLM context windows
|
|
16
|
+
|
|
17
|
+
USAGE:
|
|
18
|
+
logpare [options] [file...]
|
|
19
|
+
cat logs.txt | logpare [options]
|
|
20
|
+
|
|
21
|
+
OPTIONS:
|
|
22
|
+
-f, --format <fmt> Output format: summary, detailed, json (default: summary)
|
|
23
|
+
-o, --output <file> Write output to file instead of stdout
|
|
24
|
+
-d, --depth <n> Parse tree depth (default: 4)
|
|
25
|
+
-t, --threshold <n> Similarity threshold 0.0-1.0 (default: 0.4)
|
|
26
|
+
-c, --max-children <n> Max children per node (default: 100)
|
|
27
|
+
-m, --max-clusters <n> Max total clusters (default: 1000)
|
|
28
|
+
-n, --max-templates <n> Max templates in output (default: 50)
|
|
29
|
+
-h, --help Show this help message
|
|
30
|
+
-v, --version Show version number
|
|
31
|
+
|
|
32
|
+
EXAMPLES:
|
|
33
|
+
logpare server.log
|
|
34
|
+
cat /var/log/syslog | logpare --format json
|
|
35
|
+
logpare --depth 5 --threshold 0.5 app.log -o templates.txt
|
|
36
|
+
logpare access.log error.log --format detailed
|
|
37
|
+
|
|
38
|
+
DESCRIPTION:
|
|
39
|
+
logpare uses the Drain algorithm to extract templates from repetitive log
|
|
40
|
+
data, achieving 60-90% token reduction while preserving diagnostic information.
|
|
41
|
+
This is useful for fitting more log context into LLM prompts.
|
|
42
|
+
|
|
43
|
+
For more information: https://github.com/logpare/logpare
|
|
44
|
+
`;
|
|
45
|
+
function parseCliArgs() {
|
|
46
|
+
const { values, positionals } = parseArgs({
|
|
47
|
+
options: {
|
|
48
|
+
format: { type: "string", short: "f", default: "summary" },
|
|
49
|
+
output: { type: "string", short: "o" },
|
|
50
|
+
depth: { type: "string", short: "d", default: "4" },
|
|
51
|
+
threshold: { type: "string", short: "t", default: "0.4" },
|
|
52
|
+
"max-children": { type: "string", short: "c", default: "100" },
|
|
53
|
+
"max-clusters": { type: "string", short: "m", default: "1000" },
|
|
54
|
+
"max-templates": { type: "string", short: "n", default: "50" },
|
|
55
|
+
help: { type: "boolean", short: "h", default: false },
|
|
56
|
+
version: { type: "boolean", short: "v", default: false }
|
|
57
|
+
},
|
|
58
|
+
allowPositionals: true
|
|
59
|
+
});
|
|
60
|
+
const format = values.format;
|
|
61
|
+
if (!["summary", "detailed", "json"].includes(format)) {
|
|
62
|
+
console.error(
|
|
63
|
+
`Error: Invalid format "${format}". Use: summary, detailed, json`
|
|
64
|
+
);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const depth = parseInt(values.depth, 10);
|
|
68
|
+
const threshold = parseFloat(values.threshold);
|
|
69
|
+
const maxChildren = parseInt(values["max-children"], 10);
|
|
70
|
+
const maxClusters = parseInt(values["max-clusters"], 10);
|
|
71
|
+
const maxTemplates = parseInt(values["max-templates"], 10);
|
|
72
|
+
if (isNaN(depth) || depth < 1) {
|
|
73
|
+
console.error(
|
|
74
|
+
`Error: Invalid depth "${values.depth}". Must be a positive integer.`
|
|
75
|
+
);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
if (isNaN(threshold) || threshold < 0 || threshold > 1) {
|
|
79
|
+
console.error(
|
|
80
|
+
`Error: Invalid threshold "${values.threshold}". Must be a number between 0.0 and 1.0.`
|
|
81
|
+
);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
if (isNaN(maxChildren) || maxChildren < 1) {
|
|
85
|
+
console.error(
|
|
86
|
+
`Error: Invalid max-children "${values["max-children"]}". Must be a positive integer.`
|
|
87
|
+
);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
if (isNaN(maxClusters) || maxClusters < 1) {
|
|
91
|
+
console.error(
|
|
92
|
+
`Error: Invalid max-clusters "${values["max-clusters"]}". Must be a positive integer.`
|
|
93
|
+
);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
if (isNaN(maxTemplates) || maxTemplates < 1) {
|
|
97
|
+
console.error(
|
|
98
|
+
`Error: Invalid max-templates "${values["max-templates"]}". Must be a positive integer.`
|
|
99
|
+
);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
format,
|
|
104
|
+
output: values.output,
|
|
105
|
+
depth,
|
|
106
|
+
threshold,
|
|
107
|
+
maxChildren,
|
|
108
|
+
maxClusters,
|
|
109
|
+
maxTemplates,
|
|
110
|
+
files: positionals,
|
|
111
|
+
help: values.help,
|
|
112
|
+
version: values.version
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function readInput(files) {
|
|
116
|
+
if (files.length > 0) {
|
|
117
|
+
const contents = [];
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
if (!existsSync(file)) {
|
|
120
|
+
console.error(`Error: File not found: ${file}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
contents.push(readFileSync(file, "utf-8"));
|
|
124
|
+
}
|
|
125
|
+
return contents.join("\n");
|
|
126
|
+
}
|
|
127
|
+
if (process.stdin.isTTY) {
|
|
128
|
+
console.error(
|
|
129
|
+
"Error: No input provided. Provide file(s) or pipe input via stdin."
|
|
130
|
+
);
|
|
131
|
+
console.error('Run "logpare --help" for usage information.');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
return readFileSync(0, "utf-8");
|
|
135
|
+
}
|
|
136
|
+
function main() {
|
|
137
|
+
const args = parseCliArgs();
|
|
138
|
+
if (args.help) {
|
|
139
|
+
console.log(HELP);
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
if (args.version) {
|
|
143
|
+
console.log(`logpare v${VERSION}`);
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
const input = readInput(args.files);
|
|
147
|
+
if (!input.trim()) {
|
|
148
|
+
console.error("Error: Empty input");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
const options = {
|
|
152
|
+
format: args.format,
|
|
153
|
+
maxTemplates: args.maxTemplates,
|
|
154
|
+
drain: {
|
|
155
|
+
depth: args.depth,
|
|
156
|
+
simThreshold: args.threshold,
|
|
157
|
+
maxChildren: args.maxChildren,
|
|
158
|
+
maxClusters: args.maxClusters
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const result = compressText(input, options);
|
|
162
|
+
const output = args.format === "json" ? JSON.stringify(
|
|
163
|
+
{ templates: result.templates, stats: result.stats },
|
|
164
|
+
null,
|
|
165
|
+
2
|
|
166
|
+
) : result.formatted;
|
|
167
|
+
if (args.output) {
|
|
168
|
+
writeFileSync(args.output, output, "utf-8");
|
|
169
|
+
console.error(`Output written to ${args.output}`);
|
|
170
|
+
} else {
|
|
171
|
+
console.log(output);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
main();
|
|
175
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport { readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { compressText } from \"./api.js\";\nimport type { CompressOptions, OutputFormat } from \"./types.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\"));\nconst VERSION = pkg.version as string;\n\nconst HELP = `\nlogpare - Semantic log compression for LLM context windows\n\nUSAGE:\n logpare [options] [file...]\n cat logs.txt | logpare [options]\n\nOPTIONS:\n -f, --format <fmt> Output format: summary, detailed, json (default: summary)\n -o, --output <file> Write output to file instead of stdout\n -d, --depth <n> Parse tree depth (default: 4)\n -t, --threshold <n> Similarity threshold 0.0-1.0 (default: 0.4)\n -c, --max-children <n> Max children per node (default: 100)\n -m, --max-clusters <n> Max total clusters (default: 1000)\n -n, --max-templates <n> Max templates in output (default: 50)\n -h, --help Show this help message\n -v, --version Show version number\n\nEXAMPLES:\n logpare server.log\n cat /var/log/syslog | logpare --format json\n logpare --depth 5 --threshold 0.5 app.log -o templates.txt\n logpare access.log error.log --format detailed\n\nDESCRIPTION:\n logpare uses the Drain algorithm to extract templates from repetitive log\n data, achieving 60-90% token reduction while preserving diagnostic information.\n This is useful for fitting more log context into LLM prompts.\n\n For more information: https://github.com/logpare/logpare\n`;\n\ninterface ParsedArgs {\n format: OutputFormat;\n output: string | undefined;\n depth: number;\n threshold: number;\n maxChildren: number;\n maxClusters: number;\n maxTemplates: number;\n files: string[];\n help: boolean;\n version: boolean;\n}\n\nfunction parseCliArgs(): ParsedArgs {\n const { values, positionals } = parseArgs({\n options: {\n format: { type: \"string\", short: \"f\", default: \"summary\" },\n output: { type: \"string\", short: \"o\" },\n depth: { type: \"string\", short: \"d\", default: \"4\" },\n threshold: { type: \"string\", short: \"t\", default: \"0.4\" },\n \"max-children\": { type: \"string\", short: \"c\", default: \"100\" },\n \"max-clusters\": { type: \"string\", short: \"m\", default: \"1000\" },\n \"max-templates\": { type: \"string\", short: \"n\", default: \"50\" },\n help: { type: \"boolean\", short: \"h\", default: false },\n version: { type: \"boolean\", short: \"v\", default: false },\n },\n allowPositionals: true,\n });\n\n const format = values.format as string;\n if (![\"summary\", \"detailed\", \"json\"].includes(format)) {\n console.error(\n `Error: Invalid format \"${format}\". Use: summary, detailed, json`\n );\n process.exit(1);\n }\n\n const depth = parseInt(values.depth as string, 10);\n const threshold = parseFloat(values.threshold as string);\n const maxChildren = parseInt(values[\"max-children\"] as string, 10);\n const maxClusters = parseInt(values[\"max-clusters\"] as string, 10);\n const maxTemplates = parseInt(values[\"max-templates\"] as string, 10);\n\n if (isNaN(depth) || depth < 1) {\n console.error(\n `Error: Invalid depth \"${values.depth}\". Must be a positive integer.`\n );\n process.exit(1);\n }\n\n if (isNaN(threshold) || threshold < 0 || threshold > 1) {\n console.error(\n `Error: Invalid threshold \"${values.threshold}\". Must be a number between 0.0 and 1.0.`\n );\n process.exit(1);\n }\n\n if (isNaN(maxChildren) || maxChildren < 1) {\n console.error(\n `Error: Invalid max-children \"${values[\"max-children\"]}\". Must be a positive integer.`\n );\n process.exit(1);\n }\n\n if (isNaN(maxClusters) || maxClusters < 1) {\n console.error(\n `Error: Invalid max-clusters \"${values[\"max-clusters\"]}\". Must be a positive integer.`\n );\n process.exit(1);\n }\n\n if (isNaN(maxTemplates) || maxTemplates < 1) {\n console.error(\n `Error: Invalid max-templates \"${values[\"max-templates\"]}\". Must be a positive integer.`\n );\n process.exit(1);\n }\n\n return {\n format: format as OutputFormat,\n output: values.output as string | undefined,\n depth,\n threshold,\n maxChildren,\n maxClusters,\n maxTemplates,\n files: positionals,\n help: values.help as boolean,\n version: values.version as boolean,\n };\n}\n\nfunction readInput(files: string[]): string {\n if (files.length > 0) {\n const contents: string[] = [];\n for (const file of files) {\n if (!existsSync(file)) {\n console.error(`Error: File not found: ${file}`);\n process.exit(1);\n }\n contents.push(readFileSync(file, \"utf-8\"));\n }\n return contents.join(\"\\n\");\n }\n\n // Read from stdin\n if (process.stdin.isTTY) {\n console.error(\n \"Error: No input provided. Provide file(s) or pipe input via stdin.\"\n );\n console.error('Run \"logpare --help\" for usage information.');\n process.exit(1);\n }\n\n return readFileSync(0, \"utf-8\");\n}\n\nfunction main(): void {\n const args = parseCliArgs();\n\n if (args.help) {\n console.log(HELP);\n process.exit(0);\n }\n\n if (args.version) {\n console.log(`logpare v${VERSION}`);\n process.exit(0);\n }\n\n const input = readInput(args.files);\n\n if (!input.trim()) {\n console.error(\"Error: Empty input\");\n process.exit(1);\n }\n\n const options: CompressOptions = {\n format: args.format,\n maxTemplates: args.maxTemplates,\n drain: {\n depth: args.depth,\n simThreshold: args.threshold,\n maxChildren: args.maxChildren,\n maxClusters: args.maxClusters,\n },\n };\n\n const result = compressText(input, options);\n\n const output =\n args.format === \"json\"\n ? JSON.stringify(\n { templates: result.templates, stats: result.stats },\n null,\n 2\n )\n : result.formatted;\n\n if (args.output) {\n writeFileSync(args.output, output, \"utf-8\");\n console.error(`Output written to ${args.output}`);\n } else {\n console.log(output);\n }\n}\n\nmain();\n"],"mappings":";;;;;;AACA,SAAS,iBAAiB;AAC1B,SAAS,cAAc,eAAe,kBAAkB;AACxD,SAAS,qBAAqB;AAC9B,SAAS,SAAS,YAAY;AAI9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,IAAM,MAAM,KAAK,MAAM,aAAa,KAAK,WAAW,MAAM,cAAc,GAAG,OAAO,CAAC;AACnF,IAAM,UAAU,IAAI;AAEpB,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6Cb,SAAS,eAA2B;AAClC,QAAM,EAAE,QAAQ,YAAY,IAAI,UAAU;AAAA,IACxC,SAAS;AAAA,MACP,QAAQ,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,UAAU;AAAA,MACzD,QAAQ,EAAE,MAAM,UAAU,OAAO,IAAI;AAAA,MACrC,OAAO,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,IAAI;AAAA,MAClD,WAAW,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,MAAM;AAAA,MACxD,gBAAgB,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,MAAM;AAAA,MAC7D,gBAAgB,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,OAAO;AAAA,MAC9D,iBAAiB,EAAE,MAAM,UAAU,OAAO,KAAK,SAAS,KAAK;AAAA,MAC7D,MAAM,EAAE,MAAM,WAAW,OAAO,KAAK,SAAS,MAAM;AAAA,MACpD,SAAS,EAAE,MAAM,WAAW,OAAO,KAAK,SAAS,MAAM;AAAA,IACzD;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AAED,QAAM,SAAS,OAAO;AACtB,MAAI,CAAC,CAAC,WAAW,YAAY,MAAM,EAAE,SAAS,MAAM,GAAG;AACrD,YAAQ;AAAA,MACN,0BAA0B,MAAM;AAAA,IAClC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,SAAS,OAAO,OAAiB,EAAE;AACjD,QAAM,YAAY,WAAW,OAAO,SAAmB;AACvD,QAAM,cAAc,SAAS,OAAO,cAAc,GAAa,EAAE;AACjE,QAAM,cAAc,SAAS,OAAO,cAAc,GAAa,EAAE;AACjE,QAAM,eAAe,SAAS,OAAO,eAAe,GAAa,EAAE;AAEnE,MAAI,MAAM,KAAK,KAAK,QAAQ,GAAG;AAC7B,YAAQ;AAAA,MACN,yBAAyB,OAAO,KAAK;AAAA,IACvC;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,SAAS,KAAK,YAAY,KAAK,YAAY,GAAG;AACtD,YAAQ;AAAA,MACN,6BAA6B,OAAO,SAAS;AAAA,IAC/C;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,WAAW,KAAK,cAAc,GAAG;AACzC,YAAQ;AAAA,MACN,gCAAgC,OAAO,cAAc,CAAC;AAAA,IACxD;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,WAAW,KAAK,cAAc,GAAG;AACzC,YAAQ;AAAA,MACN,gCAAgC,OAAO,cAAc,CAAC;AAAA,IACxD;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,MAAM,YAAY,KAAK,eAAe,GAAG;AAC3C,YAAQ;AAAA,MACN,iCAAiC,OAAO,eAAe,CAAC;AAAA,IAC1D;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,OAAO;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,MAAM,OAAO;AAAA,IACb,SAAS,OAAO;AAAA,EAClB;AACF;AAEA,SAAS,UAAU,OAAyB;AAC1C,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,WAAqB,CAAC;AAC5B,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,WAAW,IAAI,GAAG;AACrB,gBAAQ,MAAM,0BAA0B,IAAI,EAAE;AAC9C,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,eAAS,KAAK,aAAa,MAAM,OAAO,CAAC;AAAA,IAC3C;AACA,WAAO,SAAS,KAAK,IAAI;AAAA,EAC3B;AAGA,MAAI,QAAQ,MAAM,OAAO;AACvB,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,MAAM,6CAA6C;AAC3D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAO,aAAa,GAAG,OAAO;AAChC;AAEA,SAAS,OAAa;AACpB,QAAM,OAAO,aAAa;AAE1B,MAAI,KAAK,MAAM;AACb,YAAQ,IAAI,IAAI;AAChB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,KAAK,SAAS;AAChB,YAAQ,IAAI,YAAY,OAAO,EAAE;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,UAAU,KAAK,KAAK;AAElC,MAAI,CAAC,MAAM,KAAK,GAAG;AACjB,YAAQ,MAAM,oBAAoB;AAClC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAA2B;AAAA,IAC/B,QAAQ,KAAK;AAAA,IACb,cAAc,KAAK;AAAA,IACnB,OAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK;AAAA,MACnB,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,SAAS,aAAa,OAAO,OAAO;AAE1C,QAAM,SACJ,KAAK,WAAW,SACZ,KAAK;AAAA,IACH,EAAE,WAAW,OAAO,WAAW,OAAO,OAAO,MAAM;AAAA,IACnD;AAAA,IACA;AAAA,EACF,IACA,OAAO;AAEb,MAAI,KAAK,QAAQ;AACf,kBAAc,KAAK,QAAQ,QAAQ,OAAO;AAC1C,YAAQ,MAAM,qBAAqB,KAAK,MAAM,EAAE;AAAA,EAClD,OAAO;AACL,YAAQ,IAAI,MAAM;AAAA,EACpB;AACF;AAEA,KAAK;","names":[]}
|