w3pk 0.9.3 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":["fs","path"]}
1
+ {"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Maximum total size in KB for all collected code (prevents exceeding API token limits)\n * @default 100\n */\n maxTotalSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @param cumulativeSizeKB - Tracks total size collected so far\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string,\n cumulativeSizeKB: { value: number } = { value: 0 }\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Check if we've reached the total size limit\n if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {\n console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);\n break;\n }\n\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n // Calculate content size\n const contentSizeKB = Buffer.byteLength(content, 'utf-8') / 1024;\n\n // Check if adding this file would exceed the total limit\n if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {\n console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);\n break;\n }\n\n // Add file and update cumulative size\n cumulativeSizeKB.value += contentSizeKB;\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n maxTotalSizeKB: options.maxTotalSizeKB || 100,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":["fs","path"]}
@@ -57,18 +57,22 @@ function isTransactionRelevant(content, filename) {
57
57
  (keyword) => lowerContent.includes(keyword.toLowerCase())
58
58
  );
59
59
  }
60
- async function walkDirectory(dir, options, rootDir) {
60
+ async function walkDirectory(dir, options, rootDir, cumulativeSizeKB = { value: 0 }) {
61
61
  const files = [];
62
62
  try {
63
63
  const entries = await fs.readdir(dir, { withFileTypes: true });
64
64
  for (const entry of entries) {
65
+ if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {
66
+ console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);
67
+ break;
68
+ }
65
69
  const fullPath = path.join(dir, entry.name);
66
70
  const relativePath = path.relative(rootDir, fullPath);
67
71
  if (entry.isDirectory()) {
68
72
  if (options.excludeDirs.includes(entry.name)) {
69
73
  continue;
70
74
  }
71
- const subFiles = await walkDirectory(fullPath, options, rootDir);
75
+ const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);
72
76
  files.push(...subFiles);
73
77
  } else if (entry.isFile()) {
74
78
  const ext = path.extname(entry.name);
@@ -94,6 +98,12 @@ async function walkDirectory(dir, options, rootDir) {
94
98
  continue;
95
99
  }
96
100
  }
101
+ const contentSizeKB = Buffer.byteLength(content, "utf-8") / 1024;
102
+ if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {
103
+ console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);
104
+ break;
105
+ }
106
+ cumulativeSizeKB.value += contentSizeKB;
97
107
  files.push({ path: fullPath, content, relativePath });
98
108
  }
99
109
  }
@@ -125,6 +135,7 @@ async function gatherCode(options = {}) {
125
135
  ".turbo"
126
136
  ],
127
137
  maxFileSizeKB: options.maxFileSizeKB || 500,
138
+ maxTotalSizeKB: options.maxTotalSizeKB || 100,
128
139
  focusMode: options.focusMode || "all"
129
140
  };
