| "use strict"; | ||
| //#region rolldown:runtime | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { | ||
| value: mod, | ||
| enumerable: true | ||
| }) : target, mod)); | ||
| //#endregion | ||
| const node_path = __toESM(require("node:path")); | ||
| const node_fs_promises = __toESM(require("node:fs/promises")); | ||
| //#region src/tokenizer.ts | ||
| const VALID_ENCODINGS = new Set([ | ||
| "o200k_base", | ||
| "o200k_harmony", | ||
| "cl100k_base", | ||
| "p50k_base", | ||
| "p50k_edit", | ||
| "r50k_base" | ||
| ]); | ||
| function resolveEncoding(encoding, model) { | ||
| if (encoding && model) { | ||
| throw new Error("Cannot specify both --encoding and --model"); | ||
| } | ||
| if (encoding) { | ||
| if (!VALID_ENCODINGS.has(encoding)) { | ||
| throw new Error(`Unknown encoding: ${encoding}. Valid encodings: ${[...VALID_ENCODINGS].join(", ")}`); | ||
| } | ||
| return encoding; | ||
| } | ||
| return "o200k_base"; | ||
| } | ||
| async function getTokenCounter(encoding, model) { | ||
| if (model) { | ||
| if (encoding) { | ||
| throw new Error("Cannot specify both --encoding and --model"); | ||
| } | ||
| const mod$1 = await import(`gpt-tokenizer/model/${model}`); | ||
| return mod$1.countTokens; | ||
| } | ||
| const resolvedEncoding = resolveEncoding(encoding); | ||
| if (resolvedEncoding === "o200k_base") { | ||
| const { countTokens } = await import("gpt-tokenizer"); | ||
| return countTokens; | ||
| } | ||
| const mod = await import(`gpt-tokenizer/encoding/${resolvedEncoding}`); | ||
| return mod.countTokens; | ||
| } | ||
| //#endregion | ||
| //#region src/walker.ts | ||
| const DEFAULT_IGNORE = new Set(["node_modules", ".git"]); | ||
| const BINARY_CHECK_BYTES = 8192; | ||
| const GITIGNORE_FILE = ".gitignore"; | ||
| function isBinaryBuffer(buffer) { | ||
| const length = Math.min(buffer.length, BINARY_CHECK_BYTES); | ||
| for (let i = 0; i < length; i++) { | ||
| if (buffer[i] === 0) return true; | ||
| } | ||
| return false; | ||
| } | ||
| function matchesExclude(name, excludePatterns) { | ||
| for (const pattern of excludePatterns) { | ||
| if (pattern === name) return true; | ||
| if (pattern.startsWith("*.") && name.endsWith(pattern.slice(1))) return true; | ||
| if (pattern.endsWith("/*") && name === pattern.slice(0, -2)) return true; | ||
| } | ||
| return false; | ||
| } | ||
| function toPosixPath(value) { | ||
| return node_path.sep === "/" ? value : value.split(node_path.sep).join("/"); | ||
| } | ||
| function parseGitIgnoreLine(rawLine) { | ||
| let pattern = rawLine.trim(); | ||
| if (pattern === "" || pattern.startsWith("#")) return undefined; | ||
| if (pattern.startsWith("\\#") || pattern.startsWith("\\!")) { | ||
| pattern = pattern.slice(1); | ||
| } | ||
| let negated = false; | ||
| if (pattern.startsWith("!")) { | ||
| negated = true; | ||
| pattern = pattern.slice(1); | ||
| } | ||
| pattern = pattern.trim(); | ||
| if (pattern === "") return undefined; | ||
| const anchored = pattern.startsWith("/"); | ||
| pattern = pattern.replace(/^\/+/, ""); | ||
| const directoryOnly = pattern.endsWith("/"); | ||
| pattern = pattern.replace(/\/+$/, ""); | ||
| if (pattern === "") return undefined; | ||
| return { | ||
| pattern, | ||
| negated, | ||
| directoryOnly, | ||
| anchored, | ||
| hasSlash: pattern.includes("/") | ||
| }; | ||
| } | ||
| function parseGitIgnore(content) { | ||
| const rules = []; | ||
| for (const line of content.split(/\r?\n/)) { | ||
| const rule = parseGitIgnoreLine(line); | ||
| if (rule) rules.push(rule); | ||
| } | ||
| return rules; | ||
| } | ||
| async function loadGitIgnoreContext(targetPath) { | ||
| try { | ||
| const content = await (0, node_fs_promises.readFile)((0, node_path.join)(targetPath, GITIGNORE_FILE), "utf-8"); | ||
| const rules = parseGitIgnore(content); | ||
| return rules.length > 0 ? { | ||
| rootPath: targetPath, | ||
| rules | ||
| } : undefined; | ||
| } catch (err) { | ||
| if (err instanceof Error && "code" in err && err.code === "ENOENT") { | ||
| return undefined; | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| function matchesGitIgnorePattern(relativePath, basename, isDirectory, rule) { | ||
| if (rule.directoryOnly && !isDirectory) return false; | ||
| if (!rule.anchored && !rule.hasSlash) { | ||
| return (0, node_path.matchesGlob)(basename, rule.pattern); | ||
| } | ||
| if ((0, node_path.matchesGlob)(relativePath, rule.pattern)) return true; | ||
| if (isDirectory) { | ||
| return (0, node_path.matchesGlob)(`${relativePath}/`, `${rule.pattern}/**`); | ||
| } | ||
| return false; | ||
| } | ||
| function matchesGitIgnore(childPath, entryName, isDirectory, ignoreContexts) { | ||
| let ignored = false; | ||
| for (const context of ignoreContexts) { | ||
| const relativePath = toPosixPath((0, node_path.relative)(context.rootPath, childPath)); | ||
| if (relativePath === "" || relativePath.startsWith("../")) continue; | ||
| for (const rule of context.rules) { | ||
| if (matchesGitIgnorePattern(relativePath, entryName, isDirectory, rule)) { | ||
| ignored = !rule.negated; | ||
| } | ||
| } | ||
| } | ||
| return ignored; | ||
| } | ||
| async function countFileTokens(filePath, countFn) { | ||
| const buffer = await (0, node_fs_promises.readFile)(filePath); | ||
| if (buffer.length === 0) return 0; | ||
| if (isBinaryBuffer(buffer)) return 0; | ||
| const text = buffer.toString("utf-8"); | ||
| try { | ||
| return countFn(text); | ||
| } catch { | ||
| return 0; | ||
| } | ||
| } | ||
| async function walkPath(targetPath, basePath, options, currentDepth = 0, ignoreContexts = []) { | ||
| const info = await (0, node_fs_promises.stat)(targetPath); | ||
| if (info.isFile()) { | ||
| const tokens = await countFileTokens(targetPath, options.countFn); | ||
| return { | ||
| path: (0, node_path.relative)(basePath, targetPath) || targetPath, | ||
| tokens, | ||
| isFile: true | ||
| }; | ||
| } | ||
| if (!info.isDirectory()) { | ||
| return { | ||
| path: (0, node_path.relative)(basePath, targetPath) || targetPath, | ||
| tokens: 0, | ||
| isFile: false | ||
| }; | ||
| } | ||
| const entries = await (0, node_fs_promises.readdir)(targetPath, { withFileTypes: true }); | ||
| const children = []; | ||
| let totalTokens = 0; | ||
| const smartIgnore = options.smartIgnore ?? true; | ||
| const currentIgnoreContexts = smartIgnore ? [...ignoreContexts, ...await loadGitIgnoreContext(targetPath).then((context) => context ? [context] : [])] : ignoreContexts; | ||
| for (const entry of entries) { | ||
| const childPath = (0, node_path.join)(targetPath, entry.name); | ||
| const isDirectory = entry.isDirectory(); | ||
| if (smartIgnore && DEFAULT_IGNORE.has(entry.name)) continue; | ||
| if (smartIgnore && matchesGitIgnore(childPath, entry.name, isDirectory, currentIgnoreContexts)) continue; | ||
| if (matchesExclude(entry.name, options.exclude)) continue; | ||
| if (entry.isFile()) { | ||
| const tokens = await countFileTokens(childPath, options.countFn); | ||
| totalTokens += tokens; | ||
| children.push({ | ||
| path: (0, node_path.relative)(basePath, childPath), | ||
| tokens, | ||
| isFile: true | ||
| }); | ||
| } else if (entry.isDirectory()) { | ||
| if (options.maxDepth !== undefined && currentDepth >= options.maxDepth) { | ||
| continue; | ||
| } | ||
| const childResult = await walkPath(childPath, basePath, options, currentDepth + 1, currentIgnoreContexts); | ||
| totalTokens += childResult.tokens; | ||
| children.push(childResult); | ||
| } | ||
| } | ||
| children.sort((a, b) => a.path.localeCompare(b.path)); | ||
| return { | ||
| path: (0, node_path.relative)(basePath, targetPath) || targetPath, | ||
| tokens: totalTokens, | ||
| isFile: false, | ||
| children | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/formatter.ts | ||
| function humanReadableTokens(count) { | ||
| if (count >= 1e6) { | ||
| const value = count / 1e6; | ||
| return value % 1 === 0 ? `${value}M` : `${value.toFixed(1)}M`; | ||
| } | ||
| if (count >= 1e3) { | ||
| const value = count / 1e3; | ||
| return value % 1 === 0 ? `${value}K` : `${value.toFixed(1)}K`; | ||
| } | ||
| return count.toString(); | ||
| } | ||
| function formatLine(tokens, path, humanReadable) { | ||
| const count = humanReadable ? humanReadableTokens(tokens) : tokens.toString(); | ||
| return `${count}\t${path}`; | ||
| } | ||
| function collectTextLines(result, options, lines, depth = 0) { | ||
| if (result.isFile) { | ||
| if (options.all) { | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| return; | ||
| } | ||
| if (result.children) { | ||
| for (const child of result.children) { | ||
| if (options.maxDepth !== undefined && depth >= options.maxDepth) break; | ||
| collectTextLines(child, options, lines, depth + 1); | ||
| } | ||
| } | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| function formatText(results, options) { | ||
| const lines = []; | ||
| if (options.summarize) { | ||
| for (const result of results) { | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| } else { | ||
| for (const result of results) { | ||
| collectTextLines(result, options, lines); | ||
| } | ||
| } | ||
| if (options.total) { | ||
| const grandTotal = results.reduce((sum, r) => sum + r.tokens, 0); | ||
| lines.push(formatLine(grandTotal, "total", options.humanReadable)); | ||
| } | ||
| return lines.join("\n"); | ||
| } | ||
| function collectJsonEntries(result, entries) { | ||
| if (result.isFile) { | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: "file" | ||
| }); | ||
| return; | ||
| } | ||
| if (result.children) { | ||
| for (const child of result.children) { | ||
| collectJsonEntries(child, entries); | ||
| } | ||
| } | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: "directory", | ||
| children: result.children?.map((c) => c.path) | ||
| }); | ||
| } | ||
| function formatJson(results, options, version) { | ||
| const entries = []; | ||
| if (options.summarize) { | ||
| for (const result of results) { | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: result.isFile ? "file" : "directory" | ||
| }); | ||
| } | ||
| } else { | ||
| for (const result of results) { | ||
| collectJsonEntries(result, entries); | ||
| } | ||
| } | ||
| const output = { | ||
| version, | ||
| encoding: options.encoding, | ||
| timestamp: new Date().toISOString(), | ||
| results: entries | ||
| }; | ||
| if (options.total || options.summarize) { | ||
| output.total = results.reduce((sum, r) => sum + r.tokens, 0); | ||
| } | ||
| return JSON.stringify(output, null, 2); | ||
| } | ||
| //#endregion | ||
| //#region src/main.ts | ||
| async function walkAndCount(paths, options) { | ||
| const countFn = await getTokenCounter(options.encoding, options.model); | ||
| const results = []; | ||
| for (const targetPath of paths) { | ||
| const absPath = (0, node_path.resolve)(targetPath); | ||
| const result = await walkPath(absPath, (0, node_path.resolve)(absPath, ".."), { | ||
| countFn, | ||
| all: options.all, | ||
| maxDepth: options.maxDepth, | ||
| exclude: options.exclude, | ||
| smartIgnore: options.smartIgnore | ||
| }); | ||
| results.push(result); | ||
| } | ||
| return results; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, '__toESM', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return __toESM; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'countFileTokens', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return countFileTokens; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'formatJson', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return formatJson; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'formatText', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return formatText; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'getTokenCounter', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return getTokenCounter; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'resolveEncoding', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return resolveEncoding; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'walkAndCount', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return walkAndCount; | ||
| } | ||
| }); |
| import { join, matchesGlob, relative, resolve, sep } from "node:path"; | ||
| import { readFile, readdir, stat } from "node:fs/promises"; | ||
| //#region src/tokenizer.ts | ||
| const VALID_ENCODINGS = new Set([ | ||
| "o200k_base", | ||
| "o200k_harmony", | ||
| "cl100k_base", | ||
| "p50k_base", | ||
| "p50k_edit", | ||
| "r50k_base" | ||
| ]); | ||
| function resolveEncoding(encoding, model) { | ||
| if (encoding && model) { | ||
| throw new Error("Cannot specify both --encoding and --model"); | ||
| } | ||
| if (encoding) { | ||
| if (!VALID_ENCODINGS.has(encoding)) { | ||
| throw new Error(`Unknown encoding: ${encoding}. Valid encodings: ${[...VALID_ENCODINGS].join(", ")}`); | ||
| } | ||
| return encoding; | ||
| } | ||
| return "o200k_base"; | ||
| } | ||
| async function getTokenCounter(encoding, model) { | ||
| if (model) { | ||
| if (encoding) { | ||
| throw new Error("Cannot specify both --encoding and --model"); | ||
| } | ||
| const mod$1 = await import(`gpt-tokenizer/model/${model}`); | ||
| return mod$1.countTokens; | ||
| } | ||
| const resolvedEncoding = resolveEncoding(encoding); | ||
| if (resolvedEncoding === "o200k_base") { | ||
| const { countTokens } = await import("gpt-tokenizer"); | ||
| return countTokens; | ||
| } | ||
| const mod = await import(`gpt-tokenizer/encoding/${resolvedEncoding}`); | ||
| return mod.countTokens; | ||
| } | ||
| //#endregion | ||
| //#region src/walker.ts | ||
| const DEFAULT_IGNORE = new Set(["node_modules", ".git"]); | ||
| const BINARY_CHECK_BYTES = 8192; | ||
| const GITIGNORE_FILE = ".gitignore"; | ||
| function isBinaryBuffer(buffer) { | ||
| const length = Math.min(buffer.length, BINARY_CHECK_BYTES); | ||
| for (let i = 0; i < length; i++) { | ||
| if (buffer[i] === 0) return true; | ||
| } | ||
| return false; | ||
| } | ||
| function matchesExclude(name, excludePatterns) { | ||
| for (const pattern of excludePatterns) { | ||
| if (pattern === name) return true; | ||
| if (pattern.startsWith("*.") && name.endsWith(pattern.slice(1))) return true; | ||
| if (pattern.endsWith("/*") && name === pattern.slice(0, -2)) return true; | ||
| } | ||
| return false; | ||
| } | ||
| function toPosixPath(value) { | ||
| return sep === "/" ? value : value.split(sep).join("/"); | ||
| } | ||
| function parseGitIgnoreLine(rawLine) { | ||
| let pattern = rawLine.trim(); | ||
| if (pattern === "" || pattern.startsWith("#")) return undefined; | ||
| if (pattern.startsWith("\\#") || pattern.startsWith("\\!")) { | ||
| pattern = pattern.slice(1); | ||
| } | ||
| let negated = false; | ||
| if (pattern.startsWith("!")) { | ||
| negated = true; | ||
| pattern = pattern.slice(1); | ||
| } | ||
| pattern = pattern.trim(); | ||
| if (pattern === "") return undefined; | ||
| const anchored = pattern.startsWith("/"); | ||
| pattern = pattern.replace(/^\/+/, ""); | ||
| const directoryOnly = pattern.endsWith("/"); | ||
| pattern = pattern.replace(/\/+$/, ""); | ||
| if (pattern === "") return undefined; | ||
| return { | ||
| pattern, | ||
| negated, | ||
| directoryOnly, | ||
| anchored, | ||
| hasSlash: pattern.includes("/") | ||
| }; | ||
| } | ||
| function parseGitIgnore(content) { | ||
| const rules = []; | ||
| for (const line of content.split(/\r?\n/)) { | ||
| const rule = parseGitIgnoreLine(line); | ||
| if (rule) rules.push(rule); | ||
| } | ||
| return rules; | ||
| } | ||
| async function loadGitIgnoreContext(targetPath) { | ||
| try { | ||
| const content = await readFile(join(targetPath, GITIGNORE_FILE), "utf-8"); | ||
| const rules = parseGitIgnore(content); | ||
| return rules.length > 0 ? { | ||
| rootPath: targetPath, | ||
| rules | ||
| } : undefined; | ||
| } catch (err) { | ||
| if (err instanceof Error && "code" in err && err.code === "ENOENT") { | ||
| return undefined; | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| function matchesGitIgnorePattern(relativePath, basename, isDirectory, rule) { | ||
| if (rule.directoryOnly && !isDirectory) return false; | ||
| if (!rule.anchored && !rule.hasSlash) { | ||
| return matchesGlob(basename, rule.pattern); | ||
| } | ||
| if (matchesGlob(relativePath, rule.pattern)) return true; | ||
| if (isDirectory) { | ||
| return matchesGlob(`${relativePath}/`, `${rule.pattern}/**`); | ||
| } | ||
| return false; | ||
| } | ||
| function matchesGitIgnore(childPath, entryName, isDirectory, ignoreContexts) { | ||
| let ignored = false; | ||
| for (const context of ignoreContexts) { | ||
| const relativePath = toPosixPath(relative(context.rootPath, childPath)); | ||
| if (relativePath === "" || relativePath.startsWith("../")) continue; | ||
| for (const rule of context.rules) { | ||
| if (matchesGitIgnorePattern(relativePath, entryName, isDirectory, rule)) { | ||
| ignored = !rule.negated; | ||
| } | ||
| } | ||
| } | ||
| return ignored; | ||
| } | ||
| async function countFileTokens(filePath, countFn) { | ||
| const buffer = await readFile(filePath); | ||
| if (buffer.length === 0) return 0; | ||
| if (isBinaryBuffer(buffer)) return 0; | ||
| const text = buffer.toString("utf-8"); | ||
| try { | ||
| return countFn(text); | ||
| } catch { | ||
| return 0; | ||
| } | ||
| } | ||
| async function walkPath(targetPath, basePath, options, currentDepth = 0, ignoreContexts = []) { | ||
| const info = await stat(targetPath); | ||
| if (info.isFile()) { | ||
| const tokens = await countFileTokens(targetPath, options.countFn); | ||
| return { | ||
| path: relative(basePath, targetPath) || targetPath, | ||
| tokens, | ||
| isFile: true | ||
| }; | ||
| } | ||
| if (!info.isDirectory()) { | ||
| return { | ||
| path: relative(basePath, targetPath) || targetPath, | ||
| tokens: 0, | ||
| isFile: false | ||
| }; | ||
| } | ||
| const entries = await readdir(targetPath, { withFileTypes: true }); | ||
| const children = []; | ||
| let totalTokens = 0; | ||
| const smartIgnore = options.smartIgnore ?? true; | ||
| const currentIgnoreContexts = smartIgnore ? [...ignoreContexts, ...await loadGitIgnoreContext(targetPath).then((context) => context ? [context] : [])] : ignoreContexts; | ||
| for (const entry of entries) { | ||
| const childPath = join(targetPath, entry.name); | ||
| const isDirectory = entry.isDirectory(); | ||
| if (smartIgnore && DEFAULT_IGNORE.has(entry.name)) continue; | ||
| if (smartIgnore && matchesGitIgnore(childPath, entry.name, isDirectory, currentIgnoreContexts)) continue; | ||
| if (matchesExclude(entry.name, options.exclude)) continue; | ||
| if (entry.isFile()) { | ||
| const tokens = await countFileTokens(childPath, options.countFn); | ||
| totalTokens += tokens; | ||
| children.push({ | ||
| path: relative(basePath, childPath), | ||
| tokens, | ||
| isFile: true | ||
| }); | ||
| } else if (entry.isDirectory()) { | ||
| if (options.maxDepth !== undefined && currentDepth >= options.maxDepth) { | ||
| continue; | ||
| } | ||
| const childResult = await walkPath(childPath, basePath, options, currentDepth + 1, currentIgnoreContexts); | ||
| totalTokens += childResult.tokens; | ||
| children.push(childResult); | ||
| } | ||
| } | ||
| children.sort((a, b) => a.path.localeCompare(b.path)); | ||
| return { | ||
| path: relative(basePath, targetPath) || targetPath, | ||
| tokens: totalTokens, | ||
| isFile: false, | ||
| children | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/formatter.ts | ||
| function humanReadableTokens(count) { | ||
| if (count >= 1e6) { | ||
| const value = count / 1e6; | ||
| return value % 1 === 0 ? `${value}M` : `${value.toFixed(1)}M`; | ||
| } | ||
| if (count >= 1e3) { | ||
| const value = count / 1e3; | ||
| return value % 1 === 0 ? `${value}K` : `${value.toFixed(1)}K`; | ||
| } | ||
| return count.toString(); | ||
| } | ||
| function formatLine(tokens, path, humanReadable) { | ||
| const count = humanReadable ? humanReadableTokens(tokens) : tokens.toString(); | ||
| return `${count}\t${path}`; | ||
| } | ||
| function collectTextLines(result, options, lines, depth = 0) { | ||
| if (result.isFile) { | ||
| if (options.all) { | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| return; | ||
| } | ||
| if (result.children) { | ||
| for (const child of result.children) { | ||
| if (options.maxDepth !== undefined && depth >= options.maxDepth) break; | ||
| collectTextLines(child, options, lines, depth + 1); | ||
| } | ||
| } | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| function formatText(results, options) { | ||
| const lines = []; | ||
| if (options.summarize) { | ||
| for (const result of results) { | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| } else { | ||
| for (const result of results) { | ||
| collectTextLines(result, options, lines); | ||
| } | ||
| } | ||
| if (options.total) { | ||
| const grandTotal = results.reduce((sum, r) => sum + r.tokens, 0); | ||
| lines.push(formatLine(grandTotal, "total", options.humanReadable)); | ||
| } | ||
| return lines.join("\n"); | ||
| } | ||
| function collectJsonEntries(result, entries) { | ||
| if (result.isFile) { | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: "file" | ||
| }); | ||
| return; | ||
| } | ||
| if (result.children) { | ||
| for (const child of result.children) { | ||
| collectJsonEntries(child, entries); | ||
| } | ||
| } | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: "directory", | ||
| children: result.children?.map((c) => c.path) | ||
| }); | ||
| } | ||
| function formatJson(results, options, version) { | ||
| const entries = []; | ||
| if (options.summarize) { | ||
| for (const result of results) { | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: result.isFile ? "file" : "directory" | ||
| }); | ||
| } | ||
| } else { | ||
| for (const result of results) { | ||
| collectJsonEntries(result, entries); | ||
| } | ||
| } | ||
| const output = { | ||
| version, | ||
| encoding: options.encoding, | ||
| timestamp: new Date().toISOString(), | ||
| results: entries | ||
| }; | ||
| if (options.total || options.summarize) { | ||
| output.total = results.reduce((sum, r) => sum + r.tokens, 0); | ||
| } | ||
| return JSON.stringify(output, null, 2); | ||
| } | ||
| //#endregion | ||
| //#region src/main.ts | ||
| async function walkAndCount(paths, options) { | ||
| const countFn = await getTokenCounter(options.encoding, options.model); | ||
| const results = []; | ||
| for (const targetPath of paths) { | ||
| const absPath = resolve(targetPath); | ||
| const result = await walkPath(absPath, resolve(absPath, ".."), { | ||
| countFn, | ||
| all: options.all, | ||
| maxDepth: options.maxDepth, | ||
| exclude: options.exclude, | ||
| smartIgnore: options.smartIgnore | ||
| }); | ||
| results.push(result); | ||
| } | ||
| return results; | ||
| } | ||
| //#endregion | ||
| export { countFileTokens, formatJson, formatText, getTokenCounter, resolveEncoding, walkAndCount }; | ||
| //# sourceMappingURL=main-RYEeU4ax.mjs.map |
| {"version":3,"file":"main-RYEeU4ax.mjs","names":["encoding?: string","model?: string","buffer: Buffer","name: string","excludePatterns: string[]","value: string","rawLine: string","content: string","rules: GitIgnoreRule[]","targetPath: string","relativePath: string","basename: string","isDirectory: boolean","rule: GitIgnoreRule","childPath: string","entryName: string","ignoreContexts: IgnoreContext[]","filePath: string","countFn: TokenCountFn","basePath: string","options: WalkOptions","currentDepth: number","children: TokenResult[]","count: number","tokens: number","path: string","humanReadable: boolean","result: TokenResult","options: CliOptions","lines: string[]","depth: number","results: TokenResult[]","entries: JsonOutputEntry[]","version: string","output: JsonOutput","paths: string[]","options: Omit<CliOptions, 'paths' | 'json' | 'humanReadable' | 'total' | 'summarize'>","results: TokenResult[]"],"sources":["../src/tokenizer.ts","../src/walker.ts","../src/formatter.ts","../src/main.ts"],"sourcesContent":["import type { TokenCountFn } from './types.ts'\n\nconst VALID_ENCODINGS = new Set([\n 'o200k_base',\n 'o200k_harmony',\n 'cl100k_base',\n 'p50k_base',\n 'p50k_edit',\n 'r50k_base',\n])\n\nexport function resolveEncoding (encoding?: string, model?: string): string {\n if (encoding && model) {\n throw new Error('Cannot specify both --encoding and --model')\n }\n if (encoding) {\n if (!VALID_ENCODINGS.has(encoding)) {\n throw new Error(`Unknown encoding: ${encoding}. Valid encodings: ${[...VALID_ENCODINGS].join(', ')}`)\n }\n return encoding\n }\n return 'o200k_base'\n}\n\nexport async function getTokenCounter (encoding?: string, model?: string): Promise<TokenCountFn> {\n if (model) {\n if (encoding) {\n throw new Error('Cannot specify both --encoding and --model')\n }\n const mod = await import(`gpt-tokenizer/model/${model}`) as { countTokens: TokenCountFn }\n return mod.countTokens\n }\n\n const resolvedEncoding = resolveEncoding(encoding)\n if (resolvedEncoding === 'o200k_base') {\n const { countTokens } = await import('gpt-tokenizer')\n return countTokens\n }\n\n const mod = await import(`gpt-tokenizer/encoding/${resolvedEncoding}`) as { countTokens: TokenCountFn }\n return mod.countTokens\n}\n","import { readdir, readFile, stat } from 'node:fs/promises'\nimport { join, matchesGlob, relative, sep } from 'node:path'\nimport type { TokenCountFn, TokenResult } from './types.ts'\n\nconst DEFAULT_IGNORE = new Set([\n 'node_modules',\n '.git',\n])\n\nconst BINARY_CHECK_BYTES = 8192\nconst GITIGNORE_FILE = '.gitignore'\n\ninterface GitIgnoreRule {\n pattern: string\n negated: boolean\n directoryOnly: boolean\n anchored: boolean\n hasSlash: boolean\n}\n\ninterface IgnoreContext {\n rootPath: string\n rules: GitIgnoreRule[]\n}\n\nfunction isBinaryBuffer (buffer: Buffer): boolean {\n const length = Math.min(buffer.length, BINARY_CHECK_BYTES)\n for (let i = 0; i < length; i++) {\n if (buffer[i] === 0) return true\n }\n return false\n}\n\nfunction matchesExclude (name: string, excludePatterns: string[]): boolean {\n for (const pattern of excludePatterns) {\n if (pattern === name) return true\n if (pattern.startsWith('*.') && name.endsWith(pattern.slice(1))) return true\n if (pattern.endsWith('/*') && name === pattern.slice(0, -2)) return true\n }\n return false\n}\n\nfunction toPosixPath (value: string): string {\n return sep === '/' ? value : value.split(sep).join('/')\n}\n\nfunction parseGitIgnoreLine (rawLine: string): GitIgnoreRule | undefined {\n let pattern = rawLine.trim()\n if (pattern === '' || pattern.startsWith('#')) return undefined\n\n if (pattern.startsWith('\\\\#') || pattern.startsWith('\\\\!')) {\n pattern = pattern.slice(1)\n }\n\n let negated = false\n if (pattern.startsWith('!')) {\n negated = true\n pattern = pattern.slice(1)\n }\n\n pattern = pattern.trim()\n if (pattern === '') return undefined\n\n const anchored = pattern.startsWith('/')\n pattern = pattern.replace(/^\\/+/, '')\n\n const directoryOnly = pattern.endsWith('/')\n pattern = pattern.replace(/\\/+$/, '')\n if (pattern === '') return undefined\n\n return {\n pattern,\n negated,\n directoryOnly,\n anchored,\n hasSlash: pattern.includes('/'),\n }\n}\n\nfunction parseGitIgnore (content: string): GitIgnoreRule[] {\n const rules: GitIgnoreRule[] = []\n for (const line of content.split(/\\r?\\n/)) {\n const rule = parseGitIgnoreLine(line)\n if (rule) rules.push(rule)\n }\n return rules\n}\n\nasync function loadGitIgnoreContext (targetPath: string): Promise<IgnoreContext | undefined> {\n try {\n const content = await readFile(join(targetPath, GITIGNORE_FILE), 'utf-8')\n const rules = parseGitIgnore(content)\n return rules.length > 0 ? { rootPath: targetPath, rules } : undefined\n } catch (err) {\n if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {\n return undefined\n }\n throw err\n }\n}\n\nfunction matchesGitIgnorePattern (\n relativePath: string,\n basename: string,\n isDirectory: boolean,\n rule: GitIgnoreRule\n): boolean {\n if (rule.directoryOnly && !isDirectory) return false\n\n if (!rule.anchored && !rule.hasSlash) {\n return matchesGlob(basename, rule.pattern)\n }\n\n if (matchesGlob(relativePath, rule.pattern)) return true\n\n if (isDirectory) {\n return matchesGlob(`${relativePath}/`, `${rule.pattern}/**`)\n }\n\n return false\n}\n\nfunction matchesGitIgnore (\n childPath: string,\n entryName: string,\n isDirectory: boolean,\n ignoreContexts: IgnoreContext[]\n): boolean {\n let ignored = false\n\n for (const context of ignoreContexts) {\n const relativePath = toPosixPath(relative(context.rootPath, childPath))\n if (relativePath === '' || relativePath.startsWith('../')) continue\n\n for (const rule of context.rules) {\n if (matchesGitIgnorePattern(relativePath, entryName, isDirectory, rule)) {\n ignored = !rule.negated\n }\n }\n }\n\n return ignored\n}\n\nexport async function countFileTokens (\n filePath: string,\n countFn: TokenCountFn\n): Promise<number> {\n const buffer = await readFile(filePath)\n if (buffer.length === 0) return 0\n if (isBinaryBuffer(buffer)) return 0\n const text = buffer.toString('utf-8')\n try {\n return countFn(text)\n } catch {\n return 0\n }\n}\n\nexport interface WalkOptions {\n countFn: TokenCountFn\n all: boolean\n maxDepth?: number\n exclude: string[]\n smartIgnore?: boolean\n}\n\nexport async function walkPath (\n targetPath: string,\n basePath: string,\n options: WalkOptions,\n currentDepth: number = 0,\n ignoreContexts: IgnoreContext[] = []\n): Promise<TokenResult> {\n const info = await stat(targetPath)\n\n if (info.isFile()) {\n const tokens = await countFileTokens(targetPath, options.countFn)\n return {\n path: relative(basePath, targetPath) || targetPath,\n tokens,\n isFile: true,\n }\n }\n\n if (!info.isDirectory()) {\n return { path: relative(basePath, targetPath) || targetPath, tokens: 0, isFile: false }\n }\n\n const entries = await readdir(targetPath, { withFileTypes: true })\n const children: TokenResult[] = []\n let totalTokens = 0\n const smartIgnore = options.smartIgnore ?? true\n const currentIgnoreContexts = smartIgnore\n ? [...ignoreContexts, ...await loadGitIgnoreContext(targetPath).then(context => context ? [context] : [])]\n : ignoreContexts\n\n for (const entry of entries) {\n const childPath = join(targetPath, entry.name)\n const isDirectory = entry.isDirectory()\n\n if (smartIgnore && DEFAULT_IGNORE.has(entry.name)) continue\n if (smartIgnore && matchesGitIgnore(childPath, entry.name, isDirectory, currentIgnoreContexts)) continue\n if (matchesExclude(entry.name, options.exclude)) continue\n\n if (entry.isFile()) {\n const tokens = await countFileTokens(childPath, options.countFn)\n totalTokens += tokens\n children.push({\n path: relative(basePath, childPath),\n tokens,\n isFile: true,\n })\n } else if (entry.isDirectory()) {\n if (options.maxDepth !== undefined && currentDepth >= options.maxDepth) {\n continue\n }\n const childResult = await walkPath(childPath, basePath, options, currentDepth + 1, currentIgnoreContexts)\n totalTokens += childResult.tokens\n children.push(childResult)\n }\n }\n\n children.sort((a, b) => a.path.localeCompare(b.path))\n\n return {\n path: relative(basePath, targetPath) || targetPath,\n tokens: totalTokens,\n isFile: false,\n children,\n }\n}\n","import type { CliOptions, JsonOutput, JsonOutputEntry, TokenResult } from './types.ts'\n\nfunction humanReadableTokens (count: number): string {\n if (count >= 1_000_000) {\n const value = count / 1_000_000\n return value % 1 === 0 ? `${value}M` : `${value.toFixed(1)}M`\n }\n if (count >= 1_000) {\n const value = count / 1_000\n return value % 1 === 0 ? `${value}K` : `${value.toFixed(1)}K`\n }\n return count.toString()\n}\n\nfunction formatLine (tokens: number, path: string, humanReadable: boolean): string {\n const count = humanReadable ? humanReadableTokens(tokens) : tokens.toString()\n return `${count}\\t${path}`\n}\n\nfunction collectTextLines (\n result: TokenResult,\n options: CliOptions,\n lines: string[],\n depth: number = 0\n): void {\n if (result.isFile) {\n if (options.all) {\n lines.push(formatLine(result.tokens, result.path, options.humanReadable))\n }\n return\n }\n\n if (result.children) {\n for (const child of result.children) {\n if (options.maxDepth !== undefined && depth >= options.maxDepth) break\n collectTextLines(child, options, lines, depth + 1)\n }\n }\n\n lines.push(formatLine(result.tokens, result.path, options.humanReadable))\n}\n\nexport function formatText (results: TokenResult[], options: CliOptions): string {\n const lines: string[] = []\n\n if (options.summarize) {\n for (const result of results) {\n lines.push(formatLine(result.tokens, result.path, options.humanReadable))\n }\n } else {\n for (const result of results) {\n collectTextLines(result, options, lines)\n }\n }\n\n if (options.total) {\n const grandTotal = results.reduce((sum, r) => sum + r.tokens, 0)\n lines.push(formatLine(grandTotal, 'total', options.humanReadable))\n }\n\n return lines.join('\\n')\n}\n\nfunction collectJsonEntries (result: TokenResult, entries: JsonOutputEntry[]): void {\n if (result.isFile) {\n entries.push({\n path: result.path,\n tokens: result.tokens,\n type: 'file',\n })\n return\n }\n\n if (result.children) {\n for (const child of result.children) {\n collectJsonEntries(child, entries)\n }\n }\n\n entries.push({\n path: result.path,\n tokens: result.tokens,\n type: 'directory',\n children: result.children?.map(c => c.path),\n })\n}\n\nexport function formatJson (\n results: TokenResult[],\n options: CliOptions,\n version: string\n): string {\n const entries: JsonOutputEntry[] = []\n\n if (options.summarize) {\n for (const result of results) {\n entries.push({\n path: result.path,\n tokens: result.tokens,\n type: result.isFile ? 'file' : 'directory',\n })\n }\n } else {\n for (const result of results) {\n collectJsonEntries(result, entries)\n }\n }\n\n const output: JsonOutput = {\n version,\n encoding: options.encoding,\n timestamp: new Date().toISOString(),\n results: entries,\n }\n\n if (options.total || options.summarize) {\n output.total = results.reduce((sum, r) => sum + r.tokens, 0)\n }\n\n return JSON.stringify(output, null, 2)\n}\n","import { resolve } from 'node:path'\nimport type { CliOptions, TokenResult } from './types.ts'\nimport { getTokenCounter } from './tokenizer.ts'\nimport { walkPath } from './walker.ts'\nexport { countFileTokens } from './walker.ts'\nexport { getTokenCounter, resolveEncoding } from './tokenizer.ts'\nexport { formatText, formatJson } from './formatter.ts'\nexport type { TokenResult, CliOptions, TokenCountFn, JsonOutput, JsonOutputEntry } from './types.ts'\n\nexport async function walkAndCount (\n paths: string[],\n options: Omit<CliOptions, 'paths' | 'json' | 'humanReadable' | 'total' | 'summarize'>\n): Promise<TokenResult[]> {\n const countFn = await getTokenCounter(options.encoding, options.model)\n const results: TokenResult[] = []\n\n for (const targetPath of paths) {\n const absPath = resolve(targetPath)\n const result = await walkPath(absPath, resolve(absPath, '..'), {\n countFn,\n all: options.all,\n maxDepth: options.maxDepth,\n exclude: options.exclude,\n smartIgnore: options.smartIgnore,\n })\n results.push(result)\n }\n\n return results\n}\n"],"mappings":"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"} |
+9
-2
| #!/usr/bin/env node | ||
| "use strict"; | ||
| const require_main = require('../main-BLcEaqCf.cjs'); | ||
| const require_main = require('../main-COYhytHE.cjs'); | ||
| const node_path = require_main.__toESM(require("node:path")); | ||
@@ -24,2 +24,3 @@ const node_fs_promises = require_main.__toESM(require("node:fs/promises")); | ||
| --exclude <pat> Glob pattern to exclude (repeatable) | ||
| --no-ignore Disable default .git, node_modules, and .gitignore skips | ||
| --version Print version and exit | ||
@@ -85,2 +86,6 @@ --help Print this help and exit | ||
| }, | ||
| "no-ignore": { | ||
| type: "boolean", | ||
| default: false | ||
| }, | ||
| version: { | ||
@@ -130,2 +135,3 @@ type: "boolean", | ||
| exclude: excludePatterns, | ||
| smartIgnore: !(values["no-ignore"] ?? false), | ||
| paths | ||
@@ -139,3 +145,4 @@ }; | ||
| model: cliOptions.model, | ||
| exclude: cliOptions.exclude | ||
| exclude: cliOptions.exclude, | ||
| smartIgnore: cliOptions.smartIgnore | ||
| }); | ||
@@ -142,0 +149,0 @@ let output; |
+9
-2
| #!/usr/bin/env node | ||
| import { formatJson, formatText, resolveEncoding, walkAndCount } from "../main-Bt4L5ONO.mjs"; | ||
| import { formatJson, formatText, resolveEncoding, walkAndCount } from "../main-RYEeU4ax.mjs"; | ||
| import { dirname, resolve } from "node:path"; | ||
@@ -23,2 +23,3 @@ import { readFile } from "node:fs/promises"; | ||
| --exclude <pat> Glob pattern to exclude (repeatable) | ||
| --no-ignore Disable default .git, node_modules, and .gitignore skips | ||
| --version Print version and exit | ||
@@ -84,2 +85,6 @@ --help Print this help and exit | ||
| }, | ||
| "no-ignore": { | ||
| type: "boolean", | ||
| default: false | ||
| }, | ||
| version: { | ||
@@ -129,2 +134,3 @@ type: "boolean", | ||
| exclude: excludePatterns, | ||
| smartIgnore: !(values["no-ignore"] ?? false), | ||
| paths | ||
@@ -138,3 +144,4 @@ }; | ||
| model: cliOptions.model, | ||
| exclude: cliOptions.exclude | ||
| exclude: cliOptions.exclude, | ||
| smartIgnore: cliOptions.smartIgnore | ||
| }); | ||
@@ -141,0 +148,0 @@ let output; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"cli.mjs","names":["maxDepth: number | undefined","cliOptions: CliOptions","output: string"],"sources":["../../src/bin/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from 'node:util'\nimport { readFile } from 'node:fs/promises'\nimport { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { walkAndCount, resolveEncoding, formatText, formatJson } from '../main.ts'\nimport type { CliOptions } from '../types.ts'\n\nconst HELP_TEXT = `Usage: tokenu [options] [path...]\n\nA du-like CLI that counts token usage per file and directory.\n\nOptions:\n -s, --summarize Display only a total for each argument\n -h, --human-readable Print token counts in human-readable format (1K, 1M)\n -a, --all Show counts for all files, not just directories\n -d, --max-depth <N> Print totals only for directories N levels deep\n -c, --total Produce a grand total\n --json Output as JSON (for AI agent consumption)\n --encoding <enc> Tokenizer encoding (default: o200k_base)\n --model <name> Model name (e.g. gpt-4o, gpt-3.5-turbo)\n --exclude <pat> Glob pattern to exclude (repeatable)\n --version Print version and exit\n --help Print this help and exit\n\nEncodings: o200k_base, o200k_harmony, cl100k_base, p50k_base, p50k_edit, r50k_base\n\nExamples:\n tokenu . Recursive token counts per directory\n tokenu -hs src/ Human-readable summary of src/\n tokenu -a --json . All files as JSON\n tokenu --encoding cl100k_base . Use GPT-4/3.5 encoding\n tokenu -d 1 --total . Depth-limited with grand total\n`\n\nasync function getVersion (): Promise<string> {\n const currentDir = dirname(fileURLToPath(import.meta.url))\n const pkgPath = resolve(currentDir, '..', '..', 'package.json')\n try {\n const raw = await readFile(pkgPath, 'utf-8')\n const pkg = JSON.parse(raw) as { version: string }\n return pkg.version\n } catch {\n return '0.0.0'\n }\n}\n\nasync function main (): Promise<void> {\n const { values, positionals } = parseArgs({\n options: {\n summarize: { type: 'boolean', short: 's', default: false },\n 'human-readable': { type: 'boolean', short: 'h', default: false },\n all: { type: 'boolean', short: 'a', default: false },\n 'max-depth': { type: 'string', short: 'd' },\n total: { type: 'boolean', short: 'c', default: false },\n json: { type: 'boolean', default: false },\n encoding: { type: 'string' },\n model: { type: 'string' },\n exclude: { type: 'string', multiple: true },\n version: { type: 'boolean', default: false },\n help: { type: 'boolean', default: false },\n },\n allowPositionals: true,\n strict: true,\n })\n\n if (values.help) {\n process.stdout.write(HELP_TEXT)\n return\n }\n\n if (values.version) {\n const version = await getVersion()\n process.stdout.write(`${version}\\n`)\n return\n }\n\n const maxDepthRaw = values['max-depth']\n let maxDepth: number | undefined\n if (maxDepthRaw !== undefined) {\n maxDepth = parseInt(maxDepthRaw, 10)\n if (Number.isNaN(maxDepth) || maxDepth < 0) {\n process.stderr.write(`tokenu: invalid max depth: ${maxDepthRaw}\\n`)\n process.exitCode = 1\n return\n }\n }\n\n const encoding = resolveEncoding(values.encoding, values.model)\n const paths = positionals.length > 0 ? positionals : ['.']\n const excludePatterns = values.exclude ?? []\n\n const cliOptions: CliOptions = {\n summarize: values.summarize ?? false,\n humanReadable: values['human-readable'] ?? false,\n all: values.all ?? false,\n maxDepth,\n total: values.total ?? false,\n json: values.json ?? false,\n encoding,\n model: values.model,\n exclude: excludePatterns,\n paths,\n }\n\n try {\n const results = await walkAndCount(paths, {\n all: cliOptions.all,\n maxDepth: cliOptions.maxDepth,\n encoding: cliOptions.encoding,\n model: cliOptions.model,\n exclude: cliOptions.exclude,\n })\n\n let output: string\n if (cliOptions.json) {\n const version = await getVersion()\n output = formatJson(results, cliOptions, version)\n } else {\n output = formatText(results, cliOptions)\n }\n\n process.stdout.write(output + '\\n')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`tokenu: ${message}\\n`)\n process.exitCode = 1\n }\n}\n\nmain()\n"],"mappings":";;;;;;;;AAQA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BnB,eAAe,aAA+B;CAC5C,MAAM,aAAa,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;CAC1D,MAAM,UAAU,QAAQ,YAAY,MAAM,MAAM,eAAe;AAC/D,KAAI;EACF,MAAM,MAAM,MAAM,SAAS,SAAS,QAAQ;EAC5C,MAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,SAAO,IAAI;CACZ,QAAO;AACN,SAAO;CACR;AACF;AAED,eAAe,OAAuB;CACpC,MAAM,EAAE,QAAQ,aAAa,GAAG,UAAU;EACxC,SAAS;GACP,WAAW;IAAE,MAAM;IAAW,OAAO;IAAK,SAAS;GAAO;GAC1D,kBAAkB;IAAE,MAAM;IAAW,OAAO;IAAK,SAAS;GAAO;GACjE,KAAK;IAAE,MAAM;IAAW,OAAO;IAAK,SAAS;GAAO;GACpD,aAAa;IAAE,MAAM;IAAU,OAAO;GAAK;GAC3C,OAAO;IAAE,MAAM;IAAW,OAAO;IAAK,SAAS;GAAO;GACtD,MAAM;IAAE,MAAM;IAAW,SAAS;GAAO;GACzC,UAAU,EAAE,MAAM,SAAU;GAC5B,OAAO,EAAE,MAAM,SAAU;GACzB,SAAS;IAAE,MAAM;IAAU,UAAU;GAAM;GAC3C,SAAS;IAAE,MAAM;IAAW,SAAS;GAAO;GAC5C,MAAM;IAAE,MAAM;IAAW,SAAS;GAAO;EAC1C;EACD,kBAAkB;EAClB,QAAQ;CACT,EAAC;AAEF,KAAI,OAAO,MAAM;AACf,UAAQ,OAAO,MAAM,UAAU;AAC/B;CACD;AAED,KAAI,OAAO,SAAS;EAClB,MAAM,UAAU,MAAM,YAAY;AAClC,UAAQ,OAAO,OAAO,EAAE,QAAQ,IAAI;AACpC;CACD;CAED,MAAM,cAAc,OAAO;CAC3B,IAAIA;AACJ,KAAI,gBAAgB,WAAW;AAC7B,aAAW,SAAS,aAAa,GAAG;AACpC,MAAI,OAAO,MAAM,SAAS,IAAI,WAAW,GAAG;AAC1C,WAAQ,OAAO,OAAO,6BAA6B,YAAY,IAAI;AACnE,WAAQ,WAAW;AACnB;EACD;CACF;CAED,MAAM,WAAW,gBAAgB,OAAO,UAAU,OAAO,MAAM;CAC/D,MAAM,QAAQ,YAAY,SAAS,IAAI,cAAc,CAAC,GAAI;CAC1D,MAAM,kBAAkB,OAAO,WAAW,CAAE;CAE5C,MAAMC,aAAyB;EAC7B,WAAW,OAAO,aAAa;EAC/B,eAAe,OAAO,qBAAqB;EAC3C,KAAK,OAAO,OAAO;EACnB;EACA,OAAO,OAAO,SAAS;EACvB,MAAM,OAAO,QAAQ;EACrB;EACA,OAAO,OAAO;EACd,SAAS;EACT;CACD;AAED,KAAI;EACF,MAAM,UAAU,MAAM,aAAa,OAAO;GACxC,KAAK,WAAW;GAChB,UAAU,WAAW;GACrB,UAAU,WAAW;GACrB,OAAO,WAAW;GAClB,SAAS,WAAW;EACrB,EAAC;EAEF,IAAIC;AACJ,MAAI,WAAW,MAAM;GACnB,MAAM,UAAU,MAAM,YAAY;AAClC,YAAS,WAAW,SAAS,YAAY,QAAQ;EAClD,OAAM;AACL,YAAS,WAAW,SAAS,WAAW;EACzC;AAED,UAAQ,OAAO,MAAM,SAAS,KAAK;CACpC,SAAQ,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAQ,OAAO,OAAO,UAAU,QAAQ,IAAI;AAC5C,UAAQ,WAAW;CACpB;AACF;AAED,MAAM"} | ||
| {"version":3,"file":"cli.mjs","names":["maxDepth: number | undefined","cliOptions: CliOptions","output: string"],"sources":["../../src/bin/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { parseArgs } from 'node:util'\nimport { readFile } from 'node:fs/promises'\nimport { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { walkAndCount, resolveEncoding, formatText, formatJson } from '../main.ts'\nimport type { CliOptions } from '../types.ts'\n\nconst HELP_TEXT = `Usage: tokenu [options] [path...]\n\nA du-like CLI that counts token usage per file and directory.\n\nOptions:\n -s, --summarize Display only a total for each argument\n -h, --human-readable Print token counts in human-readable format (1K, 1M)\n -a, --all Show counts for all files, not just directories\n -d, --max-depth <N> Print totals only for directories N levels deep\n -c, --total Produce a grand total\n --json Output as JSON (for AI agent consumption)\n --encoding <enc> Tokenizer encoding (default: o200k_base)\n --model <name> Model name (e.g. gpt-4o, gpt-3.5-turbo)\n --exclude <pat> Glob pattern to exclude (repeatable)\n --no-ignore Disable default .git, node_modules, and .gitignore skips\n --version Print version and exit\n --help Print this help and exit\n\nEncodings: o200k_base, o200k_harmony, cl100k_base, p50k_base, p50k_edit, r50k_base\n\nExamples:\n tokenu . Recursive token counts per directory\n tokenu -hs src/ Human-readable summary of src/\n tokenu -a --json . All files as JSON\n tokenu --encoding cl100k_base . Use GPT-4/3.5 encoding\n tokenu -d 1 --total . Depth-limited with grand total\n`\n\nasync function getVersion (): Promise<string> {\n const currentDir = dirname(fileURLToPath(import.meta.url))\n const pkgPath = resolve(currentDir, '..', '..', 'package.json')\n try {\n const raw = await readFile(pkgPath, 'utf-8')\n const pkg = JSON.parse(raw) as { version: string }\n return pkg.version\n } catch {\n return '0.0.0'\n }\n}\n\nasync function main (): Promise<void> {\n const { values, positionals } = parseArgs({\n options: {\n summarize: { type: 'boolean', short: 's', default: false },\n 'human-readable': { type: 'boolean', short: 'h', default: false },\n all: { type: 'boolean', short: 'a', default: false },\n 'max-depth': { type: 'string', short: 'd' },\n total: { type: 'boolean', short: 'c', default: false },\n json: { type: 'boolean', default: false },\n encoding: { type: 'string' },\n model: { type: 'string' },\n exclude: { type: 'string', multiple: true },\n 'no-ignore': { type: 'boolean', default: false },\n version: { type: 'boolean', default: false },\n help: { type: 'boolean', default: false },\n },\n allowPositionals: true,\n strict: true,\n })\n\n if (values.help) {\n process.stdout.write(HELP_TEXT)\n return\n }\n\n if (values.version) {\n const version = await getVersion()\n process.stdout.write(`${version}\\n`)\n return\n }\n\n const maxDepthRaw = values['max-depth']\n let maxDepth: number | undefined\n if (maxDepthRaw !== undefined) {\n maxDepth = parseInt(maxDepthRaw, 10)\n if (Number.isNaN(maxDepth) || maxDepth < 0) {\n process.stderr.write(`tokenu: invalid max depth: ${maxDepthRaw}\\n`)\n process.exitCode = 1\n return\n }\n }\n\n const encoding = resolveEncoding(values.encoding, values.model)\n const paths = positionals.length > 0 ? positionals : ['.']\n const excludePatterns = values.exclude ?? []\n\n const cliOptions: CliOptions = {\n summarize: values.summarize ?? false,\n humanReadable: values['human-readable'] ?? false,\n all: values.all ?? false,\n maxDepth,\n total: values.total ?? false,\n json: values.json ?? false,\n encoding,\n model: values.model,\n exclude: excludePatterns,\n smartIgnore: !(values['no-ignore'] ?? false),\n paths,\n }\n\n try {\n const results = await walkAndCount(paths, {\n all: cliOptions.all,\n maxDepth: cliOptions.maxDepth,\n encoding: cliOptions.encoding,\n model: cliOptions.model,\n exclude: cliOptions.exclude,\n smartIgnore: cliOptions.smartIgnore,\n })\n\n let output: string\n if (cliOptions.json) {\n const version = await getVersion()\n output = formatJson(results, cliOptions, version)\n } else {\n output = formatText(results, cliOptions)\n }\n\n process.stdout.write(output + '\\n')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n process.stderr.write(`tokenu: ${message}\\n`)\n process.exitCode = 1\n }\n}\n\nmain()\n"],"mappings":";;;;;;;;AAQA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BnB,eAAe,aAA+B;CAC5C,MAAM,aAAa,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;CAC1D,MAAM,UAAU,QAAQ,YAAY,MAAM,MAAM,eAAe;AAC/D,KAAI;EACF,MAAM,MAAM,MAAM,SAAS,SAAS,QAAQ;EAC5C,MAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,SAAO,IAAI;CACZ,QAAO;AACN,SAAO;CACR;AACF;AAED,eAAe,OAAuB;CACpC,MAAM,EAAE,QAAQ,aAAa,GAAG,UAAU;EACxC,SAAS;GACP,WAAW;IAAE,MAAM;IAAW,OAAO;IAAK,SAAS;GAAO;GAC1D,kBAAkB;IAAE,MAAM;IAAW,OAAO;IAAK,SAAS;GAAO;GACjE,KAAK;IAAE,MAAM;IAAW,OAAO;IAAK,SAAS;GAAO;GACpD,aAAa;IAAE,MAAM;IAAU,OAAO;GAAK;GAC3C,OAAO;IAAE,MAAM;IAAW,OAAO;IAAK,SAAS;GAAO;GACtD,MAAM;IAAE,MAAM;IAAW,SAAS;GAAO;GACzC,UAAU,EAAE,MAAM,SAAU;GAC5B,OAAO,EAAE,MAAM,SAAU;GACzB,SAAS;IAAE,MAAM;IAAU,UAAU;GAAM;GAC3C,aAAa;IAAE,MAAM;IAAW,SAAS;GAAO;GAChD,SAAS;IAAE,MAAM;IAAW,SAAS;GAAO;GAC5C,MAAM;IAAE,MAAM;IAAW,SAAS;GAAO;EAC1C;EACD,kBAAkB;EAClB,QAAQ;CACT,EAAC;AAEF,KAAI,OAAO,MAAM;AACf,UAAQ,OAAO,MAAM,UAAU;AAC/B;CACD;AAED,KAAI,OAAO,SAAS;EAClB,MAAM,UAAU,MAAM,YAAY;AAClC,UAAQ,OAAO,OAAO,EAAE,QAAQ,IAAI;AACpC;CACD;CAED,MAAM,cAAc,OAAO;CAC3B,IAAIA;AACJ,KAAI,gBAAgB,WAAW;AAC7B,aAAW,SAAS,aAAa,GAAG;AACpC,MAAI,OAAO,MAAM,SAAS,IAAI,WAAW,GAAG;AAC1C,WAAQ,OAAO,OAAO,6BAA6B,YAAY,IAAI;AACnE,WAAQ,WAAW;AACnB;EACD;CACF;CAED,MAAM,WAAW,gBAAgB,OAAO,UAAU,OAAO,MAAM;CAC/D,MAAM,QAAQ,YAAY,SAAS,IAAI,cAAc,CAAC,GAAI;CAC1D,MAAM,kBAAkB,OAAO,WAAW,CAAE;CAE5C,MAAMC,aAAyB;EAC7B,WAAW,OAAO,aAAa;EAC/B,eAAe,OAAO,qBAAqB;EAC3C,KAAK,OAAO,OAAO;EACnB;EACA,OAAO,OAAO,SAAS;EACvB,MAAM,OAAO,QAAQ;EACrB;EACA,OAAO,OAAO;EACd,SAAS;EACT,eAAe,OAAO,gBAAgB;EACtC;CACD;AAED,KAAI;EACF,MAAM,UAAU,MAAM,aAAa,OAAO;GACxC,KAAK,WAAW;GAChB,UAAU,WAAW;GACrB,UAAU,WAAW;GACrB,OAAO,WAAW;GAClB,SAAS,WAAW;GACpB,aAAa,WAAW;EACzB,EAAC;EAEF,IAAIC;AACJ,MAAI,WAAW,MAAM;GACnB,MAAM,UAAU,MAAM,YAAY;AAClC,YAAS,WAAW,SAAS,YAAY,QAAQ;EAClD,OAAM;AACL,YAAS,WAAW,SAAS,WAAW;EACzC;AAED,UAAQ,OAAO,MAAM,SAAS,KAAK;CACpC,SAAQ,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAQ,OAAO,OAAO,UAAU,QAAQ,IAAI;AAC5C,UAAQ,WAAW;CACpB;AACF;AAED,MAAM"} |
+1
-1
@@ -1,2 +0,2 @@ | ||
| const require_main = require('./main-BLcEaqCf.cjs'); | ||
| const require_main = require('./main-COYhytHE.cjs'); | ||
@@ -3,0 +3,0 @@ exports.countFileTokens = require_main.countFileTokens |
+14
-2
@@ -18,2 +18,3 @@ //#region src/types.d.ts | ||
| exclude: string[]; | ||
| smartIgnore?: boolean; | ||
| paths: string[]; | ||
@@ -38,2 +39,13 @@ } | ||
| //# sourceMappingURL=types.d.ts.map | ||
| interface GitIgnoreRule { | ||
| pattern: string; | ||
| negated: boolean; | ||
| directoryOnly: boolean; | ||
| anchored: boolean; | ||
| hasSlash: boolean; | ||
| } | ||
| interface IgnoreContext { | ||
| rootPath: string; | ||
| rules: GitIgnoreRule[]; | ||
| } | ||
| declare function countFileTokens(filePath: string, countFn: TokenCountFn): Promise<number>; | ||
@@ -45,8 +57,8 @@ interface WalkOptions { | ||
| exclude: string[]; | ||
| smartIgnore?: boolean; | ||
| } | ||
| declare function walkPath(targetPath: string, basePath: string, options: WalkOptions, currentDepth?: number): Promise<TokenResult>; | ||
| declare function walkPath(targetPath: string, basePath: string, options: WalkOptions, currentDepth?: number, ignoreContexts?: IgnoreContext[]): Promise<TokenResult>; | ||
| //#endregion | ||
| //#region src/tokenizer.d.ts | ||
| //# sourceMappingURL=walker.d.ts.map | ||
| declare function resolveEncoding(encoding?: string, model?: string): string; | ||
@@ -53,0 +65,0 @@ declare function getTokenCounter(encoding?: string, model?: string): Promise<TokenCountFn>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"main.d.cts","names":[],"sources":["../src/types.ts","../src/walker.ts","../src/tokenizer.ts","../src/formatter.ts","../src/main.ts"],"sourcesContent":null,"mappings":";UAAiB,WAAA;EAAA,IAAA,EAAA,MAAA;EAOA,MAAA,EAAA,MAAU;EAaV,MAAA,EAAA,OAAA;EAOA,QAAA,CAAA,EAvBJ,WAuBc,EAIhB;AAIX;UA5BiB,UAAA;;;ECqBK,GAAA,EAAA,OAAA;EAAe,QAAA,CAAA,EAAA,MAAA;EAAA,KAE1B,EAAA,OAAA;EAAY,IACpB,EAAA,OAAA;EAAO,QAAA,EAAA,MAAA;EAYO,KAAA,CAAA,EAAA,MAAW;EAON,OAAA,EAAA,MAAQ,EAAA;EAAA,KAAA,EAAA,MAAA,EAAA;;AAKnB,UDnCM,eAAA,CCmCN;EAAW,IAAnB,EAAA,MAAA;EAAO,MAAA,EAAA,MAAA;;;;AC5CM,UFgBC,UAAA,CEhBc;EAaT,OAAA,EAAA,MAAA;EAAe,QAAA,EAAA,MAAA;EAAA,SAA8C,EAAA,MAAA;EAAY,OAApB,EFOhE,eEPgE,EAAA;EAAO,KAAA,CAAA,EAAA,MAAA;;KFWtE,YAAA;;;;AAnCK,iBC4BK,eAAA,CDxBE,QAAA,EAAA,MAAA,EAAA,OAAA,EC0Bb,YD1Ba,CAAA,EC2BrB,OD3BqB,CAAA,MAAA,CAAA;AAGP,UCoCA,WAAA,CDpCU;EAaV,OAAA,ECwBN,YDxBqB;EAOf,GAAA,EAAA,OAAA;EAQL,QAAA,CAAA,EAAA,MAAY;;;iBCeF,QAAA,gDAGX,qCAER,QAAQ;;;;AA3BX;AD5BiB,iBEWD,eAAA,CFPQ,QAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAGP,iBEiBK,eAAA,CFjBK,QAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EEiBgD,OFjBhD,CEiBwD,YFjBxD,CAAA;;;;AAa3B;AApBiB,iBG0CD,UAAA,CHtCH,OAAW,EGsCa,WHtCb,EAAA,EAAA,OAAA,EGsCqC,UHtCrC,CAAA,EAAA,MAAA;AAGP,iBGgFD,UAAA,CHhFW,OAAA,EGiFhB,WHjFgB,EAAA,EAAA,OAAA,EGkFhB,UHlFgB,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;AAa3B;;AAeY,iBI1BU,YAAA,CJ0BE,KAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EIxBb,IJwBa,CIxBR,UJwBQ,EAAA,OAAA,GAAA,MAAA,GAAA,eAAA,GAAA,OAAA,GAAA,WAAA,CAAA,CAAA,EIvBrB,OJuBqB,CIvBb,WJuBa,EAAA,CAAA"} | ||
| {"version":3,"file":"main.d.cts","names":[],"sources":["../src/types.ts","../src/walker.ts","../src/tokenizer.ts","../src/formatter.ts","../src/main.ts"],"sourcesContent":null,"mappings":";UAAiB,WAAA;EAAA,IAAA,EAAA,MAAA;EAOA,MAAA,EAAA,MAAU;EAcV,MAAA,EAAA,OAAA;EAOA,QAAA,CAAA,EAxBJ,WAwBc,EAIhB;AAIX;UA7BiB,UAAA;;;ECKP,GAAA,EAAA,OAAA;EAQA,QAAA,CAAA,EAAA,MAAa;EA4HD,KAAA,EAAA,OAAA;EAAe,IAAA,EAAA,OAAA;EAAA,QAE1B,EAAA,MAAA;EAAY,KACpB,CAAA,EAAA,MAAA;EAAO,OAAA,EAAA,MAAA,EAAA;EAYO,WAAA,CAAA,EAAW,OAAA;EAQN,KAAA,EAAA,MAAQ,EAAA;;AAGnB,UDrJM,eAAA,CCqJN;EAAW,IAEJ,EAAA,MAAA;EAAa,MACpB,EAAA,MAAA;EAAW,IAAnB,EAAA,MAAA,GAAA,WAAA;EAAO,QAAA,CAAA,EAAA,MAAA,EAAA;;UDjJO,UAAA;;EEjBD,QAAA,EAAA,MAAA;EAaM,SAAA,EAAA,MAAA;EAAe,OAAA,EFQ1B,eER0B,EAAA;EAAA,KAA8C,CAAA,EAAA,MAAA;;AAAD,KFYtE,YAAA,GEZsE,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,MAAA,CAAA;;;;AFxBlF,UCYU,aAAA,CDZkB;EAOX,OAAA,EAAA,MAAU;EAcV,OAAA,EAAA,OAAA;EAOA,aAAU,EAAA,OAAA;EAQf,QAAA,EAAA,OAAY;;;UChBd,aAAA;EARA,QAAA,EAAA,MAAa;EAQb,KAAA,EAED,aAFc,EAAA;AA4HvB;AAAqC,iBAAf,eAAA,CAAe,QAAA,EAAA,MAAA,EAAA,OAAA,EAE1B,YAF0B,CAAA,EAGlC,OAHkC,CAAA,MAAA,CAAA;AAE1B,UAaM,WAAA,CAbN;EAAY,OACpB,EAaQ,YAbR;EAAO,GAAA,EAAA,OAAA;EAYO,QAAA,CAAA,EAAA,MAAW;EAQN,OAAA,EAAA,MAAQ,EAAA;EAAA,WAAA,CAAA,EAAA,OAAA;;AAKZ,iBALI,QAAA,CAKJ,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAFP,WAEO,EAAA,YAAA,CAAA,EAAA,MAAA,EAAA,cAAA,CAAA,EAAA,aAAA,EAAA,CAAA,EACf,OADe,CACP,WADO,CAAA;;;;AD5KD,iBEWD,eAAA,CFPQ,QAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAGP,iBEiBK,eAAA,CFjBK,QAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EEiBgD,OFjBhD,CEiBwD,YFjBxD,CAAA;;;;AAc3B;AArBiB,iBG0CD,UAAA,CHtCH,OAAW,EGsCa,WHtCb,EAAA,EAAA,OAAA,EGsCqC,UHtCrC,CAAA,EAAA,MAAA;AAGP,iBGgFD,UAAA,CHhFW,OAAA,EGiFhB,WHjFgB,EAAA,EAAA,OAAA,EGkFhB,UHlFgB,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;AAc3B;;AAeY,iBI3BU,YAAA,CJ2BE,KAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EIzBb,IJyBa,CIzBR,UJyBQ,EAAA,OAAA,GAAA,MAAA,GAAA,eAAA,GAAA,OAAA,GAAA,WAAA,CAAA,CAAA,EIxBrB,OJwBqB,CIxBb,WJwBa,EAAA,CAAA"} |
+14
-2
@@ -18,2 +18,3 @@ //#region src/types.d.ts | ||
| exclude: string[]; | ||
| smartIgnore?: boolean; | ||
| paths: string[]; | ||
@@ -38,2 +39,13 @@ } | ||
| //# sourceMappingURL=types.d.ts.map | ||
| interface GitIgnoreRule { | ||
| pattern: string; | ||
| negated: boolean; | ||
| directoryOnly: boolean; | ||
| anchored: boolean; | ||
| hasSlash: boolean; | ||
| } | ||
| interface IgnoreContext { | ||
| rootPath: string; | ||
| rules: GitIgnoreRule[]; | ||
| } | ||
| declare function countFileTokens(filePath: string, countFn: TokenCountFn): Promise<number>; | ||
@@ -45,8 +57,8 @@ interface WalkOptions { | ||
| exclude: string[]; | ||
| smartIgnore?: boolean; | ||
| } | ||
| declare function walkPath(targetPath: string, basePath: string, options: WalkOptions, currentDepth?: number): Promise<TokenResult>; | ||
| declare function walkPath(targetPath: string, basePath: string, options: WalkOptions, currentDepth?: number, ignoreContexts?: IgnoreContext[]): Promise<TokenResult>; | ||
| //#endregion | ||
| //#region src/tokenizer.d.ts | ||
| //# sourceMappingURL=walker.d.ts.map | ||
| declare function resolveEncoding(encoding?: string, model?: string): string; | ||
@@ -53,0 +65,0 @@ declare function getTokenCounter(encoding?: string, model?: string): Promise<TokenCountFn>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"main.d.mts","names":[],"sources":["../src/types.ts","../src/walker.ts","../src/tokenizer.ts","../src/formatter.ts","../src/main.ts"],"sourcesContent":null,"mappings":";UAAiB,WAAA;EAAA,IAAA,EAAA,MAAA;EAOA,MAAA,EAAA,MAAU;EAaV,MAAA,EAAA,OAAA;EAOA,QAAA,CAAA,EAvBJ,WAuBc,EAIhB;AAIX;UA5BiB,UAAA;;;ECqBK,GAAA,EAAA,OAAA;EAAe,QAAA,CAAA,EAAA,MAAA;EAAA,KAE1B,EAAA,OAAA;EAAY,IACpB,EAAA,OAAA;EAAO,QAAA,EAAA,MAAA;EAYO,KAAA,CAAA,EAAA,MAAW;EAON,OAAA,EAAA,MAAQ,EAAA;EAAA,KAAA,EAAA,MAAA,EAAA;;AAKnB,UDnCM,eAAA,CCmCN;EAAW,IAAnB,EAAA,MAAA;EAAO,MAAA,EAAA,MAAA;;;;AC5CM,UFgBC,UAAA,CEhBc;EAaT,OAAA,EAAA,MAAA;EAAe,QAAA,EAAA,MAAA;EAAA,SAA8C,EAAA,MAAA;EAAY,OAApB,EFOhE,eEPgE,EAAA;EAAO,KAAA,CAAA,EAAA,MAAA;;KFWtE,YAAA;;;;AAnCK,iBC4BK,eAAA,CDxBE,QAAA,EAAA,MAAA,EAAA,OAAA,EC0Bb,YD1Ba,CAAA,EC2BrB,OD3BqB,CAAA,MAAA,CAAA;AAGP,UCoCA,WAAA,CDpCU;EAaV,OAAA,ECwBN,YDxBqB;EAOf,GAAA,EAAA,OAAA;EAQL,QAAA,CAAA,EAAA,MAAY;;;iBCeF,QAAA,gDAGX,qCAER,QAAQ;;;;AA3BX;AD5BiB,iBEWD,eAAA,CFPQ,QAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAGP,iBEiBK,eAAA,CFjBK,QAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EEiBgD,OFjBhD,CEiBwD,YFjBxD,CAAA;;;;AAa3B;AApBiB,iBG0CD,UAAA,CHtCH,OAAW,EGsCa,WHtCb,EAAA,EAAA,OAAA,EGsCqC,UHtCrC,CAAA,EAAA,MAAA;AAGP,iBGgFD,UAAA,CHhFW,OAAA,EGiFhB,WHjFgB,EAAA,EAAA,OAAA,EGkFhB,UHlFgB,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;AAa3B;;AAeY,iBI1BU,YAAA,CJ0BE,KAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EIxBb,IJwBa,CIxBR,UJwBQ,EAAA,OAAA,GAAA,MAAA,GAAA,eAAA,GAAA,OAAA,GAAA,WAAA,CAAA,CAAA,EIvBrB,OJuBqB,CIvBb,WJuBa,EAAA,CAAA"} | ||
| {"version":3,"file":"main.d.mts","names":[],"sources":["../src/types.ts","../src/walker.ts","../src/tokenizer.ts","../src/formatter.ts","../src/main.ts"],"sourcesContent":null,"mappings":";UAAiB,WAAA;EAAA,IAAA,EAAA,MAAA;EAOA,MAAA,EAAA,MAAU;EAcV,MAAA,EAAA,OAAA;EAOA,QAAA,CAAA,EAxBJ,WAwBc,EAIhB;AAIX;UA7BiB,UAAA;;;ECKP,GAAA,EAAA,OAAA;EAQA,QAAA,CAAA,EAAA,MAAa;EA4HD,KAAA,EAAA,OAAA;EAAe,IAAA,EAAA,OAAA;EAAA,QAE1B,EAAA,MAAA;EAAY,KACpB,CAAA,EAAA,MAAA;EAAO,OAAA,EAAA,MAAA,EAAA;EAYO,WAAA,CAAA,EAAW,OAAA;EAQN,KAAA,EAAA,MAAQ,EAAA;;AAGnB,UDrJM,eAAA,CCqJN;EAAW,IAEJ,EAAA,MAAA;EAAa,MACpB,EAAA,MAAA;EAAW,IAAnB,EAAA,MAAA,GAAA,WAAA;EAAO,QAAA,CAAA,EAAA,MAAA,EAAA;;UDjJO,UAAA;;EEjBD,QAAA,EAAA,MAAA;EAaM,SAAA,EAAA,MAAA;EAAe,OAAA,EFQ1B,eER0B,EAAA;EAAA,KAA8C,CAAA,EAAA,MAAA;;AAAD,KFYtE,YAAA,GEZsE,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,MAAA,CAAA;;;;AFxBlF,UCYU,aAAA,CDZkB;EAOX,OAAA,EAAA,MAAU;EAcV,OAAA,EAAA,OAAA;EAOA,aAAU,EAAA,OAAA;EAQf,QAAA,EAAA,OAAY;;;UChBd,aAAA;EARA,QAAA,EAAA,MAAa;EAQb,KAAA,EAED,aAFc,EAAA;AA4HvB;AAAqC,iBAAf,eAAA,CAAe,QAAA,EAAA,MAAA,EAAA,OAAA,EAE1B,YAF0B,CAAA,EAGlC,OAHkC,CAAA,MAAA,CAAA;AAE1B,UAaM,WAAA,CAbN;EAAY,OACpB,EAaQ,YAbR;EAAO,GAAA,EAAA,OAAA;EAYO,QAAA,CAAA,EAAA,MAAW;EAQN,OAAA,EAAA,MAAQ,EAAA;EAAA,WAAA,CAAA,EAAA,OAAA;;AAKZ,iBALI,QAAA,CAKJ,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,MAAA,EAAA,OAAA,EAFP,WAEO,EAAA,YAAA,CAAA,EAAA,MAAA,EAAA,cAAA,CAAA,EAAA,aAAA,EAAA,CAAA,EACf,OADe,CACP,WADO,CAAA;;;;AD5KD,iBEWD,eAAA,CFPQ,QAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;AAGP,iBEiBK,eAAA,CFjBK,QAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EEiBgD,OFjBhD,CEiBwD,YFjBxD,CAAA;;;;AAc3B;AArBiB,iBG0CD,UAAA,CHtCH,OAAW,EGsCa,WHtCb,EAAA,EAAA,OAAA,EGsCqC,UHtCrC,CAAA,EAAA,MAAA;AAGP,iBGgFD,UAAA,CHhFW,OAAA,EGiFhB,WHjFgB,EAAA,EAAA,OAAA,EGkFhB,UHlFgB,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;AAc3B;;AAeY,iBI3BU,YAAA,CJ2BE,KAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EIzBb,IJyBa,CIzBR,UJyBQ,EAAA,OAAA,GAAA,MAAA,GAAA,eAAA,GAAA,OAAA,GAAA,WAAA,CAAA,CAAA,EIxBrB,OJwBqB,CIxBb,WJwBa,EAAA,CAAA"} |
+1
-1
@@ -1,3 +0,3 @@ | ||
| import { countFileTokens, formatJson, formatText, getTokenCounter, resolveEncoding, walkAndCount } from "./main-Bt4L5ONO.mjs"; | ||
| import { countFileTokens, formatJson, formatText, getTokenCounter, resolveEncoding, walkAndCount } from "./main-RYEeU4ax.mjs"; | ||
| export { countFileTokens, formatJson, formatText, getTokenCounter, resolveEncoding, walkAndCount }; |
+3
-2
| { | ||
| "name": "tokenu", | ||
| "version": "1.0.1", | ||
| "version": "1.1.0", | ||
| "description": "A Unix du-like command line tool to count token usage per files and directories", | ||
@@ -8,3 +8,4 @@ "types": "dist/main.d.mts", | ||
| "bin": { | ||
| "tokenu": "./dist/bin/cli.cjs" | ||
| "tokenu": "./dist/bin/cli.cjs", | ||
| "tu": "./dist/bin/cli.cjs" | ||
| }, | ||
@@ -11,0 +12,0 @@ "exports": { |
+15
-2
@@ -49,2 +49,14 @@ <!-- markdownlint-disable --> | ||
| Prefer a shorter command without installing tokenu globally? Add this alias to your shell config file, such as `~/.zshrc` or `~/.bashrc`: | ||
| ```sh | ||
| alias tu='npx tokenu' | ||
| ``` | ||
| Then use `tu` anywhere you would use `tokenu`: | ||
| ```sh | ||
| tu . | ||
| ``` | ||
| ### Real-World Recipes | ||
@@ -101,2 +113,3 @@ | ||
| | `--exclude <pat>` | Glob pattern to exclude (repeatable) | | ||
| | `--no-ignore` | Disable default `.git`, `node_modules`, and `.gitignore` skips | | ||
@@ -186,3 +199,3 @@ Supported encodings: `o200k_base`, `o200k_harmony`, `cl100k_base`, `p50k_base`, `p50k_edit`, `r50k_base` | ||
| Actual tokenization. No estimation or approximation. tokenu reads every file, feeds its content through a real tokenizer (the [`gpt-tokenizer`](https://github.com/nicolo-ribaudo/gpt-tokenizer) library), and sums the results recursively across directories. The counts you see are the same counts the model would consume. | ||
| Actual tokenization. No estimation or approximation. tokenu reads each included file, feeds its content through a real tokenizer (the [`gpt-tokenizer`](https://github.com/niieani/gpt-tokenizer) library), and sums the results recursively across directories. It skips `.git`, `node_modules`, and files matched by `.gitignore` by default; pass `--no-ignore` to include them. The counts you see are the same counts the model would consume. | ||
@@ -206,3 +219,3 @@ You can also choose which tokenizer encoding to use. Different model families use different encodings, and token counts can vary between them. For example: | ||
| Please consult [CONTRIBUTING](./.github/CONTRIBUTING.md) for guidelines on contributing to this project. | ||
| Please consult [CONTRIBUTING](./CONTRIBUTING.md) for guidelines on contributing to this project. | ||
@@ -209,0 +222,0 @@ ## Author |
+4
-0
@@ -23,2 +23,3 @@ #!/usr/bin/env node | ||
| --exclude <pat> Glob pattern to exclude (repeatable) | ||
| --no-ignore Disable default .git, node_modules, and .gitignore skips | ||
| --version Print version and exit | ||
@@ -61,2 +62,3 @@ --help Print this help and exit | ||
| exclude: { type: 'string', multiple: true }, | ||
| 'no-ignore': { type: 'boolean', default: false }, | ||
| version: { type: 'boolean', default: false }, | ||
@@ -105,2 +107,3 @@ help: { type: 'boolean', default: false }, | ||
| exclude: excludePatterns, | ||
| smartIgnore: !(values['no-ignore'] ?? false), | ||
| paths, | ||
@@ -116,2 +119,3 @@ } | ||
| exclude: cliOptions.exclude, | ||
| smartIgnore: cliOptions.smartIgnore, | ||
| }) | ||
@@ -118,0 +122,0 @@ |
+1
-0
@@ -24,2 +24,3 @@ import { resolve } from 'node:path' | ||
| exclude: options.exclude, | ||
| smartIgnore: options.smartIgnore, | ||
| }) | ||
@@ -26,0 +27,0 @@ results.push(result) |
+1
-0
@@ -18,2 +18,3 @@ export interface TokenResult { | ||
| exclude: string[] | ||
| smartIgnore?: boolean | ||
| paths: string[] | ||
@@ -20,0 +21,0 @@ } |
+130
-6
| import { readdir, readFile, stat } from 'node:fs/promises' | ||
| import { join, relative } from 'node:path' | ||
| import { join, matchesGlob, relative, sep } from 'node:path' | ||
| import type { TokenCountFn, TokenResult } from './types.ts' | ||
@@ -11,3 +11,17 @@ | ||
| const BINARY_CHECK_BYTES = 8192 | ||
| const GITIGNORE_FILE = '.gitignore' | ||
| interface GitIgnoreRule { | ||
| pattern: string | ||
| negated: boolean | ||
| directoryOnly: boolean | ||
| anchored: boolean | ||
| hasSlash: boolean | ||
| } | ||
| interface IgnoreContext { | ||
| rootPath: string | ||
| rules: GitIgnoreRule[] | ||
| } | ||
| function isBinaryBuffer (buffer: Buffer): boolean { | ||
@@ -30,2 +44,104 @@ const length = Math.min(buffer.length, BINARY_CHECK_BYTES) | ||
| function toPosixPath (value: string): string { | ||
| return sep === '/' ? value : value.split(sep).join('/') | ||
| } | ||
| function parseGitIgnoreLine (rawLine: string): GitIgnoreRule | undefined { | ||
| let pattern = rawLine.trim() | ||
| if (pattern === '' || pattern.startsWith('#')) return undefined | ||
| if (pattern.startsWith('\\#') || pattern.startsWith('\\!')) { | ||
| pattern = pattern.slice(1) | ||
| } | ||
| let negated = false | ||
| if (pattern.startsWith('!')) { | ||
| negated = true | ||
| pattern = pattern.slice(1) | ||
| } | ||
| pattern = pattern.trim() | ||
| if (pattern === '') return undefined | ||
| const anchored = pattern.startsWith('/') | ||
| pattern = pattern.replace(/^\/+/, '') | ||
| const directoryOnly = pattern.endsWith('/') | ||
| pattern = pattern.replace(/\/+$/, '') | ||
| if (pattern === '') return undefined | ||
| return { | ||
| pattern, | ||
| negated, | ||
| directoryOnly, | ||
| anchored, | ||
| hasSlash: pattern.includes('/'), | ||
| } | ||
| } | ||
| function parseGitIgnore (content: string): GitIgnoreRule[] { | ||
| const rules: GitIgnoreRule[] = [] | ||
| for (const line of content.split(/\r?\n/)) { | ||
| const rule = parseGitIgnoreLine(line) | ||
| if (rule) rules.push(rule) | ||
| } | ||
| return rules | ||
| } | ||
| async function loadGitIgnoreContext (targetPath: string): Promise<IgnoreContext | undefined> { | ||
| try { | ||
| const content = await readFile(join(targetPath, GITIGNORE_FILE), 'utf-8') | ||
| const rules = parseGitIgnore(content) | ||
| return rules.length > 0 ? { rootPath: targetPath, rules } : undefined | ||
| } catch (err) { | ||
| if (err instanceof Error && 'code' in err && err.code === 'ENOENT') { | ||
| return undefined | ||
| } | ||
| throw err | ||
| } | ||
| } | ||
| function matchesGitIgnorePattern ( | ||
| relativePath: string, | ||
| basename: string, | ||
| isDirectory: boolean, | ||
| rule: GitIgnoreRule | ||
| ): boolean { | ||
| if (rule.directoryOnly && !isDirectory) return false | ||
| if (!rule.anchored && !rule.hasSlash) { | ||
| return matchesGlob(basename, rule.pattern) | ||
| } | ||
| if (matchesGlob(relativePath, rule.pattern)) return true | ||
| if (isDirectory) { | ||
| return matchesGlob(`${relativePath}/`, `${rule.pattern}/**`) | ||
| } | ||
| return false | ||
| } | ||
| function matchesGitIgnore ( | ||
| childPath: string, | ||
| entryName: string, | ||
| isDirectory: boolean, | ||
| ignoreContexts: IgnoreContext[] | ||
| ): boolean { | ||
| let ignored = false | ||
| for (const context of ignoreContexts) { | ||
| const relativePath = toPosixPath(relative(context.rootPath, childPath)) | ||
| if (relativePath === '' || relativePath.startsWith('../')) continue | ||
| for (const rule of context.rules) { | ||
| if (matchesGitIgnorePattern(relativePath, entryName, isDirectory, rule)) { | ||
| ignored = !rule.negated | ||
| } | ||
| } | ||
| } | ||
| return ignored | ||
| } | ||
| export async function countFileTokens ( | ||
@@ -51,2 +167,3 @@ filePath: string, | ||
| exclude: string[] | ||
| smartIgnore?: boolean | ||
| } | ||
@@ -58,3 +175,4 @@ | ||
| options: WalkOptions, | ||
| currentDepth: number = 0 | ||
| currentDepth: number = 0, | ||
| ignoreContexts: IgnoreContext[] = [] | ||
| ): Promise<TokenResult> { | ||
@@ -79,9 +197,15 @@ const info = await stat(targetPath) | ||
| let totalTokens = 0 | ||
| const smartIgnore = options.smartIgnore ?? true | ||
| const currentIgnoreContexts = smartIgnore | ||
| ? [...ignoreContexts, ...await loadGitIgnoreContext(targetPath).then(context => context ? [context] : [])] | ||
| : ignoreContexts | ||
| for (const entry of entries) { | ||
| if (DEFAULT_IGNORE.has(entry.name)) continue | ||
| const childPath = join(targetPath, entry.name) | ||
| const isDirectory = entry.isDirectory() | ||
| if (smartIgnore && DEFAULT_IGNORE.has(entry.name)) continue | ||
| if (smartIgnore && matchesGitIgnore(childPath, entry.name, isDirectory, currentIgnoreContexts)) continue | ||
| if (matchesExclude(entry.name, options.exclude)) continue | ||
| const childPath = join(targetPath, entry.name) | ||
| if (entry.isFile()) { | ||
@@ -99,3 +223,3 @@ const tokens = await countFileTokens(childPath, options.countFn) | ||
| } | ||
| const childResult = await walkPath(childPath, basePath, options, currentDepth + 1) | ||
| const childResult = await walkPath(childPath, basePath, options, currentDepth + 1, currentIgnoreContexts) | ||
| totalTokens += childResult.tokens | ||
@@ -102,0 +226,0 @@ children.push(childResult) |
| "use strict"; | ||
| //#region rolldown:runtime | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { | ||
| value: mod, | ||
| enumerable: true | ||
| }) : target, mod)); | ||
| //#endregion | ||
| const node_path = __toESM(require("node:path")); | ||
| const node_fs_promises = __toESM(require("node:fs/promises")); | ||
| //#region src/tokenizer.ts | ||
| const VALID_ENCODINGS = new Set([ | ||
| "o200k_base", | ||
| "o200k_harmony", | ||
| "cl100k_base", | ||
| "p50k_base", | ||
| "p50k_edit", | ||
| "r50k_base" | ||
| ]); | ||
| function resolveEncoding(encoding, model) { | ||
| if (encoding && model) { | ||
| throw new Error("Cannot specify both --encoding and --model"); | ||
| } | ||
| if (encoding) { | ||
| if (!VALID_ENCODINGS.has(encoding)) { | ||
| throw new Error(`Unknown encoding: ${encoding}. Valid encodings: ${[...VALID_ENCODINGS].join(", ")}`); | ||
| } | ||
| return encoding; | ||
| } | ||
| return "o200k_base"; | ||
| } | ||
| async function getTokenCounter(encoding, model) { | ||
| if (model) { | ||
| if (encoding) { | ||
| throw new Error("Cannot specify both --encoding and --model"); | ||
| } | ||
| const mod$1 = await import(`gpt-tokenizer/model/${model}`); | ||
| return mod$1.countTokens; | ||
| } | ||
| const resolvedEncoding = resolveEncoding(encoding); | ||
| if (resolvedEncoding === "o200k_base") { | ||
| const { countTokens } = await import("gpt-tokenizer"); | ||
| return countTokens; | ||
| } | ||
| const mod = await import(`gpt-tokenizer/encoding/${resolvedEncoding}`); | ||
| return mod.countTokens; | ||
| } | ||
| //#endregion | ||
| //#region src/walker.ts | ||
| const DEFAULT_IGNORE = new Set(["node_modules", ".git"]); | ||
| const BINARY_CHECK_BYTES = 8192; | ||
| function isBinaryBuffer(buffer) { | ||
| const length = Math.min(buffer.length, BINARY_CHECK_BYTES); | ||
| for (let i = 0; i < length; i++) { | ||
| if (buffer[i] === 0) return true; | ||
| } | ||
| return false; | ||
| } | ||
| function matchesExclude(name, excludePatterns) { | ||
| for (const pattern of excludePatterns) { | ||
| if (pattern === name) return true; | ||
| if (pattern.startsWith("*.") && name.endsWith(pattern.slice(1))) return true; | ||
| if (pattern.endsWith("/*") && name === pattern.slice(0, -2)) return true; | ||
| } | ||
| return false; | ||
| } | ||
| async function countFileTokens(filePath, countFn) { | ||
| const buffer = await (0, node_fs_promises.readFile)(filePath); | ||
| if (buffer.length === 0) return 0; | ||
| if (isBinaryBuffer(buffer)) return 0; | ||
| const text = buffer.toString("utf-8"); | ||
| try { | ||
| return countFn(text); | ||
| } catch { | ||
| return 0; | ||
| } | ||
| } | ||
| async function walkPath(targetPath, basePath, options, currentDepth = 0) { | ||
| const info = await (0, node_fs_promises.stat)(targetPath); | ||
| if (info.isFile()) { | ||
| const tokens = await countFileTokens(targetPath, options.countFn); | ||
| return { | ||
| path: (0, node_path.relative)(basePath, targetPath) || targetPath, | ||
| tokens, | ||
| isFile: true | ||
| }; | ||
| } | ||
| if (!info.isDirectory()) { | ||
| return { | ||
| path: (0, node_path.relative)(basePath, targetPath) || targetPath, | ||
| tokens: 0, | ||
| isFile: false | ||
| }; | ||
| } | ||
| const entries = await (0, node_fs_promises.readdir)(targetPath, { withFileTypes: true }); | ||
| const children = []; | ||
| let totalTokens = 0; | ||
| for (const entry of entries) { | ||
| if (DEFAULT_IGNORE.has(entry.name)) continue; | ||
| if (matchesExclude(entry.name, options.exclude)) continue; | ||
| const childPath = (0, node_path.join)(targetPath, entry.name); | ||
| if (entry.isFile()) { | ||
| const tokens = await countFileTokens(childPath, options.countFn); | ||
| totalTokens += tokens; | ||
| children.push({ | ||
| path: (0, node_path.relative)(basePath, childPath), | ||
| tokens, | ||
| isFile: true | ||
| }); | ||
| } else if (entry.isDirectory()) { | ||
| if (options.maxDepth !== undefined && currentDepth >= options.maxDepth) { | ||
| continue; | ||
| } | ||
| const childResult = await walkPath(childPath, basePath, options, currentDepth + 1); | ||
| totalTokens += childResult.tokens; | ||
| children.push(childResult); | ||
| } | ||
| } | ||
| children.sort((a, b) => a.path.localeCompare(b.path)); | ||
| return { | ||
| path: (0, node_path.relative)(basePath, targetPath) || targetPath, | ||
| tokens: totalTokens, | ||
| isFile: false, | ||
| children | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/formatter.ts | ||
| function humanReadableTokens(count) { | ||
| if (count >= 1e6) { | ||
| const value = count / 1e6; | ||
| return value % 1 === 0 ? `${value}M` : `${value.toFixed(1)}M`; | ||
| } | ||
| if (count >= 1e3) { | ||
| const value = count / 1e3; | ||
| return value % 1 === 0 ? `${value}K` : `${value.toFixed(1)}K`; | ||
| } | ||
| return count.toString(); | ||
| } | ||
| function formatLine(tokens, path, humanReadable) { | ||
| const count = humanReadable ? humanReadableTokens(tokens) : tokens.toString(); | ||
| return `${count}\t${path}`; | ||
| } | ||
| function collectTextLines(result, options, lines, depth = 0) { | ||
| if (result.isFile) { | ||
| if (options.all) { | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| return; | ||
| } | ||
| if (result.children) { | ||
| for (const child of result.children) { | ||
| if (options.maxDepth !== undefined && depth >= options.maxDepth) break; | ||
| collectTextLines(child, options, lines, depth + 1); | ||
| } | ||
| } | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| function formatText(results, options) { | ||
| const lines = []; | ||
| if (options.summarize) { | ||
| for (const result of results) { | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| } else { | ||
| for (const result of results) { | ||
| collectTextLines(result, options, lines); | ||
| } | ||
| } | ||
| if (options.total) { | ||
| const grandTotal = results.reduce((sum, r) => sum + r.tokens, 0); | ||
| lines.push(formatLine(grandTotal, "total", options.humanReadable)); | ||
| } | ||
| return lines.join("\n"); | ||
| } | ||
| function collectJsonEntries(result, entries) { | ||
| if (result.isFile) { | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: "file" | ||
| }); | ||
| return; | ||
| } | ||
| if (result.children) { | ||
| for (const child of result.children) { | ||
| collectJsonEntries(child, entries); | ||
| } | ||
| } | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: "directory", | ||
| children: result.children?.map((c) => c.path) | ||
| }); | ||
| } | ||
| function formatJson(results, options, version) { | ||
| const entries = []; | ||
| if (options.summarize) { | ||
| for (const result of results) { | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: result.isFile ? "file" : "directory" | ||
| }); | ||
| } | ||
| } else { | ||
| for (const result of results) { | ||
| collectJsonEntries(result, entries); | ||
| } | ||
| } | ||
| const output = { | ||
| version, | ||
| encoding: options.encoding, | ||
| timestamp: new Date().toISOString(), | ||
| results: entries | ||
| }; | ||
| if (options.total || options.summarize) { | ||
| output.total = results.reduce((sum, r) => sum + r.tokens, 0); | ||
| } | ||
| return JSON.stringify(output, null, 2); | ||
| } | ||
| //#endregion | ||
| //#region src/main.ts | ||
| async function walkAndCount(paths, options) { | ||
| const countFn = await getTokenCounter(options.encoding, options.model); | ||
| const results = []; | ||
| for (const targetPath of paths) { | ||
| const absPath = (0, node_path.resolve)(targetPath); | ||
| const result = await walkPath(absPath, (0, node_path.resolve)(absPath, ".."), { | ||
| countFn, | ||
| all: options.all, | ||
| maxDepth: options.maxDepth, | ||
| exclude: options.exclude | ||
| }); | ||
| results.push(result); | ||
| } | ||
| return results; | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, '__toESM', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return __toESM; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'countFileTokens', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return countFileTokens; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'formatJson', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return formatJson; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'formatText', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return formatText; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'getTokenCounter', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return getTokenCounter; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'resolveEncoding', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return resolveEncoding; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'walkAndCount', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return walkAndCount; | ||
| } | ||
| }); |
| import { join, relative, resolve } from "node:path"; | ||
| import { readFile, readdir, stat } from "node:fs/promises"; | ||
| //#region src/tokenizer.ts | ||
| const VALID_ENCODINGS = new Set([ | ||
| "o200k_base", | ||
| "o200k_harmony", | ||
| "cl100k_base", | ||
| "p50k_base", | ||
| "p50k_edit", | ||
| "r50k_base" | ||
| ]); | ||
| function resolveEncoding(encoding, model) { | ||
| if (encoding && model) { | ||
| throw new Error("Cannot specify both --encoding and --model"); | ||
| } | ||
| if (encoding) { | ||
| if (!VALID_ENCODINGS.has(encoding)) { | ||
| throw new Error(`Unknown encoding: ${encoding}. Valid encodings: ${[...VALID_ENCODINGS].join(", ")}`); | ||
| } | ||
| return encoding; | ||
| } | ||
| return "o200k_base"; | ||
| } | ||
| async function getTokenCounter(encoding, model) { | ||
| if (model) { | ||
| if (encoding) { | ||
| throw new Error("Cannot specify both --encoding and --model"); | ||
| } | ||
| const mod$1 = await import(`gpt-tokenizer/model/${model}`); | ||
| return mod$1.countTokens; | ||
| } | ||
| const resolvedEncoding = resolveEncoding(encoding); | ||
| if (resolvedEncoding === "o200k_base") { | ||
| const { countTokens } = await import("gpt-tokenizer"); | ||
| return countTokens; | ||
| } | ||
| const mod = await import(`gpt-tokenizer/encoding/${resolvedEncoding}`); | ||
| return mod.countTokens; | ||
| } | ||
| //#endregion | ||
| //#region src/walker.ts | ||
| const DEFAULT_IGNORE = new Set(["node_modules", ".git"]); | ||
| const BINARY_CHECK_BYTES = 8192; | ||
| function isBinaryBuffer(buffer) { | ||
| const length = Math.min(buffer.length, BINARY_CHECK_BYTES); | ||
| for (let i = 0; i < length; i++) { | ||
| if (buffer[i] === 0) return true; | ||
| } | ||
| return false; | ||
| } | ||
| function matchesExclude(name, excludePatterns) { | ||
| for (const pattern of excludePatterns) { | ||
| if (pattern === name) return true; | ||
| if (pattern.startsWith("*.") && name.endsWith(pattern.slice(1))) return true; | ||
| if (pattern.endsWith("/*") && name === pattern.slice(0, -2)) return true; | ||
| } | ||
| return false; | ||
| } | ||
| async function countFileTokens(filePath, countFn) { | ||
| const buffer = await readFile(filePath); | ||
| if (buffer.length === 0) return 0; | ||
| if (isBinaryBuffer(buffer)) return 0; | ||
| const text = buffer.toString("utf-8"); | ||
| try { | ||
| return countFn(text); | ||
| } catch { | ||
| return 0; | ||
| } | ||
| } | ||
| async function walkPath(targetPath, basePath, options, currentDepth = 0) { | ||
| const info = await stat(targetPath); | ||
| if (info.isFile()) { | ||
| const tokens = await countFileTokens(targetPath, options.countFn); | ||
| return { | ||
| path: relative(basePath, targetPath) || targetPath, | ||
| tokens, | ||
| isFile: true | ||
| }; | ||
| } | ||
| if (!info.isDirectory()) { | ||
| return { | ||
| path: relative(basePath, targetPath) || targetPath, | ||
| tokens: 0, | ||
| isFile: false | ||
| }; | ||
| } | ||
| const entries = await readdir(targetPath, { withFileTypes: true }); | ||
| const children = []; | ||
| let totalTokens = 0; | ||
| for (const entry of entries) { | ||
| if (DEFAULT_IGNORE.has(entry.name)) continue; | ||
| if (matchesExclude(entry.name, options.exclude)) continue; | ||
| const childPath = join(targetPath, entry.name); | ||
| if (entry.isFile()) { | ||
| const tokens = await countFileTokens(childPath, options.countFn); | ||
| totalTokens += tokens; | ||
| children.push({ | ||
| path: relative(basePath, childPath), | ||
| tokens, | ||
| isFile: true | ||
| }); | ||
| } else if (entry.isDirectory()) { | ||
| if (options.maxDepth !== undefined && currentDepth >= options.maxDepth) { | ||
| continue; | ||
| } | ||
| const childResult = await walkPath(childPath, basePath, options, currentDepth + 1); | ||
| totalTokens += childResult.tokens; | ||
| children.push(childResult); | ||
| } | ||
| } | ||
| children.sort((a, b) => a.path.localeCompare(b.path)); | ||
| return { | ||
| path: relative(basePath, targetPath) || targetPath, | ||
| tokens: totalTokens, | ||
| isFile: false, | ||
| children | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/formatter.ts | ||
| function humanReadableTokens(count) { | ||
| if (count >= 1e6) { | ||
| const value = count / 1e6; | ||
| return value % 1 === 0 ? `${value}M` : `${value.toFixed(1)}M`; | ||
| } | ||
| if (count >= 1e3) { | ||
| const value = count / 1e3; | ||
| return value % 1 === 0 ? `${value}K` : `${value.toFixed(1)}K`; | ||
| } | ||
| return count.toString(); | ||
| } | ||
| function formatLine(tokens, path, humanReadable) { | ||
| const count = humanReadable ? humanReadableTokens(tokens) : tokens.toString(); | ||
| return `${count}\t${path}`; | ||
| } | ||
| function collectTextLines(result, options, lines, depth = 0) { | ||
| if (result.isFile) { | ||
| if (options.all) { | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| return; | ||
| } | ||
| if (result.children) { | ||
| for (const child of result.children) { | ||
| if (options.maxDepth !== undefined && depth >= options.maxDepth) break; | ||
| collectTextLines(child, options, lines, depth + 1); | ||
| } | ||
| } | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| function formatText(results, options) { | ||
| const lines = []; | ||
| if (options.summarize) { | ||
| for (const result of results) { | ||
| lines.push(formatLine(result.tokens, result.path, options.humanReadable)); | ||
| } | ||
| } else { | ||
| for (const result of results) { | ||
| collectTextLines(result, options, lines); | ||
| } | ||
| } | ||
| if (options.total) { | ||
| const grandTotal = results.reduce((sum, r) => sum + r.tokens, 0); | ||
| lines.push(formatLine(grandTotal, "total", options.humanReadable)); | ||
| } | ||
| return lines.join("\n"); | ||
| } | ||
| function collectJsonEntries(result, entries) { | ||
| if (result.isFile) { | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: "file" | ||
| }); | ||
| return; | ||
| } | ||
| if (result.children) { | ||
| for (const child of result.children) { | ||
| collectJsonEntries(child, entries); | ||
| } | ||
| } | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: "directory", | ||
| children: result.children?.map((c) => c.path) | ||
| }); | ||
| } | ||
| function formatJson(results, options, version) { | ||
| const entries = []; | ||
| if (options.summarize) { | ||
| for (const result of results) { | ||
| entries.push({ | ||
| path: result.path, | ||
| tokens: result.tokens, | ||
| type: result.isFile ? "file" : "directory" | ||
| }); | ||
| } | ||
| } else { | ||
| for (const result of results) { | ||
| collectJsonEntries(result, entries); | ||
| } | ||
| } | ||
| const output = { | ||
| version, | ||
| encoding: options.encoding, | ||
| timestamp: new Date().toISOString(), | ||
| results: entries | ||
| }; | ||
| if (options.total || options.summarize) { | ||
| output.total = results.reduce((sum, r) => sum + r.tokens, 0); | ||
| } | ||
| return JSON.stringify(output, null, 2); | ||
| } | ||
| //#endregion | ||
| //#region src/main.ts | ||
| async function walkAndCount(paths, options) { | ||
| const countFn = await getTokenCounter(options.encoding, options.model); | ||
| const results = []; | ||
| for (const targetPath of paths) { | ||
| const absPath = resolve(targetPath); | ||
| const result = await walkPath(absPath, resolve(absPath, ".."), { | ||
| countFn, | ||
| all: options.all, | ||
| maxDepth: options.maxDepth, | ||
| exclude: options.exclude | ||
| }); | ||
| results.push(result); | ||
| } | ||
| return results; | ||
| } | ||
| //#endregion | ||
| export { countFileTokens, formatJson, formatText, getTokenCounter, resolveEncoding, walkAndCount }; | ||
| //# sourceMappingURL=main-Bt4L5ONO.mjs.map |
| {"version":3,"file":"main-Bt4L5ONO.mjs","names":["encoding?: string","model?: string","buffer: Buffer","name: string","excludePatterns: string[]","filePath: string","countFn: TokenCountFn","targetPath: string","basePath: string","options: WalkOptions","currentDepth: number","children: TokenResult[]","count: number","tokens: number","path: string","humanReadable: boolean","result: TokenResult","options: CliOptions","lines: string[]","depth: number","results: TokenResult[]","entries: JsonOutputEntry[]","version: string","output: JsonOutput","paths: string[]","options: Omit<CliOptions, 'paths' | 'json' | 'humanReadable' | 'total' | 'summarize'>","results: TokenResult[]"],"sources":["../src/tokenizer.ts","../src/walker.ts","../src/formatter.ts","../src/main.ts"],"sourcesContent":["import type { TokenCountFn } from './types.ts'\n\nconst VALID_ENCODINGS = new Set([\n 'o200k_base',\n 'o200k_harmony',\n 'cl100k_base',\n 'p50k_base',\n 'p50k_edit',\n 'r50k_base',\n])\n\nexport function resolveEncoding (encoding?: string, model?: string): string {\n if (encoding && model) {\n throw new Error('Cannot specify both --encoding and --model')\n }\n if (encoding) {\n if (!VALID_ENCODINGS.has(encoding)) {\n throw new Error(`Unknown encoding: ${encoding}. Valid encodings: ${[...VALID_ENCODINGS].join(', ')}`)\n }\n return encoding\n }\n return 'o200k_base'\n}\n\nexport async function getTokenCounter (encoding?: string, model?: string): Promise<TokenCountFn> {\n if (model) {\n if (encoding) {\n throw new Error('Cannot specify both --encoding and --model')\n }\n const mod = await import(`gpt-tokenizer/model/${model}`) as { countTokens: TokenCountFn }\n return mod.countTokens\n }\n\n const resolvedEncoding = resolveEncoding(encoding)\n if (resolvedEncoding === 'o200k_base') {\n const { countTokens } = await import('gpt-tokenizer')\n return countTokens\n }\n\n const mod = await import(`gpt-tokenizer/encoding/${resolvedEncoding}`) as { countTokens: TokenCountFn }\n return mod.countTokens\n}\n","import { readdir, readFile, stat } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport type { TokenCountFn, TokenResult } from './types.ts'\n\nconst DEFAULT_IGNORE = new Set([\n 'node_modules',\n '.git',\n])\n\nconst BINARY_CHECK_BYTES = 8192\n\nfunction isBinaryBuffer (buffer: Buffer): boolean {\n const length = Math.min(buffer.length, BINARY_CHECK_BYTES)\n for (let i = 0; i < length; i++) {\n if (buffer[i] === 0) return true\n }\n return false\n}\n\nfunction matchesExclude (name: string, excludePatterns: string[]): boolean {\n for (const pattern of excludePatterns) {\n if (pattern === name) return true\n if (pattern.startsWith('*.') && name.endsWith(pattern.slice(1))) return true\n if (pattern.endsWith('/*') && name === pattern.slice(0, -2)) return true\n }\n return false\n}\n\nexport async function countFileTokens (\n filePath: string,\n countFn: TokenCountFn\n): Promise<number> {\n const buffer = await readFile(filePath)\n if (buffer.length === 0) return 0\n if (isBinaryBuffer(buffer)) return 0\n const text = buffer.toString('utf-8')\n try {\n return countFn(text)\n } catch {\n return 0\n }\n}\n\nexport interface WalkOptions {\n countFn: TokenCountFn\n all: boolean\n maxDepth?: number\n exclude: string[]\n}\n\nexport async function walkPath (\n targetPath: string,\n basePath: string,\n options: WalkOptions,\n currentDepth: number = 0\n): Promise<TokenResult> {\n const info = await stat(targetPath)\n\n if (info.isFile()) {\n const tokens = await countFileTokens(targetPath, options.countFn)\n return {\n path: relative(basePath, targetPath) || targetPath,\n tokens,\n isFile: true,\n }\n }\n\n if (!info.isDirectory()) {\n return { path: relative(basePath, targetPath) || targetPath, tokens: 0, isFile: false }\n }\n\n const entries = await readdir(targetPath, { withFileTypes: true })\n const children: TokenResult[] = []\n let totalTokens = 0\n\n for (const entry of entries) {\n if (DEFAULT_IGNORE.has(entry.name)) continue\n if (matchesExclude(entry.name, options.exclude)) continue\n\n const childPath = join(targetPath, entry.name)\n\n if (entry.isFile()) {\n const tokens = await countFileTokens(childPath, options.countFn)\n totalTokens += tokens\n children.push({\n path: relative(basePath, childPath),\n tokens,\n isFile: true,\n })\n } else if (entry.isDirectory()) {\n if (options.maxDepth !== undefined && currentDepth >= options.maxDepth) {\n continue\n }\n const childResult = await walkPath(childPath, basePath, options, currentDepth + 1)\n totalTokens += childResult.tokens\n children.push(childResult)\n }\n }\n\n children.sort((a, b) => a.path.localeCompare(b.path))\n\n return {\n path: relative(basePath, targetPath) || targetPath,\n tokens: totalTokens,\n isFile: false,\n children,\n }\n}\n","import type { CliOptions, JsonOutput, JsonOutputEntry, TokenResult } from './types.ts'\n\nfunction humanReadableTokens (count: number): string {\n if (count >= 1_000_000) {\n const value = count / 1_000_000\n return value % 1 === 0 ? `${value}M` : `${value.toFixed(1)}M`\n }\n if (count >= 1_000) {\n const value = count / 1_000\n return value % 1 === 0 ? `${value}K` : `${value.toFixed(1)}K`\n }\n return count.toString()\n}\n\nfunction formatLine (tokens: number, path: string, humanReadable: boolean): string {\n const count = humanReadable ? humanReadableTokens(tokens) : tokens.toString()\n return `${count}\\t${path}`\n}\n\nfunction collectTextLines (\n result: TokenResult,\n options: CliOptions,\n lines: string[],\n depth: number = 0\n): void {\n if (result.isFile) {\n if (options.all) {\n lines.push(formatLine(result.tokens, result.path, options.humanReadable))\n }\n return\n }\n\n if (result.children) {\n for (const child of result.children) {\n if (options.maxDepth !== undefined && depth >= options.maxDepth) break\n collectTextLines(child, options, lines, depth + 1)\n }\n }\n\n lines.push(formatLine(result.tokens, result.path, options.humanReadable))\n}\n\nexport function formatText (results: TokenResult[], options: CliOptions): string {\n const lines: string[] = []\n\n if (options.summarize) {\n for (const result of results) {\n lines.push(formatLine(result.tokens, result.path, options.humanReadable))\n }\n } else {\n for (const result of results) {\n collectTextLines(result, options, lines)\n }\n }\n\n if (options.total) {\n const grandTotal = results.reduce((sum, r) => sum + r.tokens, 0)\n lines.push(formatLine(grandTotal, 'total', options.humanReadable))\n }\n\n return lines.join('\\n')\n}\n\nfunction collectJsonEntries (result: TokenResult, entries: JsonOutputEntry[]): void {\n if (result.isFile) {\n entries.push({\n path: result.path,\n tokens: result.tokens,\n type: 'file',\n })\n return\n }\n\n if (result.children) {\n for (const child of result.children) {\n collectJsonEntries(child, entries)\n }\n }\n\n entries.push({\n path: result.path,\n tokens: result.tokens,\n type: 'directory',\n children: result.children?.map(c => c.path),\n })\n}\n\nexport function formatJson (\n results: TokenResult[],\n options: CliOptions,\n version: string\n): string {\n const entries: JsonOutputEntry[] = []\n\n if (options.summarize) {\n for (const result of results) {\n entries.push({\n path: result.path,\n tokens: result.tokens,\n type: result.isFile ? 'file' : 'directory',\n })\n }\n } else {\n for (const result of results) {\n collectJsonEntries(result, entries)\n }\n }\n\n const output: JsonOutput = {\n version,\n encoding: options.encoding,\n timestamp: new Date().toISOString(),\n results: entries,\n }\n\n if (options.total || options.summarize) {\n output.total = results.reduce((sum, r) => sum + r.tokens, 0)\n }\n\n return JSON.stringify(output, null, 2)\n}\n","import { resolve } from 'node:path'\nimport type { CliOptions, TokenResult } from './types.ts'\nimport { getTokenCounter } from './tokenizer.ts'\nimport { walkPath } from './walker.ts'\nexport { countFileTokens } from './walker.ts'\nexport { getTokenCounter, resolveEncoding } from './tokenizer.ts'\nexport { formatText, formatJson } from './formatter.ts'\nexport type { TokenResult, CliOptions, TokenCountFn, JsonOutput, JsonOutputEntry } from './types.ts'\n\nexport async function walkAndCount (\n paths: string[],\n options: Omit<CliOptions, 'paths' | 'json' | 'humanReadable' | 'total' | 'summarize'>\n): Promise<TokenResult[]> {\n const countFn = await getTokenCounter(options.encoding, options.model)\n const results: TokenResult[] = []\n\n for (const targetPath of paths) {\n const absPath = resolve(targetPath)\n const result = await walkPath(absPath, resolve(absPath, '..'), {\n countFn,\n all: options.all,\n maxDepth: options.maxDepth,\n exclude: options.exclude,\n })\n results.push(result)\n }\n\n return results\n}\n"],"mappings":";;;;AAEA,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;AACD;AAED,SAAgB,gBAAiBA,UAAmBC,OAAwB;AAC1E,KAAI,YAAY,OAAO;AACrB,QAAM,IAAI,MAAM;CACjB;AACD,KAAI,UAAU;AACZ,OAAK,gBAAgB,IAAI,SAAS,EAAE;AAClC,SAAM,IAAI,OAAO,oBAAoB,SAAS,qBAAqB,CAAC,GAAG,eAAgB,EAAC,KAAK,KAAK,CAAC;EACpG;AACD,SAAO;CACR;AACD,QAAO;AACR;AAED,eAAsB,gBAAiBD,UAAmBC,OAAuC;AAC/F,KAAI,OAAO;AACT,MAAI,UAAU;AACZ,SAAM,IAAI,MAAM;EACjB;EACD,MAAM,QAAM,MAAM,QAAQ,sBAAsB,MAAM;AACtD,SAAO,MAAI;CACZ;CAED,MAAM,mBAAmB,gBAAgB,SAAS;AAClD,KAAI,qBAAqB,cAAc;EACrC,MAAM,EAAE,aAAa,GAAG,MAAM,OAAO;AACrC,SAAO;CACR;CAED,MAAM,MAAM,MAAM,QAAQ,yBAAyB,iBAAiB;AACpE,QAAO,IAAI;AACZ;;;;ACrCD,MAAM,iBAAiB,IAAI,IAAI,CAC7B,gBACA,MACD;AAED,MAAM,qBAAqB;AAE3B,SAAS,eAAgBC,QAAyB;CAChD,MAAM,SAAS,KAAK,IAAI,OAAO,QAAQ,mBAAmB;AAC1D,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,MAAI,OAAO,OAAO,EAAG,QAAO;CAC7B;AACD,QAAO;AACR;AAED,SAAS,eAAgBC,MAAcC,iBAAoC;AACzE,MAAK,MAAM,WAAW,iBAAiB;AACrC,MAAI,YAAY,KAAM,QAAO;AAC7B,MAAI,QAAQ,WAAW,KAAK,IAAI,KAAK,SAAS,QAAQ,MAAM,EAAE,CAAC,CAAE,QAAO;AACxE,MAAI,QAAQ,SAAS,KAAK,IAAI,SAAS,QAAQ,MAAM,IAAI,EAAE,CAAE,QAAO;CACrE;AACD,QAAO;AACR;AAED,eAAsB,gBACpBC,UACAC,SACiB;CACjB,MAAM,SAAS,MAAM,SAAS,SAAS;AACvC,KAAI,OAAO,WAAW,EAAG,QAAO;AAChC,KAAI,eAAe,OAAO,CAAE,QAAO;CACnC,MAAM,OAAO,OAAO,SAAS,QAAQ;AACrC,KAAI;AACF,SAAO,QAAQ,KAAK;CACrB,QAAO;AACN,SAAO;CACR;AACF;AASD,eAAsB,SACpBC,YACAC,UACAC,SACAC,eAAuB,GACD;CACtB,MAAM,OAAO,MAAM,KAAK,WAAW;AAEnC,KAAI,KAAK,QAAQ,EAAE;EACjB,MAAM,SAAS,MAAM,gBAAgB,YAAY,QAAQ,QAAQ;AACjE,SAAO;GACL,MAAM,SAAS,UAAU,WAAW,IAAI;GACxC;GACA,QAAQ;EACT;CACF;AAED,MAAK,KAAK,aAAa,EAAE;AACvB,SAAO;GAAE,MAAM,SAAS,UAAU,WAAW,IAAI;GAAY,QAAQ;GAAG,QAAQ;EAAO;CACxF;CAED,MAAM,UAAU,MAAM,QAAQ,YAAY,EAAE,eAAe,KAAM,EAAC;CAClE,MAAMC,WAA0B,CAAE;CAClC,IAAI,cAAc;AAElB,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,eAAe,IAAI,MAAM,KAAK,CAAE;AACpC,MAAI,eAAe,MAAM,MAAM,QAAQ,QAAQ,CAAE;EAEjD,MAAM,YAAY,KAAK,YAAY,MAAM,KAAK;AAE9C,MAAI,MAAM,QAAQ,EAAE;GAClB,MAAM,SAAS,MAAM,gBAAgB,WAAW,QAAQ,QAAQ;AAChE,kBAAe;AACf,YAAS,KAAK;IACZ,MAAM,SAAS,UAAU,UAAU;IACnC;IACA,QAAQ;GACT,EAAC;EACH,WAAU,MAAM,aAAa,EAAE;AAC9B,OAAI,QAAQ,aAAa,aAAa,gBAAgB,QAAQ,UAAU;AACtE;GACD;GACD,MAAM,cAAc,MAAM,SAAS,WAAW,UAAU,SAAS,eAAe,EAAE;AAClF,kBAAe,YAAY;AAC3B,YAAS,KAAK,YAAY;EAC3B;CACF;AAED,UAAS,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;AAErD,QAAO;EACL,MAAM,SAAS,UAAU,WAAW,IAAI;EACxC,QAAQ;EACR,QAAQ;EACR;CACD;AACF;;;;ACzGD,SAAS,oBAAqBC,OAAuB;AACnD,KAAI,SAAS,KAAW;EACtB,MAAM,QAAQ,QAAQ;AACtB,SAAO,QAAQ,MAAM,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,QAAQ,EAAE,CAAC;CAC5D;AACD,KAAI,SAAS,KAAO;EAClB,MAAM,QAAQ,QAAQ;AACtB,SAAO,QAAQ,MAAM,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,QAAQ,EAAE,CAAC;CAC5D;AACD,QAAO,MAAM,UAAU;AACxB;AAED,SAAS,WAAYC,QAAgBC,MAAcC,eAAgC;CACjF,MAAM,QAAQ,gBAAgB,oBAAoB,OAAO,GAAG,OAAO,UAAU;AAC7E,SAAQ,EAAE,MAAM,IAAI,KAAK;AAC1B;AAED,SAAS,iBACPC,QACAC,SACAC,OACAC,QAAgB,GACV;AACN,KAAI,OAAO,QAAQ;AACjB,MAAI,QAAQ,KAAK;AACf,SAAM,KAAK,WAAW,OAAO,QAAQ,OAAO,MAAM,QAAQ,cAAc,CAAC;EAC1E;AACD;CACD;AAED,KAAI,OAAO,UAAU;AACnB,OAAK,MAAM,SAAS,OAAO,UAAU;AACnC,OAAI,QAAQ,aAAa,aAAa,SAAS,QAAQ,SAAU;AACjE,oBAAiB,OAAO,SAAS,OAAO,QAAQ,EAAE;EACnD;CACF;AAED,OAAM,KAAK,WAAW,OAAO,QAAQ,OAAO,MAAM,QAAQ,cAAc,CAAC;AAC1E;AAED,SAAgB,WAAYC,SAAwBH,SAA6B;CAC/E,MAAMC,QAAkB,CAAE;AAE1B,KAAI,QAAQ,WAAW;AACrB,OAAK,MAAM,UAAU,SAAS;AAC5B,SAAM,KAAK,WAAW,OAAO,QAAQ,OAAO,MAAM,QAAQ,cAAc,CAAC;EAC1E;CACF,OAAM;AACL,OAAK,MAAM,UAAU,SAAS;AAC5B,oBAAiB,QAAQ,SAAS,MAAM;EACzC;CACF;AAED,KAAI,QAAQ,OAAO;EACjB,MAAM,aAAa,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,EAAE;AAChE,QAAM,KAAK,WAAW,YAAY,SAAS,QAAQ,cAAc,CAAC;CACnE;AAED,QAAO,MAAM,KAAK,KAAK;AACxB;AAED,SAAS,mBAAoBF,QAAqBK,SAAkC;AAClF,KAAI,OAAO,QAAQ;AACjB,UAAQ,KAAK;GACX,MAAM,OAAO;GACb,QAAQ,OAAO;GACf,MAAM;EACP,EAAC;AACF;CACD;AAED,KAAI,OAAO,UAAU;AACnB,OAAK,MAAM,SAAS,OAAO,UAAU;AACnC,sBAAmB,OAAO,QAAQ;EACnC;CACF;AAED,SAAQ,KAAK;EACX,MAAM,OAAO;EACb,QAAQ,OAAO;EACf,MAAM;EACN,UAAU,OAAO,UAAU,IAAI,CAAA,MAAK,EAAE,KAAK;CAC5C,EAAC;AACH;AAED,SAAgB,WACdD,SACAH,SACAK,SACQ;CACR,MAAMD,UAA6B,CAAE;AAErC,KAAI,QAAQ,WAAW;AACrB,OAAK,MAAM,UAAU,SAAS;AAC5B,WAAQ,KAAK;IACX,MAAM,OAAO;IACb,QAAQ,OAAO;IACf,MAAM,OAAO,SAAS,SAAS;GAChC,EAAC;EACH;CACF,OAAM;AACL,OAAK,MAAM,UAAU,SAAS;AAC5B,sBAAmB,QAAQ,QAAQ;EACpC;CACF;CAED,MAAME,SAAqB;EACzB;EACA,UAAU,QAAQ;EAClB,WAAW,IAAI,OAAO,aAAa;EACnC,SAAS;CACV;AAED,KAAI,QAAQ,SAAS,QAAQ,WAAW;AACtC,SAAO,QAAQ,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,EAAE;CAC7D;AAED,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE;AACvC;;;;AC/GD,eAAsB,aACpBC,OACAC,SACwB;CACxB,MAAM,UAAU,MAAM,gBAAgB,QAAQ,UAAU,QAAQ,MAAM;CACtE,MAAMC,UAAyB,CAAE;AAEjC,MAAK,MAAM,cAAc,OAAO;EAC9B,MAAM,UAAU,QAAQ,WAAW;EACnC,MAAM,SAAS,MAAM,SAAS,SAAS,QAAQ,SAAS,KAAK,EAAE;GAC7D;GACA,KAAK,QAAQ;GACb,UAAU,QAAQ;GAClB,SAAS,QAAQ;EAClB,EAAC;AACF,UAAQ,KAAK,OAAO;CACrB;AAED,QAAO;AACR"} |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
104539
20.03%1524
23.2%221
6.25%