diff --git a/.gitignore b/.gitignore index dbdf017e7..057e8207b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ playwright-report meta.json .agents/skills .claude.expect +SOURCE.md diff --git a/packages/react-grab/package.json b/packages/react-grab/package.json index 5543eff6d..fb8b196ab 100644 --- a/packages/react-grab/package.json +++ b/packages/react-grab/package.json @@ -91,6 +91,8 @@ "check": "vp check", "test": "playwright test", "test:expect": "bun e2e/react-grab.expect.ts", + "bundle-source": "tsx scripts/bundle-source.ts", + "count-tokens": "tsx scripts/count-tokens.ts", "typecheck": "tsc --noEmit", "prepublishOnly": "pnpm build", "test:e2e:ui": "playwright test --ui" @@ -102,7 +104,6 @@ "devDependencies": { "@babel/core": "^7.28.5", "@babel/preset-typescript": "^7.28.5", - "solid-js": "^1.9.10", "@playwright/test": "^1.40.0", "@tailwindcss/cli": "^4.1.17", "@types/babel__core": "^7.20.5", @@ -111,6 +112,9 @@ "babel-preset-solid": "^1.9.10", "concurrently": "^9.1.2", "expect-sdk": "0.0.0-canary-20260405095424", + "js-tiktoken": "^1.0.21", + "oxc-parser": "^0.125.0", + "solid-js": "^1.9.10", "tailwindcss": "^4.1.0", "tsx": "^4.21.0" }, diff --git a/packages/react-grab/scripts/bundle-source.ts b/packages/react-grab/scripts/bundle-source.ts new file mode 100644 index 000000000..d3bc4c813 --- /dev/null +++ b/packages/react-grab/scripts/bundle-source.ts @@ -0,0 +1,560 @@ +import { readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join, relative, resolve } from "node:path"; +import { parseSync } from "oxc-parser"; +import type { + ExportSpecifier, + ImportDeclarationSpecifier, + Program, + StringLiteral, +} from "oxc-parser"; +import { encodingForModel } from "js-tiktoken"; + +const SOURCE_DIR = resolve(import.meta.dirname, "..", "src"); +const OUTPUT_PATH = resolve(import.meta.dirname, "..", "SOURCE.md"); +const SOURCE_EXTENSIONS = new Set([".ts", ".tsx"]); +const ENTRY_FILES = new Set(["index.ts", "primitives.ts"]); + +interface ExportedSymbol { + name: string; + kind: "value" | "type" | "default" | "namespace"; +} + +interface ImportedSymbol { + localName: string; + importedName: string; + kind: "value" | "type" | "default" | "namespace"; +} + +interface ImportLink { + resolvedPath: string | null; + rawSpecifier: string; + isExternal: boolean; + symbols: ImportedSymbol[]; +} + +interface FileInfo { + relativePath: string; + absolutePath: string; + content: string; + tokens: number; + lines: number; + exports: ExportedSymbol[]; + imports: ImportLink[]; + importedBy: string[]; +} + +interface UnusedExport { + file: string; + symbolName: string; + kind: string; +} + +const collectSourceFiles = (directory: string): string[] => { + const entries = readdirSync(directory, { withFileTypes: true }); + const files: string[] = []; + for (const entry of entries) { + const fullPath = join(directory, entry.name); + if (entry.isDirectory()) { + files.push(...collectSourceFiles(fullPath)); + } else if (SOURCE_EXTENSIONS.has(entry.name.slice(entry.name.lastIndexOf(".")))) { + files.push(fullPath); + } + } + return files; +}; + +const resolveImportSpecifier = ( + specifier: string, + importerAbsolutePath: string, + allAbsolutePaths: Set, +): string | null => { + if (!specifier.startsWith(".") && !specifier.startsWith("/")) { + return null; + } + + const importerDir = dirname(importerAbsolutePath); + let resolved = resolve(importerDir, specifier); + resolved = resolved.replace(/\.js$/, "").replace(/\.jsx$/, ""); + + for (const candidate of [ + `${resolved}.ts`, + `${resolved}.tsx`, + `${resolved}/index.ts`, + `${resolved}/index.tsx`, + resolved, + ]) { + if (allAbsolutePaths.has(candidate)) { + return candidate; + } + } + + return null; +}; + +const getNodeName = (node: Record): string | undefined => { + const identifier = node.id as Record | undefined; + return identifier?.name as string | undefined; +}; + +const extractExports = (program: Program): ExportedSymbol[] => { + const exports: ExportedSymbol[] = []; + + for (const node of program.body) { + if (node.type === "ExportNamedDeclaration") { + if (node.declaration) { + const declaration = node.declaration; + const name = getNodeName(declaration); + if (name) { + const isTypeExport = + declaration.type === "TSInterfaceDeclaration" || + declaration.type === "TSTypeAliasDeclaration" || + declaration.type === "TSEnumDeclaration"; + exports.push({ name, kind: isTypeExport ? "type" : "value" }); + } + if ("declarations" in declaration && Array.isArray(declaration.declarations)) { + for (const declarator of declaration.declarations) { + const declaratorName = getNodeName(declarator); + if (declaratorName) { + exports.push({ name: declaratorName, kind: "value" }); + } + } + } + } + for (const specifier of node.specifiers) { + const kind = node.exportKind === "type" ? "type" : "value"; + exports.push({ name: specifier.exported.name, kind }); + } + } + + if (node.type === "ExportDefaultDeclaration") { + const name = getNodeName(node.declaration as Record) ?? "(default)"; + exports.push({ name, kind: "default" }); + } + + if (node.type === "ExportAllDeclaration") { + const label = node.exported?.name ?? "*"; + exports.push({ name: label, kind: "namespace" }); + } + } + + return exports; +}; + +const specifierToSymbol = ( + specifier: ImportDeclarationSpecifier | ExportSpecifier, + importKind: string, +): ImportedSymbol => { + if (specifier.type === "ImportSpecifier") { + return { + localName: specifier.local.name, + importedName: specifier.imported?.name ?? specifier.local.name, + kind: importKind === "type" ? "type" : "value", + }; + } + if (specifier.type === "ImportDefaultSpecifier") { + return { localName: specifier.local.name, importedName: "default", kind: "default" }; + } + if (specifier.type === "ImportNamespaceSpecifier") { + return { localName: specifier.local.name, importedName: "*", kind: "namespace" }; + } + return { + localName: specifier.exported?.name ?? "?", + importedName: specifier.local?.name ?? "?", + kind: importKind === "type" ? "type" : "value", + }; +}; + +const extractImports = ( + program: Program, + importerAbsolutePath: string, + allAbsolutePaths: Set, +): ImportLink[] => { + const imports: ImportLink[] = []; + + const processSource = ( + source: StringLiteral | null, + specifiers: Array, + importKind: string, + ): void => { + if (!source) return; + + const rawSpecifier = source.value; + const isExternal = !rawSpecifier.startsWith(".") && !rawSpecifier.startsWith("/"); + const resolvedPath = isExternal + ? null + : resolveImportSpecifier(rawSpecifier, importerAbsolutePath, allAbsolutePaths); + + const symbols: ImportedSymbol[] = specifiers.map((specifier) => + specifierToSymbol(specifier, importKind), + ); + + imports.push({ resolvedPath, rawSpecifier, isExternal, symbols }); + }; + + for (const node of program.body) { + if (node.type === "ImportDeclaration") { + processSource(node.source, node.specifiers, node.importKind ?? "value"); + } + if (node.type === "ExportNamedDeclaration" && node.source) { + processSource(node.source, node.specifiers, node.exportKind ?? "value"); + } + if (node.type === "ExportAllDeclaration") { + processSource(node.source, [], "value"); + } + } + + return imports; +}; + +const topologicalSort = (files: Map): FileInfo[] => { + const visited = new Set(); + const visiting = new Set(); + const ordered: FileInfo[] = []; + + const visit = (filePath: string): void => { + if (visited.has(filePath)) return; + if (visiting.has(filePath)) return; + visiting.add(filePath); + + const fileInfo = files.get(filePath); + if (fileInfo) { + for (const importLink of fileInfo.imports) { + if (importLink.resolvedPath && files.has(importLink.resolvedPath)) { + visit(importLink.resolvedPath); + } + } + } + + visiting.delete(filePath); + visited.add(filePath); + if (fileInfo) { + ordered.push(fileInfo); + } + }; + + for (const filePath of files.keys()) { + visit(filePath); + } + + return ordered; +}; + +const buildConsumedSymbolsMap = (allFiles: Map): Map> => { + const consumed = new Map>(); + + for (const file of allFiles.values()) { + for (const importLink of file.imports) { + if (!importLink.resolvedPath || !allFiles.has(importLink.resolvedPath)) continue; + const targetRelative = allFiles.get(importLink.resolvedPath)!.relativePath; + + if (!consumed.has(targetRelative)) { + consumed.set(targetRelative, new Set()); + } + const symbolSet = consumed.get(targetRelative)!; + + for (const symbol of importLink.symbols) { + symbolSet.add(symbol.importedName); + } + } + } + + return consumed; +}; + +const findUnusedExports = ( + allFiles: Map, + consumedSymbols: Map>, +): UnusedExport[] => { + const unused: UnusedExport[] = []; + + for (const file of allFiles.values()) { + if (ENTRY_FILES.has(file.relativePath)) continue; + + const hasStarImporter = [...allFiles.values()].some((otherFile) => + otherFile.imports.some( + (importLink) => + importLink.resolvedPath === file.absolutePath && + importLink.symbols.length === 0 && + !importLink.isExternal, + ), + ); + if (hasStarImporter) continue; + + const consumed = consumedSymbols.get(file.relativePath) ?? new Set(); + + for (const exportedSymbol of file.exports) { + if (exportedSymbol.kind === "namespace") continue; + + const isConsumed = + consumed.has(exportedSymbol.name) || + (exportedSymbol.kind === "default" && consumed.has("default")); + + if (!isConsumed) { + unused.push({ + file: file.relativePath, + symbolName: exportedSymbol.name, + kind: exportedSymbol.kind, + }); + } + } + } + + return unused; +}; + +const findOrphanFiles = (allFiles: Map): string[] => { + const orphans: string[] = []; + for (const file of allFiles.values()) { + if (ENTRY_FILES.has(file.relativePath)) continue; + if (file.importedBy.length === 0) { + orphans.push(file.relativePath); + } + } + return orphans.sort(); +}; + +const findSingleConsumerExports = ( + allFiles: Map, +): Array<{ file: string; consumer: string }> => { + const results: Array<{ file: string; consumer: string }> = []; + for (const file of allFiles.values()) { + if (ENTRY_FILES.has(file.relativePath)) continue; + if (file.importedBy.length === 1 && file.exports.length > 0) { + results.push({ file: file.relativePath, consumer: file.importedBy[0] }); + } + } + return results.sort((first, second) => first.file.localeCompare(second.file)); +}; + +const formatSymbolKind = (kind: string): string => { + if (kind === "type") return "type "; + if (kind === "default") return "default "; + if (kind === "namespace") return "* "; + return ""; +}; + +const formatNumber = (value: number): string => value.toLocaleString("en-US"); + +const generateMarkdown = (orderedFiles: FileInfo[], allFiles: Map): string => { + const totalTokens = orderedFiles.reduce((sum, file) => sum + file.tokens, 0); + const totalLines = orderedFiles.reduce((sum, file) => sum + file.lines, 0); + + const consumedSymbols = buildConsumedSymbolsMap(allFiles); + const unusedExports = findUnusedExports(allFiles, consumedSymbols); + const orphanFiles = findOrphanFiles(allFiles); + const singleConsumerFiles = findSingleConsumerExports(allFiles); + + const lines: string[] = []; + const push = (...text: string[]) => lines.push(...text); + + push("# react-grab source"); + push(""); + push( + `${formatNumber(orderedFiles.length)} files, ${formatNumber(totalLines)} lines, ${formatNumber(totalTokens)} tokens`, + ); + push(""); + push( + "Files are topologically sorted: dependencies appear before dependents. Entry points: index.ts (public API), primitives.ts (low-level primitives).", + ); + push(""); + + push("## Analysis"); + push(""); + + if (orphanFiles.length > 0) { + push("### Orphan files (no internal importer)"); + push(""); + push( + "These files are not imported by any other source file. They may be entry points, dead code, or only used at runtime via side effects.", + ); + push(""); + for (const orphan of orphanFiles) { + push(`- ${orphan}`); + } + push(""); + } + + if (unusedExports.length > 0) { + push("### Unused exports (not imported by any internal file)"); + push(""); + push( + "These symbols are exported but never imported within the codebase. They may be part of the public API consumed externally, or they may be dead code.", + ); + push(""); + + const groupedByFile = new Map(); + for (const entry of unusedExports) { + if (!groupedByFile.has(entry.file)) { + groupedByFile.set(entry.file, []); + } + groupedByFile.get(entry.file)!.push(entry); + } + + for (const [filePath, symbols] of [...groupedByFile.entries()].sort()) { + const symbolList = symbols + .map((symbol) => `${formatSymbolKind(symbol.kind)}${symbol.symbolName}`) + .join(", "); + push(`- ${filePath}: ${symbolList}`); + } + push(""); + } + + if (singleConsumerFiles.length > 0) { + push("### Single-consumer files (inlining candidates)"); + push(""); + push( + "These files are only imported by one other file. The code may be better inlined into the consumer.", + ); + push(""); + for (const { file, consumer } of singleConsumerFiles) { + push(`- ${file} โ†’ only used by ${consumer}`); + } + push(""); + } + + const externalDeps = new Map>(); + for (const file of orderedFiles) { + for (const importLink of file.imports) { + if (importLink.isExternal) { + const packageName = importLink.rawSpecifier.startsWith("@") + ? importLink.rawSpecifier.split("/").slice(0, 2).join("/") + : importLink.rawSpecifier.split("/")[0]; + if (!externalDeps.has(packageName)) { + externalDeps.set(packageName, new Set()); + } + externalDeps.get(packageName)!.add(file.relativePath); + } + } + } + + push("### External dependencies"); + push(""); + for (const [packageName, importers] of [...externalDeps.entries()].sort()) { + push(`- ${packageName}: ${[...importers].sort().join(", ")}`); + } + push(""); + + push("---"); + push(""); + + for (const file of orderedFiles) { + push(`## ${file.relativePath}`); + push(""); + push(`${file.tokens} tokens, ${file.lines} lines`); + + const internalImports = file.imports.filter( + (importLink) => !importLink.isExternal && importLink.resolvedPath, + ); + const externalImports = file.imports.filter((importLink) => importLink.isExternal); + + if (internalImports.length > 0) { + const importLines = internalImports.map((importLink) => { + const targetInfo = allFiles.get(importLink.resolvedPath!); + const targetRelative = targetInfo?.relativePath ?? importLink.rawSpecifier; + const symbolList = importLink.symbols + .map( + (symbol) => + `${formatSymbolKind(symbol.kind)}${symbol.importedName !== symbol.localName ? `${symbol.importedName} as ` : ""}${symbol.localName}`, + ) + .join(", "); + return symbolList ? `${targetRelative} { ${symbolList} }` : targetRelative; + }); + push(`imports: ${importLines.join("; ")}`); + } + + if (externalImports.length > 0) { + const importLines = externalImports.map((importLink) => { + const symbolList = importLink.symbols + .map( + (symbol) => + `${formatSymbolKind(symbol.kind)}${symbol.importedName !== symbol.localName ? `${symbol.importedName} as ` : ""}${symbol.localName}`, + ) + .join(", "); + return symbolList + ? `${importLink.rawSpecifier} { ${symbolList} }` + : importLink.rawSpecifier; + }); + push(`external: ${importLines.join("; ")}`); + } + + if (file.exports.length > 0) { + const exportList = file.exports + .map((symbol) => `${formatSymbolKind(symbol.kind)}${symbol.name}`) + .join(", "); + push(`exports: ${exportList}`); + } + + if (file.importedBy.length > 0) { + push(`imported by: ${file.importedBy.sort().join(", ")}`); + } else if (!ENTRY_FILES.has(file.relativePath)) { + push("imported by: (none โ€” possible dead code)"); + } + + push(""); + + const extension = file.relativePath.endsWith(".tsx") ? "tsx" : "ts"; + push(`\`\`\`${extension}`); + push(file.content); + if (!file.content.endsWith("\n")) { + push(""); + } + push("```"); + push(""); + } + + return lines.join("\n"); +}; + +const run = () => { + const encoding = encodingForModel("gpt-4o"); + const absolutePaths = collectSourceFiles(SOURCE_DIR).sort(); + const allAbsolutePathsSet = new Set(absolutePaths); + + const filesMap = new Map(); + + for (const absolutePath of absolutePaths) { + const content = readFileSync(absolutePath, "utf8"); + const relativePath = relative(SOURCE_DIR, absolutePath); + const tokens = encoding.encode(content).length; + const lines = content.split("\n").length; + + const parseResult = parseSync(absolutePath, content); + const exports = extractExports(parseResult.program); + const imports = extractImports(parseResult.program, absolutePath, allAbsolutePathsSet); + + filesMap.set(absolutePath, { + relativePath, + absolutePath, + content, + tokens, + lines, + exports, + imports, + importedBy: [], + }); + } + + for (const file of filesMap.values()) { + for (const importLink of file.imports) { + if (importLink.resolvedPath && filesMap.has(importLink.resolvedPath)) { + const target = filesMap.get(importLink.resolvedPath)!; + if (!target.importedBy.includes(file.relativePath)) { + target.importedBy.push(file.relativePath); + } + } + } + } + + const orderedFiles = topologicalSort(filesMap); + const markdown = generateMarkdown(orderedFiles, filesMap); + + writeFileSync(OUTPUT_PATH, markdown); + + const totalTokens = orderedFiles.reduce((sum, file) => sum + file.tokens, 0); + console.log(`\n Wrote ${OUTPUT_PATH}`); + console.log( + ` ${formatNumber(orderedFiles.length)} files ยท ${formatNumber(totalTokens)} tokens\n`, + ); +}; + +run(); diff --git a/packages/react-grab/scripts/count-tokens.ts b/packages/react-grab/scripts/count-tokens.ts new file mode 100644 index 000000000..db5fee8c2 --- /dev/null +++ b/packages/react-grab/scripts/count-tokens.ts @@ -0,0 +1,77 @@ +import { readdirSync, readFileSync } from "node:fs"; +import { join, relative } from "node:path"; +import { encodingForModel } from "js-tiktoken"; + +const SOURCE_DIR = join(import.meta.dirname, "..", "src"); +const SOURCE_EXTENSIONS = new Set([".ts", ".tsx"]); + +interface FileTokenCount { + path: string; + tokens: number; + lines: number; + characters: number; +} + +const collectSourceFiles = (directory: string): string[] => { + const entries = readdirSync(directory, { withFileTypes: true }); + const files: string[] = []; + + for (const entry of entries) { + const fullPath = join(directory, entry.name); + if (entry.isDirectory()) { + files.push(...collectSourceFiles(fullPath)); + } else if (SOURCE_EXTENSIONS.has(entry.name.slice(entry.name.lastIndexOf(".")))) { + files.push(fullPath); + } + } + + return files; +}; + +const formatNumber = (value: number): string => value.toLocaleString("en-US"); + +const run = () => { + const encoding = encodingForModel("gpt-4o"); + const sourceFiles = collectSourceFiles(SOURCE_DIR).sort(); + + const fileCounts: FileTokenCount[] = sourceFiles.map((filePath) => { + const content = readFileSync(filePath, "utf8"); + const tokens = encoding.encode(content).length; + const lines = content.split("\n").length; + return { + path: relative(SOURCE_DIR, filePath), + tokens, + lines, + characters: content.length, + }; + }); + + const totalTokens = fileCounts.reduce((sum, file) => sum + file.tokens, 0); + const totalLines = fileCounts.reduce((sum, file) => sum + file.lines, 0); + const totalCharacters = fileCounts.reduce((sum, file) => sum + file.characters, 0); + + const maxPathLength = Math.max(...fileCounts.map((file) => file.path.length)); + const maxTokenLength = Math.max(...fileCounts.map((file) => formatNumber(file.tokens).length)); + + console.log(`\n react-grab source token count (cl100k_base encoding)\n`); + + const sortedByTokens = [...fileCounts].sort((first, second) => second.tokens - first.tokens); + + for (const file of sortedByTokens) { + const paddedPath = file.path.padEnd(maxPathLength); + const paddedTokens = formatNumber(file.tokens).padStart(maxTokenLength); + console.log(` ${paddedPath} ${paddedTokens} tokens`); + } + + const separatorLength = maxPathLength + maxTokenLength + 12; + console.log(` ${"โ”€".repeat(separatorLength)}`); + console.log( + ` ${"Total".padEnd(maxPathLength)} ${formatNumber(totalTokens).padStart(maxTokenLength)} tokens`, + ); + console.log(`\n ${formatNumber(sourceFiles.length)} files`); + console.log(` ${formatNumber(totalLines)} lines`); + console.log(` ${formatNumber(totalCharacters)} characters`); + console.log(` ${formatNumber(totalTokens)} tokens\n`); +}; + +run(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88ad1caaf..9e42b69f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -395,6 +395,12 @@ importers: expect-sdk: specifier: 0.0.0-canary-20260405095424 version: 0.0.0-canary-20260405095424(@types/react@19.2.11)(express@5.2.1)(ioredis@5.10.1)(playwright@1.57.0)(zod@4.3.5) + js-tiktoken: + specifier: ^1.0.21 + version: 1.0.21 + oxc-parser: + specifier: ^0.125.0 + version: 0.125.0 solid-js: specifier: ^1.9.10 version: 1.9.10 @@ -752,9 +758,18 @@ packages: effect: ^4.0.0-beta.35 ioredis: ^5.7.0 + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.6.0': resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emotion/is-prop-valid@1.4.0': resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} @@ -1506,6 +1521,12 @@ packages: cpu: [x64] os: [win32] + '@napi-rs/wasm-runtime@1.1.3': + resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@next/env@15.3.8': resolution: {integrity: sha512-SAfHg0g91MQVMPioeFeDjE+8UPF3j3BvHjs8ZKJAUz1BG7eMPvfCKOAgNWJ6s1MLNeP6O2InKQRTNblxPWuq+Q==} @@ -1830,6 +1851,125 @@ packages: resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} engines: {node: '>=14'} + '@oxc-parser/binding-android-arm-eabi@0.125.0': + resolution: {integrity: sha512-YfHwPEH8c5XNOlffaAqhsChNOBgmJ7rEgVbxSwAr65KDR0wbpZUBkrSaCClYL4urf0LmwyULrahHMvFAyk/dwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.125.0': + resolution: {integrity: sha512-rh72O8ackqp0HC+3W38oCTkCFmOpXrHRrbP+4xrX8O1UmCWcyb5pIbA/+0ATPGVVl9NcHt/CgqI8rBuw4Y9kMg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.125.0': + resolution: {integrity: sha512-14Q74TMQA/eO0N5dz5Tel25qma9vVJEpmrmqXnx0R7jMXhqFxkSSy40NOtCQijWUfeD5ho5+NuXDl5WSxyifJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.125.0': + resolution: {integrity: sha512-qWQDphAaIS6qXeuYcWm4jta8qFZpjjim2WxiPwZmHi77COS8i0Jct8tBcNIOZ/JaVh+hCL2it228m2Lr9GOL6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.125.0': + resolution: {integrity: sha512-PTATC/j2MvDP8lejoCC7PFWNoYV2NsVzzM0WgBqZDFAkFdKsW0wfbQWochfY3fHNUN1QhZNetrd/K4Pdo6cIHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.125.0': + resolution: {integrity: sha512-Colj5agHBAMKZrkyPcCEelfKuh8sNi1lWpJf1TiEeEmbREQ6I2ytG+ccfdDaiUV7Z0Vw5FyJbnqEPgHo8kF3RQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.125.0': + resolution: {integrity: sha512-BxQ8o082+/qtjAFK6WUV+/bi0y3M0RPvPQNm8JSY7/7LfhbWq6NykgZiGayrtauO1nowpmGlnpJXXMp9q0oT1A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.125.0': + resolution: {integrity: sha512-qR0dOth+4whygUwoNnfews8jMC78gjhIBfcy9AFzvxoh7PFGdferRp3KV/4kkeaVk2kOS/5grlAeJevpA+/Pfg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-arm64-musl@0.125.0': + resolution: {integrity: sha512-eIXyzpA12/+maKjMSsXdHfpzwQcoRfzokT+/ZhVEo6u/9RcXQrZZmZ70MmmJqwVcLez6U4ScjB/eiYlsEs7p0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-ppc64-gnu@0.125.0': + resolution: {integrity: sha512-w7ir5OuqSJUKLadmsSAWwTNso/ZGem2bPT/1LSU7l+ecmKPyegIvU+wzY0ADhZ/t/goaedqyp24SDRxyLxO9zg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-gnu@0.125.0': + resolution: {integrity: sha512-2KPTfWorcW8RNE8aEMHKbPSjHDBjFVYqg8nSLRBp7pe7VBqHsmkO9jpK8YmaYA5d5GcUy+J++5O4EgxkrQBEtw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-musl@0.125.0': + resolution: {integrity: sha512-Vsl8dmQdKtDsQiDPHP5VFjXOuVGcZQcziYMkU/yPnlaKHMqoX/q+bxt7K+BwResi9Cc8pnZ6oYGTgPcjAtt5QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-s390x-gnu@0.125.0': + resolution: {integrity: sha512-HwY5kuM818r/kHdHG2TZqzqxyF7fz90prPg85R/2VmgRWk8cMyGZo+8BNZDQAMJ6aGSTRvn2sdGXv3sZ5bsUWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxc-parser/binding-linux-x64-gnu@0.125.0': + resolution: {integrity: sha512-o7k6+xAI2pIkjBsCqM0elI4q+qY/3TexH6cpIlGm+nJze1tvx7QEHCKdiy6wnRacFvUYmySEZ5hWFBc9MbxrIA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-linux-x64-musl@0.125.0': + resolution: {integrity: sha512-vksRynFD6vytE1sDZCaeIk6y6rCsq0a18T4kcXbfGHBq2q/qSyDogWLk3A3S3hl/ikNfse7yrEwAuQ8ldIJeAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-openharmony-arm64@0.125.0': + resolution: {integrity: sha512-AAtg4pnKvrKsay2ldZZRY98ALFBOgbyy3Gyxo658z6aecM0Zr5mI9BOHRCchSVKUHqMqmjhCA4wIdZvz02VrAw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.125.0': + resolution: {integrity: sha512-FkIQFrwlBXoFsazb9NQpQPP4YI9sWWXUOLkIPYlQb+hPwr+VY6d0B7l26yMBR2ktf2h3qyAMOW6Pd+mX9rtOJg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.125.0': + resolution: {integrity: sha512-bi4RY9oktNm3kQ3qRCJgBKtwqSg+mtnt5W9l33rdiTyiXlL8a1LQQy1x7aym/ArHDE+19kSWSr2YDd2ExxzbfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.125.0': + resolution: {integrity: sha512-ZhvL2vK+9rzjk1US2d2u6NeI1/jtkzsm//ilFac+Kn3klTpJJlKNZwF23CUiAu+B3rdQUbPItm/BHlL6f/5uPA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.125.0': + resolution: {integrity: sha512-P4ywUSCYIg44Y82wF3e0ns1BV1dNn+ZhfjNDwm0FTPtBKXedOCRPrvmjXn7Qb+IDGGHAA68lmDLCjGxuKUwXPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-project/runtime@0.122.0': resolution: {integrity: sha512-vevyz3bNjevQFCV2Yg5o6Sp9BSoiYiJVymMrzA3S1ZGj4J8ak4YiywhFyQMueQ3UNlJU6HZOZYDy70TUc99aHw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1844,6 +1984,9 @@ packages: '@oxc-project/types@0.123.0': resolution: {integrity: sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==} + '@oxc-project/types@0.125.0': + resolution: {integrity: sha512-s9RKLJbRR+3kEFB3mmJVPWah3cZUAl0Jzmthx6Pb/QXnlNkRwTP75tK4uVahp/ifiiTmNYMXI1+NnGP1rNurXg==} + '@oxfmt/binding-android-arm-eabi@0.43.0': resolution: {integrity: sha512-CgU2s+/9hHZgo0IxVxrbMPrMj+tJ6VM3mD7Mr/4oiz4FNTISLoCvRmB5nk4wAAle045RtRjd86m673jwPyb1OQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3155,6 +3298,9 @@ packages: cpu: [arm64] os: [win32] + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3773,6 +3919,7 @@ packages: basic-ftp@5.2.0: resolution: {integrity: sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.1, please upgrade better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} @@ -5152,6 +5299,9 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-tiktoken@1.0.21: + resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -5787,6 +5937,10 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + oxc-parser@0.125.0: + resolution: {integrity: sha512-6M0gEDDVMGGy+Ckg/mlLh4PL87sfKRMlkQJTVTxdcEREwDa4usWjM9n4jC6Jxa5+nc3YlZTecUs4hHjoTVWKaw==} + engines: {node: ^20.19.0 || >=22.12.0} + oxfmt@0.43.0: resolution: {integrity: sha512-KTYNG5ISfHSdmeZ25Xzb3qgz9EmQvkaGAxgBY/p38+ZiAet3uZeu7FnMwcSQJg152Qwl0wnYAxDc+Z/H6cvrwA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -7704,11 +7858,27 @@ snapshots: - bufferutil - utf-8-validate + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.6.0': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/is-prop-valid@1.4.0': dependencies: '@emotion/memoize': 0.9.0 @@ -8105,7 +8275,7 @@ snapshots: '@google/gemini-cli-core': 0.35.3(express@5.2.1) '@google/genai': 1.30.0(@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)) '@iarna/toml': 2.2.5 - '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.29.0(zod@4.3.5) ansi-escapes: 7.3.0 ansi-regex: 6.2.2 chalk: 4.1.2 @@ -8120,7 +8290,7 @@ snapshots: fzf: 0.5.2 glob: 12.0.0 highlight.js: 11.11.1 - ink: '@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.1)' + ink: '@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.3)' ink-gradient: 3.0.0(@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.1)) ink-spinner: 5.0.0(@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.1))(react@19.2.1) latest-version: 9.0.0 @@ -8161,7 +8331,7 @@ snapshots: google-auth-library: 10.6.2 ws: 8.20.0 optionalDependencies: - '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.29.0(zod@4.3.5) transitivePeerDependencies: - bufferutil - supports-color @@ -8308,7 +8478,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.1)': + '@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.3)': dependencies: '@alcalzone/ansi-tokenize': 0.2.5 ansi-escapes: 7.3.0 @@ -8324,7 +8494,7 @@ snapshots: is-in-ci: 2.0.0 mnemonist: 0.40.3 patch-console: 2.0.0 - react: 19.2.1 + react: 19.2.3 react-reconciler: 0.32.0(react@19.2.1) signal-exit: 3.0.7 slice-ansi: 7.1.2 @@ -8509,6 +8679,13 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true + '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true + '@next/env@15.3.8': {} '@next/env@16.0.10': {} @@ -8853,6 +9030,70 @@ snapshots: '@opentelemetry/semantic-conventions@1.40.0': {} + '@oxc-parser/binding-android-arm-eabi@0.125.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.125.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.125.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.125.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.125.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.125.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.125.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.125.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.125.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.125.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.125.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.125.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.125.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.125.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.125.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.125.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.125.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.125.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.125.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.125.0': + optional: true + '@oxc-project/runtime@0.122.0': {} '@oxc-project/runtime@0.123.0': {} @@ -8861,6 +9102,8 @@ snapshots: '@oxc-project/types@0.123.0': {} + '@oxc-project/types@0.125.0': {} + '@oxfmt/binding-android-arm-eabi@0.43.0': optional: true @@ -9900,6 +10143,11 @@ snapshots: '@turbo/windows-arm64@2.9.3': optional: true + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 @@ -11757,14 +12005,14 @@ snapshots: dependencies: '@types/gradient-string': 1.1.6 gradient-string: 2.0.2 - ink: '@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.1)' + ink: '@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.3)' prop-types: 15.8.1 strip-ansi: 7.1.0 ink-spinner@5.0.0(@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.1))(react@19.2.1): dependencies: cli-spinners: 2.9.2 - ink: '@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.1)' + ink: '@jrichman/ink@6.4.11(@types/react@19.2.11)(react@19.2.3)' react: 19.2.1 internmap@2.0.3: {} @@ -11896,6 +12144,10 @@ snapshots: jose@6.1.3: {} + js-tiktoken@1.0.21: + dependencies: + base64-js: 1.5.1 + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -12484,6 +12736,31 @@ snapshots: outdent@0.5.0: {} + oxc-parser@0.125.0: + dependencies: + '@oxc-project/types': 0.125.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.125.0 + '@oxc-parser/binding-android-arm64': 0.125.0 + '@oxc-parser/binding-darwin-arm64': 0.125.0 + '@oxc-parser/binding-darwin-x64': 0.125.0 + '@oxc-parser/binding-freebsd-x64': 0.125.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.125.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.125.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.125.0 + '@oxc-parser/binding-linux-arm64-musl': 0.125.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.125.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.125.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.125.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.125.0 + '@oxc-parser/binding-linux-x64-gnu': 0.125.0 + '@oxc-parser/binding-linux-x64-musl': 0.125.0 + '@oxc-parser/binding-openharmony-arm64': 0.125.0 + '@oxc-parser/binding-wasm32-wasi': 0.125.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.125.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.125.0 + '@oxc-parser/binding-win32-x64-msvc': 0.125.0 + oxfmt@0.43.0: dependencies: tinypool: 2.1.0 @@ -14056,7 +14333,7 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 wrappy@1.0.2: {}