130
141
  const appPath = path.resolve(opts.appPath);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/inspect/node.ts"],"sourcesContent":["/**\n * Node.js Inspection Module\n *\n * Provides filesystem-based code inspection capabilities for analyzing web3 applications\n * from the server-side or during development. Scans application source code and generates\n * security reports via the Rukh API.\n *\n * @module inspect/node\n */\n\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\n/**\n * Configuration options for Node.js-based application inspection\n */\nexport interface InspectOptions {\n /**\n * The root directory of the application to inspect\n * @default process.cwd()\n */\n appPath?: string;\n\n /**\n * File patterns to include (glob patterns)\n * @default ['**\\/*.ts', '**\\/*.tsx', '**\\/*.js', '**\\/*.jsx', '**\\/*.json']\n */\n includePatterns?: string[];\n\n /**\n * Directories to exclude from inspection\n * @default ['node_modules', 'dist', '.next', '.git', 'build', 'coverage']\n */\n excludeDirs?: string[];\n\n /**\n * Maximum file size in KB to include\n * @default 500\n */\n maxFileSizeKB?: number;\n\n /**\n * Maximum total size in KB for all collected code (prevents exceeding API token limits)\n * @default 100\n */\n maxTotalSizeKB?: number;\n\n /**\n * Focus mode: filters files to only include those relevant to specific features\n * - 'transactions': Only files with signing, transactions, or blockchain operations\n * - 'all': Include all files (default)\n * @default 'all'\n */\n focusMode?: 'transactions' | 'all';\n}\n\n/**\n * Result of the code gathering operation\n */\nexport interface InspectResult {\n /**\n * The generated markdown content with all collected code\n */\n markdown: string;\n\n /**\n * List of files that were included\n */\n includedFiles: string[];\n\n /**\n * Total size in KB of all included files\n */\n totalSizeKB: number;\n}\n\n/**\n * Checks if file content is relevant to transactions/signing operations\n *\n * Scans file content and filename for keywords related to blockchain transactions,\n * message signing, smart contracts, and web3 operations.\n *\n * @param content - The file content to analyze\n * @param filename - The name of the file being checked\n * @returns True if the file contains transaction-related code\n * @internal\n */\nfunction isTransactionRelevant(content: string, filename: string): boolean {\n const transactionKeywords = [\n // Signing operations\n 'signMessage',\n 'signTypedData',\n 'sign(',\n 'signature',\n 'personalSign',\n 'eth_sign',\n 'sendTransaction',\n\n // Transaction operations\n 'transaction',\n 'sendTx',\n 'executeTx',\n 'Contract.connect',\n 'signer.send',\n 'wallet.send',\n 'provider.send',\n 'eth_sendTransaction',\n 'eth_sendRawTransaction',\n\n // Blockchain interaction\n 'Contract(',\n 'ethers.Contract',\n 'new Contract',\n 'ContractFactory',\n 'deployContract',\n\n // W3PK specific\n 'w3pk.sign',\n 'w3pk.send',\n 'Web3Passkey',\n 'useW3PK',\n\n // Smart contract calls\n 'call(',\n 'estimateGas',\n 'gasLimit',\n 'gasPrice',\n 'maxFeePerGas',\n 'maxPriorityFeePerGas',\n\n // Web3 providers\n 'JsonRpcProvider',\n 'Web3Provider',\n 'BrowserProvider',\n\n // EIP-7702\n 'EIP7702',\n 'authorization',\n 'delegation',\n ];\n\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check if filename suggests transaction relevance\n if (\n lowerFilename.includes('transaction') ||\n lowerFilename.includes('sign') ||\n lowerFilename.includes('wallet') ||\n lowerFilename.includes('contract') ||\n lowerFilename.includes('blockchain') ||\n lowerFilename.includes('web3') ||\n lowerFilename.includes('ethers')\n ) {\n return true;\n }\n\n // Check content for transaction-related keywords\n return transactionKeywords.some(keyword =>\n lowerContent.includes(keyword.toLowerCase())\n );\n}\n\n/**\n * Recursively walks a directory tree and collects matching files\n *\n * Traverses the directory structure, applying filters based on file patterns,\n * size limits, and focus mode settings.\n *\n * @param dir - The directory path to walk\n * @param options - Inspection options with filters and limits\n * @param rootDir - The root directory for calculating relative paths\n * @param cumulativeSizeKB - Tracks total size collected so far\n * @returns Array of file objects with path, content, and relative path\n * @internal\n */\nasync function walkDirectory(\n dir: string,\n options: Required<InspectOptions>,\n rootDir: string,\n cumulativeSizeKB: { value: number } = { value: 0 }\n): Promise<{ path: string; content: string; relativePath: string }[]> {\n const files: { path: string; content: string; relativePath: string }[] = [];\n\n try {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n // Check if we've reached the total size limit\n if (cumulativeSizeKB.value >= options.maxTotalSizeKB) {\n console.warn(`Reached total size limit of ${options.maxTotalSizeKB} KB - stopping collection`);\n break;\n }\n\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (entry.isDirectory()) {\n // Skip excluded directories\n if (options.excludeDirs.includes(entry.name)) {\n continue;\n }\n\n // Recursively walk subdirectory\n const subFiles = await walkDirectory(fullPath, options, rootDir, cumulativeSizeKB);\n files.push(...subFiles);\n } else if (entry.isFile()) {\n // Check if file matches include patterns\n const ext = path.extname(entry.name);\n const shouldInclude = options.includePatterns.some(pattern => {\n // Simple pattern matching for common extensions\n if (pattern.includes('*')) {\n const patternExt = pattern.split('.').pop();\n return ext === `.${patternExt}`;\n }\n return entry.name === pattern;\n });\n\n if (!shouldInclude) {\n continue;\n }\n\n // Check file size\n const stats = await fs.stat(fullPath);\n const fileSizeKB = stats.size / 1024;\n\n if (fileSizeKB > options.maxFileSizeKB) {\n console.warn(`Skipping ${relativePath} (${fileSizeKB.toFixed(2)} KB exceeds limit)`);\n continue;\n }\n\n // Read file content\n const content = await fs.readFile(fullPath, 'utf-8');\n\n // Apply focus mode filter if enabled\n if (options.focusMode === 'transactions') {\n if (!isTransactionRelevant(content, entry.name)) {\n continue;\n }\n }\n\n // Calculate content size\n const contentSizeKB = Buffer.byteLength(content, 'utf-8') / 1024;\n\n // Check if adding this file would exceed the total limit\n if (cumulativeSizeKB.value + contentSizeKB > options.maxTotalSizeKB) {\n console.warn(`Skipping ${relativePath} - would exceed total size limit (${(cumulativeSizeKB.value + contentSizeKB).toFixed(2)} KB > ${options.maxTotalSizeKB} KB)`);\n break;\n }\n\n // Add file and update cumulative size\n cumulativeSizeKB.value += contentSizeKB;\n files.push({ path: fullPath, content, relativePath });\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dir}:`, error);\n }\n\n return files;\n}\n\n/**\n * Gathers application code and generates a markdown document\n *\n * @param options - Configuration options for the inspection\n * @returns An object containing the markdown content and metadata\n *\n * @example\n * ```typescript\n * const result = await gatherCode({\n * appPath: '../genji-passkey',\n * includePatterns: ['**\\/*.ts', '**\\/*.tsx']\n * });\n *\n * console.log(`Collected ${result.includedFiles.length} files`);\n * console.log(`Total size: ${result.totalSizeKB} KB`);\n * ```\n */\nexport async function gatherCode(options: InspectOptions = {}): Promise<InspectResult> {\n const opts: Required<InspectOptions> = {\n appPath: options.appPath || process.cwd(),\n includePatterns: options.includePatterns || [\n '**/*.ts',\n '**/*.tsx',\n '**/*.js',\n '**/*.jsx',\n '**/*.json'\n ],\n excludeDirs: options.excludeDirs || [\n 'node_modules',\n 'dist',\n '.next',\n '.git',\n 'build',\n 'coverage',\n '.cache',\n 'out',\n '.vercel',\n '.turbo'\n ],\n maxFileSizeKB: options.maxFileSizeKB || 500,\n maxTotalSizeKB: options.maxTotalSizeKB || 100,\n focusMode: options.focusMode || 'all'\n };\n\n // Resolve app path\n const appPath = path.resolve(opts.appPath);\n\n // Check if app path exists\n try {\n await fs.access(appPath);\n } catch (error) {\n throw new Error(`App path does not exist: ${appPath}`);\n }\n\n // Walk directory and collect files\n const files = await walkDirectory(appPath, opts, appPath);\n\n // Generate markdown\n let markdown = `# Application Code Inspection\\n\\n`;\n markdown += `**Inspected Path:** \\`${appPath}\\`\\n\\n`;\n markdown += `**Files Collected:** ${files.length}\\n\\n`;\n markdown += `**Focus Mode:** ${opts.focusMode}\\n\\n`;\n markdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n markdown += `---\\n\\n`;\n\n if (opts.focusMode === 'transactions') {\n markdown += `## Analysis Focus\\n\\n`;\n markdown += `This inspection is focused on **transaction and signing operations**. Only files containing:\\n\\n`;\n markdown += `- Message signing (signMessage, signTypedData, etc.)\\n`;\n markdown += `- Transaction sending (sendTransaction, eth_sendTransaction, etc.)\\n`;\n markdown += `- Smart contract interactions (Contract calls, deployments)\\n`;\n markdown += `- Blockchain providers and wallets\\n`;\n markdown += `- W3PK signing/transaction methods\\n`;\n markdown += `- EIP-7702 authorization/delegation\\n\\n`;\n markdown += `---\\n\\n`;\n }\n\n // Add table of contents\n markdown += `## Table of Contents\\n\\n`;\n files.forEach(file => {\n markdown += `- [\\`${file.relativePath}\\`](#${file.relativePath.replace(/[^a-z0-9]/gi, '-').toLowerCase()})\\n`;\n });\n markdown += `\\n---\\n\\n`;\n\n // Add file contents\n markdown += `## Files\\n\\n`;\n\n for (const file of files) {\n const ext = path.extname(file.relativePath).slice(1);\n const language = ext === 'tsx' || ext === 'ts' ? 'typescript' :\n ext === 'jsx' || ext === 'js' ? 'javascript' :\n ext === 'json' ? 'json' : ext;\n\n markdown += `### \\`${file.relativePath}\\`\\n\\n`;\n markdown += '```' + language + '\\n';\n markdown += file.content;\n markdown += '\\n```\\n\\n';\n markdown += `---\\n\\n`;\n }\n\n // Calculate total size\n const totalSizeKB = files.reduce((sum, file) => {\n return sum + Buffer.byteLength(file.content, 'utf-8') / 1024;\n }, 0);\n\n return {\n markdown,\n includedFiles: files.map(f => f.relativePath),\n totalSizeKB: Math.round(totalSizeKB * 100) / 100\n };\n}\n\n/**\n * Inspects an application and returns a security report via Rukh API\n *\n * @param appPath - Path to the application to inspect\n * @param rukhUrl - The Rukh API endpoint URL\n * @param context - The context name to use (should have an instruction file with report format)\n * @param model - The AI model to use ('anthropic', 'mistral', or 'openai')\n * @param focusMode - Focus on 'transactions' or include 'all' files\n * @returns A markdown-formatted security report\n *\n * @example\n * ```typescript\n * const report = await inspect(\n * '../genji-passkey',\n * 'https://rukh.w3hc.org',\n * 'w3pk',\n * 'anthropic',\n * 'transactions'\n * );\n *\n * console.log(report);\n * // Outputs:\n * // # Genji Passkey Report\n * // ## Available Methods\n * // ### Method #1: Sign Message\n * // ...\n * ```\n */\nexport async function inspect(\n appPath: string,\n rukhUrl: string = 'https://rukh.w3hc.org',\n context: string = 'w3pk',\n model: 'anthropic' | 'mistral' | 'openai' = 'anthropic',\n focusMode: 'transactions' | 'all' = 'transactions'\n): Promise<string> {\n // Gather application code with focus mode\n const result = await gatherCode({ appPath, focusMode });\n\n console.log(`Inspected ${result.includedFiles.length} files (${result.totalSizeKB} KB) [Focus: ${focusMode}]`);\n\n // Default message that triggers the structured report\n const message = 'Analyze this application and provide a security report listing all transaction and signing methods.';\n\n // Prepare form data\n const formData = new FormData();\n formData.append('message', message);\n formData.append('model', model);\n formData.append('context', context);\n\n // Create a blob from the markdown content\n const blob = new Blob([result.markdown], { type: 'text/markdown' });\n formData.append('file', blob, 'app-code.md');\n\n // Send request to Rukh API\n const response = await fetch(`${rukhUrl}/ask`, {\n method: 'POST',\n body: formData\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Rukh API error: ${response.status} - ${error}`);\n }\n\n const data = await response.json();\n return data.response || data.message || JSON.stringify(data);\n}\n"],"mappings":"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","names":[]}
@@ -20,6 +20,7 @@ Complete reference for all methods, types, and utilities in the w3pk SDK.
20
20
  - [Standalone Utilities](#standalone-utilities)
21
21
  - [Validation Utilities](#validation-utilities)
22
22
  - [Build Verification Utilities](#build-verification-utilities)
23
+ - [Post-Quantum Cryptography](#post-quantum-cryptography)
23
24
  - [Wallet Generation Utilities](#wallet-generation-utilities)
24
25
  - [Security Inspection](#security-inspection)
25
26
  - [Browser Inspection](#browser-inspection)
@@ -1613,6 +1614,8 @@ const w3pk = createWeb3Passkey({
1613
1614
 
1614
1615
  All stealth address methods are accessed via `w3pk.stealth.*`
1615
1616
 
1617
+ > **🔒 Security Note:** The ERC-5564 implementation properly reduces all scalars modulo the secp256k1 curve order to prevent unspendable funds. The same reduced scalar is used consistently in both public-key operations (`s_h × G`) and private-key operations (`spendingKey + s_h`) to ensure the derived stealth address always matches the derived private key.
1618
+
1616
1619
  ### `stealth.generateStealthAddress(options?: { requireAuth?: boolean }): Promise<StealthAddressResult>`
1617
1620
 
1618
1621
  Generate a fresh ERC-5564 compliant stealth address for a recipient.
@@ -3072,7 +3075,7 @@ Compute IPFS CIDv1 hash for the currently installed w3pk version from unpkg CDN.
3072
3075
  ```typescript
3073
3076
  const hash = await getCurrentBuildHash()
3074
3077
  console.log('Build hash:', hash)
3075
- // => bafybeiafdhdxz3c3nhxtrhe7zpxfco5dlywpvzzscl277hojn7zosmrob4
3078
+ // => bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy
3076
3079
  ```
3077
3080
 
3078
3081
  ---
@@ -3207,7 +3210,89 @@ console.log(encoded) // => SGVsbG8gV29ybGQ=
3207
3210
  ### Cryptographic Utilities
3208
3211
 
3209
3212
  ```typescript
3210
- import { extractRS } from 'w3pk'
3213
+ import {
3214
+ extractRS,
3215
+ deriveEncryptionKeyFromWebAuthn,
3216
+ generateSalt
3217
+ } from 'w3pk'
3218
+ ```
3219
+
3220
+ #### `deriveEncryptionKeyFromWebAuthn(prfOutput: ArrayBuffer, salt: Uint8Array): Promise<CryptoKey>`
3221
+
3222
+ **🔐 SECURE - WebAuthn PRF-based encryption**
3223
+
3224
+ Derives an AES-256-GCM encryption key from WebAuthn PRF (Pseudo-Random Function) extension output.
3225
+
3226
+ **Security:**
3227
+ - Uses authenticator-held secrets via PRF extension (never exposed)
3228
+ - Implements random unique salts (no precomputation attacks)
3229
+ - PBKDF2 with 210,000 iterations (OWASP 2023 recommendation)
3230
+ - Prevents offline decryption attacks
3231
+
3232
+ **Parameters:**
3233
+ - `prfOutput: ArrayBuffer` - 32-byte secret from WebAuthn PRF extension
3234
+ - `salt: Uint8Array` - 32-byte random salt (generate with `generateSalt()`)
3235
+
3236
+ **Returns:** `Promise<CryptoKey>` - AES-256-GCM key for encryption/decryption
3237
+
3238
+ **Example:**
3239
+ ```typescript
3240
+ // 1. Enable PRF during WebAuthn registration
3241
+ const credential = await navigator.credentials.create({
3242
+ publicKey: {
3243
+ challenge: new Uint8Array(32),
3244
+ rp: { name: "Example" },
3245
+ user: { id: userId, name: "user", displayName: "User" },
3246
+ pubKeyCredParams: [{ alg: -7, type: "public-key" }],
3247
+ extensions: {
3248
+ prf: {} // Enable PRF extension
3249
+ }
3250
+ }
3251
+ });
3252
+
3253
+ // 2. Get PRF output during authentication
3254
+ const assertion = await navigator.credentials.get({
3255
+ publicKey: {
3256
+ challenge: new Uint8Array(32),
3257
+ extensions: {
3258
+ prf: {
3259
+ eval: {
3260
+ first: new Uint8Array(32) // Salt for PRF
3261
+ }
3262
+ }
3263
+ }
3264
+ }
3265
+ });
3266
+
3267
+ const prfOutput = assertion.getClientExtensionResults().prf.results.first;
3268
+
3269
+ // 3. Generate and store salt
3270
+ const salt = generateSalt(); // Store this with your ciphertext
3271
+
3272
+ // 4. Derive encryption key
3273
+ const encryptionKey = await deriveEncryptionKeyFromWebAuthn(
3274
+ prfOutput,
3275
+ salt
3276
+ );
3277
+
3278
+ // 5. Encrypt sensitive data
3279
+ const encrypted = await crypto.subtle.encrypt(
3280
+ { name: "AES-GCM", iv: crypto.getRandomValues(new Uint8Array(12)) },
3281
+ encryptionKey,
3282
+ new TextEncoder().encode("secret data")
3283
+ );
3284
+ ```
3285
+
3286
+ #### `generateSalt(): Uint8Array`
3287
+
3288
+ Generates a cryptographically secure random 32-byte salt for encryption.
3289
+
3290
+ **Returns:** `Uint8Array` - 32 random bytes
3291
+
3292
+ **Example:**
3293
+ ```typescript
3294
+ const salt = generateSalt();
3295
+ // Store salt alongside ciphertext for later decryption
3211
3296
  ```
3212
3297
 
3213
3298
  #### `extractRS(derSignature: Uint8Array): { r: string; s: string }`
@@ -3265,6 +3350,189 @@ This ensures compatibility with Ethereum's signature malleability protection.
3265
3350
 
3266
3351
  ---
3267
3352
 
3353
+ ### Post-Quantum Cryptography
3354
+
3355
+ w3pk provides ML-KEM-1024 encryption both as instance methods (recommended) and standalone functions.
3356
+
3357
+ #### Instance Methods (Recommended)
3358
+
3359
+ Use these methods with your w3pk wallet instance - no private key exposure:
3360
+
3361
+ ##### `w3pk.deriveMLKemPublicKey(options?): Promise<string>`
3362
+
3363
+ Derive your ML-KEM-1024 public key for sharing with others.
3364
+
3365
+ **Parameters:**
3366
+ ```typescript
3367
+ {
3368
+ context?: string; // Domain separation (default: 'mlkem-v1')
3369
+ mode?: SecurityMode; // 'STANDARD', 'STRICT', or 'YOLO' (default: 'STANDARD')
3370
+ tag?: string; // Address derivation tag (default: 'MAIN')
3371
+ origin?: string; // Origin (default: current origin)
3372
+ requireAuth?: boolean; // Force re-auth (default: false, always true in STRICT)
3373
+ }
3374
+ ```
3375
+
3376
+ **Returns:** Base64-encoded public key (1568 bytes)
3377
+
3378
+ **Example:**
3379
+ ```typescript
3380
+ const w3pk = createWeb3Passkey();
3381
+ await w3pk.login();
3382
+
3383
+ const myPubKey = await w3pk.deriveMLKemPublicKey();
3384
+ // Share myPubKey with others
3385
+ ```
3386
+
3387
+ **Note:** PRIMARY mode not supported (uses P-256 WebAuthn keys).
3388
+
3389
+ ---
3390
+
3391
+ ##### `w3pk.mlkemEncrypt(plaintext: string, recipientPublicKeys: Array<string | Uint8Array>, options?): Promise<EncryptedPayload>`
3392
+
3393
+ Encrypt data for yourself and additional recipients.
3394
+
3395
+ **Parameters:**
3396
+ ```typescript
3397
+ {
3398
+ context?: string; // Domain separation (default: 'mlkem-v1')
3399
+ mode?: SecurityMode; // Security mode (default: 'STANDARD')
3400
+ tag?: string; // Tag (default: 'MAIN')
3401
+ origin?: string; // Origin (default: current)
3402
+ requireAuth?: boolean; // Force re-auth (default: false)
3403
+ }
3404
+ ```
3405
+
3406
+ **Example:**
3407
+ ```typescript
3408
+ const serverPubKey = await fetch('/api/mlkem-key').then(r => r.text());
3409
+
3410
+ const encrypted = await w3pk.mlkemEncrypt(
3411
+ 'my secret data',
3412
+ [serverPubKey] // You + server can both decrypt
3413
+ );
3414
+ ```
3415
+
3416
+ ---
3417
+
3418
+ ##### `w3pk.mlkemDecrypt(payload: EncryptedPayload, options?): Promise<string>`
3419
+
3420
+ Decrypt data encrypted for your wallet.
3421
+
3422
+ **Parameters:** Same options as `mlkemEncrypt`
3423
+
3424
+ **Example:**
3425
+ ```typescript
3426
+ const plaintext = await w3pk.mlkemDecrypt(encrypted);
3427
+ ```
3428
+
3429
+ ---
3430
+
3431
+ #### Standalone Functions
3432
+
3433
+ Low-level functions for direct key management:
3434
+
3435
+ ```typescript
3436
+ import { mlkemEncrypt, mlkemDecrypt, deriveMLKemKeypair, type EncryptedPayload } from 'w3pk'
3437
+ ```
3438
+
3439
+ ##### `deriveMLKemKeypair(privateKey: string | Uint8Array, context?: string): Promise<MLKemKeypair>`
3440
+
3441
+ Derive deterministic ML-KEM keypair from any private key using HKDF-SHA256.
3442
+
3443
+ **Parameters:**
3444
+ - `privateKey` - Ethereum private key (hex with optional `0x` prefix or Uint8Array)
3445
+ - `context` - Context string for domain separation (default: `'mlkem-v1'`)
3446
+
3447
+ **Returns:**
3448
+ ```typescript
3449
+ {
3450
+ publicKey: Uint8Array; // 1568 bytes
3451
+ privateKey: Uint8Array; // 3168 bytes
3452
+ }
3453
+ ```
3454
+
3455
+ **Example:**
3456
+ ```typescript
3457
+ const keypair = await deriveMLKemKeypair('0x1234...', 'my-app');
3458
+ ```
3459
+
3460
+ ---
3461
+
3462
+ ##### `mlkemEncrypt(plaintext: string, publicKeys: (string | Uint8Array) | Array<string | Uint8Array>): Promise<EncryptedPayload>`
3463
+
3464
+ Encrypt data using ML-KEM-1024 (post-quantum KEM) + AES-256-GCM for one or more recipients.
3465
+
3466
+ **Parameters:**
3467
+ - `plaintext: string` - The data to encrypt
3468
+ - `publicKeys: (string | Uint8Array) | Array<string | Uint8Array>` - Single public key or array of ML-KEM-1024 public keys (1568 bytes each)
3469
+
3470
+ **Returns:**
3471
+ ```typescript
3472
+ {
3473
+ recipients: Array<{
3474
+ publicKey: string; // Base64 recipient public key (1568 bytes)
3475
+ ciphertext: string; // Base64 ML-KEM ciphertext for this recipient (1600 bytes)
3476
+ }>;
3477
+ encryptedData: string; // Base64 AES-encrypted data (shared across all recipients)
3478
+ iv: string; // Base64 initialization vector (12 bytes)
3479
+ authTag: string; // Base64 authentication tag (16 bytes)
3480
+ }
3481
+ ```
3482
+
3483
+ **Example:**
3484
+
3485
+ ```typescript
3486
+ import { mlkemEncrypt } from 'w3pk'
3487
+
3488
+ // Single recipient
3489
+ const encrypted = await mlkemEncrypt('my secret data', publicKey1)
3490
+
3491
+ // Multiple recipients
3492
+ const encrypted = await mlkemEncrypt('my secret data', [publicKey1, publicKey2, publicKey3])
3493
+
3494
+ console.log('Recipients:', encrypted.recipients.length)
3495
+ ```
3496
+
3497
+ **Security:**
3498
+ - ✅ **Quantum-resistant** - ML-KEM-1024 (NIST FIPS 203)
3499
+ - ✅ **Multi-recipient support** - Encrypt once for multiple recipients
3500
+ - ✅ **Key zeroization** - All secrets securely wiped from memory
3501
+ - ✅ **Authenticated encryption** - AES-256-GCM with 128-bit auth tag
3502
+
3503
+ ---
3504
+
3505
+ #### `mlkemDecrypt(payload: EncryptedPayload, privateKey: string | Uint8Array, publicKey?: string | Uint8Array): Promise<string>`
3506
+
3507
+ Decrypt data encrypted with `mlkemEncrypt()`.
3508
+
3509
+ **Parameters:**
3510
+ - `payload: EncryptedPayload` - The encrypted payload from `mlkemEncrypt()`
3511
+ - `privateKey: string | Uint8Array` - ML-KEM-1024 private key (3168 bytes)
3512
+ - `publicKey?: string | Uint8Array` - (Optional) Your public key for faster recipient lookup (1568 bytes)
3513
+
3514
+ **Returns:** `string` - Decrypted plaintext
3515
+
3516
+ **Example:**
3517
+
3518
+ ```typescript
3519
+ import { mlkemDecrypt } from 'w3pk'
3520
+
3521
+ // Auto-detect which recipient you are
3522
+ const plaintext = await mlkemDecrypt(encrypted, privateKey)
3523
+
3524
+ // Or specify your public key for faster lookup
3525
+ const plaintext = await mlkemDecrypt(encrypted, privateKey, myPublicKey)
3526
+ ```
3527
+
3528
+ **Throws:** Error if decryption fails (invalid key, no matching recipient, corrupted data, or tampered auth tag)
3529
+
3530
+ **Related:**
3531
+ - See [POST_QUANTUM.md](../docs/POST_QUANTUM.md) for full quantum readiness roadmap
3532
+ - See [NIST FIPS 203](https://csrc.nist.gov/pubs/fips/203/final) for ML-KEM specification
3533
+
3534
+ ---
3535
+
3268
3536
  ### Wallet Generation Utilities
3269
3537
 
3270
3538
  ```typescript