ruvector 0.2.19 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -8923,5 +8923,144 @@ routeCmd.command('info')
8923
8923
  console.log('');
8924
8924
  });
8925
8925
 
8926
+ // ── Decompile Command ──────────────────────────────────────────────────────
8927
+ const decompileCmd = program
8928
+ .command('decompile [target]')
8929
+ .description('Decompile npm packages, local JS files, or URLs into modules')
8930
+ .option('-o, --output <dir>', 'Output directory')
8931
+ .option('-f, --format <type>', 'Output format: modules, single, json', 'modules')
8932
+ .option('-c, --confidence <n>', 'Minimum confidence threshold (0-1)', '0.3')
8933
+ .option('--no-witness', 'Skip witness chain generation')
8934
+ .option('--json', 'JSON output to stdout (for piping)')
8935
+ .option('-q, --quiet', 'Suppress progress output')
8936
+ .option('--version-pkg <ver>', 'Package version (alternative to @version syntax)')
8937
+ .option('--diff <version>', 'Compare against another version')
8938
+ .action(async (target, opts) => {
8939
+ if (!target) {
8940
+ console.log(chalk.cyan('\nUsage:'));
8941
+ console.log(chalk.white(' ruvector decompile <package> Decompile npm package'));
8942
+ console.log(chalk.white(' ruvector decompile <pkg>@<ver> Specific version'));
8943
+ console.log(chalk.white(' ruvector decompile ./bundle.js Local file'));
8944
+ console.log(chalk.white(' ruvector decompile https://unpkg.com/x URL'));
8945
+ console.log(chalk.dim('\nOptions:'));
8946
+ console.log(chalk.dim(' -o, --output <dir> Output directory'));
8947
+ console.log(chalk.dim(' -f, --format <type> modules | single | json'));
8948
+ console.log(chalk.dim(' -c, --confidence <n> Min confidence (0-1, default: 0.3)'));
8949
+ console.log(chalk.dim(' --no-witness Skip witness chain'));
8950
+ console.log(chalk.dim(' --json JSON to stdout'));
8951
+ console.log(chalk.dim(' --diff <version> Diff against another version'));
8952
+ console.log('');
8953
+ return;
8954
+ }
8955
+
8956
+ const decompiler = require('../src/decompiler/index.js');
8957
+ const { parseTarget } = decompiler;
8958
+ const parsed = parseTarget(target);
8959
+ const minConfidence = parseFloat(opts.confidence);
8960
+ const decompileOpts = { minConfidence, witness: opts.witness !== false, useRust: true };
8961
+ const quiet = opts.quiet || opts.json;
8962
+ let spinner = null;
8963
+
8964
+ if (!quiet) {
8965
+ spinner = ora('Analyzing target...').start();
8966
+ }
8967
+
8968
+ try {
8969
+ let result;
8970
+
8971
+ if (parsed.type === 'npm') {
8972
+ const version = opts.versionPkg || parsed.version;
8973
+ if (!quiet) spinner.text = `Fetching ${parsed.name}${version ? '@' + version : ''}...`;
8974
+ result = await decompiler.decompilePackage(parsed.name, version, decompileOpts);
8975
+ if (!quiet) spinner.text = `Decompiled ${result.packageInfo.name}@${result.packageInfo.version}`;
8976
+ } else if (parsed.type === 'file') {
8977
+ if (!quiet) spinner.text = `Reading ${parsed.path}...`;
8978
+ result = decompiler.decompileFile(parsed.path, { ...decompileOpts, filePath: parsed.path });
8979
+ } else if (parsed.type === 'url') {
8980
+ if (!quiet) spinner.text = `Fetching ${parsed.url}...`;
8981
+ result = await decompiler.decompileUrl(parsed.url, decompileOpts);
8982
+ }
8983
+
8984
+ if (!quiet) spinner.succeed(chalk.green('Decompilation complete'));
8985
+
8986
+ // Handle --diff flag
8987
+ if (opts.diff && parsed.type === 'npm') {
8988
+ if (!quiet) {
8989
+ const diffSpinner = ora(`Fetching ${parsed.name}@${opts.diff} for diff...`).start();
8990
+ try {
8991
+ const other = await decompiler.decompilePackage(parsed.name, opts.diff, decompileOpts);
8992
+ diffSpinner.succeed('Diff complete');
8993
+ const resultNames = new Set(result.modules.map((m) => m.name));
8994
+ const otherNames = new Set(other.modules.map((m) => m.name));
8995
+ const added = [...resultNames].filter((n) => !otherNames.has(n));
8996
+ const removed = [...otherNames].filter((n) => !resultNames.has(n));
8997
+ const common = [...resultNames].filter((n) => otherNames.has(n));
8998
+
8999
+ console.log(chalk.bold.cyan('\n Version Diff'));
9000
+ console.log(chalk.white(` ${opts.diff} -> ${result.packageInfo.version}`));
9001
+ if (added.length) console.log(chalk.green(` Added: ${added.join(', ')}`));
9002
+ if (removed.length) console.log(chalk.red(` Removed: ${removed.join(', ')}`));
9003
+ console.log(chalk.dim(` Common: ${common.length} modules`));
9004
+ console.log('');
9005
+ } catch (err) {
9006
+ diffSpinner.fail(`Diff failed: ${err.message}`);
9007
+ }
9008
+ }
9009
+ }
9010
+
9011
+ // Output
9012
+ if (opts.json) {
9013
+ const jsonOut = {
9014
+ modules: result.modules.map((m) => ({
9015
+ name: m.name, fragments: m.fragments, confidence: m.confidence,
9016
+ contentLength: m.content.length,
9017
+ })),
9018
+ metrics: result.metrics,
9019
+ witness: result.witness ? { root: result.witness.root, chain_length: result.witness.chain.length } : null,
9020
+ packageInfo: result.packageInfo || null,
9021
+ };
9022
+ console.log(JSON.stringify(jsonOut, null, 2));
9023
+ return;
9024
+ }
9025
+
9026
+ // Determine output directory
9027
+ let outputDir = opts.output;
9028
+ if (!outputDir) {
9029
+ const baseName = result.packageInfo
9030
+ ? `${result.packageInfo.name.replace('/', '-')}@${result.packageInfo.version}`
9031
+ : path.basename(target, '.js');
9032
+ outputDir = path.join(process.cwd(), 'decompiled', baseName);
9033
+ }
9034
+
9035
+ decompiler.writeOutput(result, outputDir, opts.format);
9036
+
9037
+ console.log(chalk.bold.cyan('\n Decompilation Summary'));
9038
+ console.log(chalk.white(` Modules: ${result.modules.length}`));
9039
+ console.log(chalk.white(` Source size: ${(result.metrics.source.sizeBytes / 1024).toFixed(1)} KB`));
9040
+ console.log(chalk.white(` Functions: ${result.metrics.source.functions}`));
9041
+ console.log(chalk.white(` Classes: ${result.metrics.source.classes}`));
9042
+ if (result.witness) {
9043
+ const wRoot = result.witness.root || result.witness.chain_root || '';
9044
+ console.log(chalk.white(` Witness root: ${wRoot.slice(0, 16)}...`));
9045
+ }
9046
+ console.log(chalk.green(` Output: ${outputDir}`));
9047
+ console.log('');
9048
+
9049
+ if (result.modules.length > 0) {
9050
+ console.log(chalk.dim(' Detected modules:'));
9051
+ for (const mod of result.modules) {
9052
+ const conf = (mod.confidence * 100).toFixed(0);
9053
+ console.log(chalk.dim(` ${mod.name} (${mod.fragments} fragments, ${conf}% confidence)`));
9054
+ }
9055
+ console.log('');
9056
+ }
9057
+ } catch (err) {
9058
+ if (spinner) spinner.fail(chalk.red('Decompilation failed'));
9059
+ console.error(chalk.red(` ${err.message}`));
9060
+ process.exit(1);
9061
+ }
9062
+ });
9063
+
8926
9064
  program.parse();
8927
9065
 
9066
+
package/bin/mcp-server.js CHANGED
@@ -1464,6 +1464,84 @@ const TOOLS = [
1464
1464
  },
1465
1465
  required: []
1466
1466
  }
1467
+ },
1468
+
1469
+ // ── Decompiler Tools ───────────────────────────────────────────────────
1470
+ {
1471
+ name: 'decompile_package',
1472
+ description: 'Decompile an npm package. Fetches from registry, extracts bundle, splits into modules, computes metrics and witness chain.',
1473
+ inputSchema: {
1474
+ type: 'object',
1475
+ properties: {
1476
+ package: { type: 'string', description: 'npm package name (e.g. "express", "@anthropic-ai/claude-code")' },
1477
+ version: { type: 'string', description: 'Version (default: latest)' },
1478
+ min_confidence: { type: 'number', description: 'Minimum confidence threshold (0-1, default: 0.3)' }
1479
+ },
1480
+ required: ['package']
1481
+ }
1482
+ },
1483
+ {
1484
+ name: 'decompile_file',
1485
+ description: 'Decompile a local JavaScript file. Beautifies, splits into modules, computes metrics.',
1486
+ inputSchema: {
1487
+ type: 'object',
1488
+ properties: {
1489
+ path: { type: 'string', description: 'Path to .js file' },
1490
+ min_confidence: { type: 'number', description: 'Minimum confidence threshold (0-1, default: 0.3)' }
1491
+ },
1492
+ required: ['path']
1493
+ }
1494
+ },
1495
+ {
1496
+ name: 'decompile_url',
1497
+ description: 'Decompile JavaScript from a URL (unpkg, CDN, raw GitHub, etc).',
1498
+ inputSchema: {
1499
+ type: 'object',
1500
+ properties: {
1501
+ url: { type: 'string', description: 'URL to fetch JavaScript from' },
1502
+ min_confidence: { type: 'number', description: 'Minimum confidence threshold (0-1, default: 0.3)' }
1503
+ },
1504
+ required: ['url']
1505
+ }
1506
+ },
1507
+ {
1508
+ name: 'decompile_search',
1509
+ description: 'Search decompiled code for patterns, function names, or string literals.',
1510
+ inputSchema: {
1511
+ type: 'object',
1512
+ properties: {
1513
+ query: { type: 'string', description: 'Search query (regex supported)' },
1514
+ package: { type: 'string', description: 'npm package to decompile and search' },
1515
+ version: { type: 'string', description: 'Package version (default: latest)' },
1516
+ path: { type: 'string', description: 'Local file path to decompile and search (alternative to package)' }
1517
+ },
1518
+ required: ['query']
1519
+ }
1520
+ },
1521
+ {
1522
+ name: 'decompile_diff',
1523
+ description: 'Compare decompiled output between two versions of an npm package. Shows added/removed/changed modules.',
1524
+ inputSchema: {
1525
+ type: 'object',
1526
+ properties: {
1527
+ package: { type: 'string', description: 'npm package name' },
1528
+ version_a: { type: 'string', description: 'First version' },
1529
+ version_b: { type: 'string', description: 'Second version' }
1530
+ },
1531
+ required: ['package', 'version_a', 'version_b']
1532
+ }
1533
+ },
1534
+ {
1535
+ name: 'decompile_witness',
1536
+ description: 'Verify the cryptographic witness chain of a decompilation. Proves output derives faithfully from input.',
1537
+ inputSchema: {
1538
+ type: 'object',
1539
+ properties: {
1540
+ witness_path: { type: 'string', description: 'Path to witness.json file' },
1541
+ source_path: { type: 'string', description: 'Path to original bundle (optional, for source hash verification)' }
1542
+ },
1543
+ required: ['witness_path']
1544
+ }
1467
1545
  }
1468
1546
  ];
1469
1547
 
@@ -3417,6 +3495,197 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3417
3495
  return { content: [{ type: 'text', text: JSON.stringify({ success: true, pseudonym, mcp_token: mcpToken, key_prefix: key.slice(0, 8) + '...' }, null, 2) }] };
3418
3496
  }
3419
3497
 
3498
+ // ── Decompiler Tool Handlers ─────────────────────────────────────────
3499
+ case 'decompile_package': {
3500
+ const decompiler = require('../src/decompiler/index.js');
3501
+ const result = await decompiler.decompilePackage(
3502
+ args.package,
3503
+ args.version || undefined,
3504
+ { minConfidence: args.min_confidence || 0.3 }
3505
+ );
3506
+ return {
3507
+ content: [{
3508
+ type: 'text',
3509
+ text: JSON.stringify({
3510
+ success: true,
3511
+ packageInfo: result.packageInfo,
3512
+ modules: result.modules.map(m => ({
3513
+ name: m.name, fragments: m.fragments, confidence: m.confidence,
3514
+ contentPreview: m.content.slice(0, 500) + (m.content.length > 500 ? '...' : ''),
3515
+ })),
3516
+ metrics: result.metrics,
3517
+ witness_root: result.witness ? result.witness.root : null,
3518
+ }, null, 2)
3519
+ }]
3520
+ };
3521
+ }
3522
+
3523
+ case 'decompile_file': {
3524
+ const decompiler = require('../src/decompiler/index.js');
3525
+ const safePath = validateRvfPath(args.path);
3526
+ const result = decompiler.decompileFile(safePath, {
3527
+ minConfidence: args.min_confidence || 0.3
3528
+ });
3529
+ return {
3530
+ content: [{
3531
+ type: 'text',
3532
+ text: JSON.stringify({
3533
+ success: true,
3534
+ filePath: result.filePath,
3535
+ modules: result.modules.map(m => ({
3536
+ name: m.name, fragments: m.fragments, confidence: m.confidence,
3537
+ contentPreview: m.content.slice(0, 500) + (m.content.length > 500 ? '...' : ''),
3538
+ })),
3539
+ metrics: result.metrics,
3540
+ witness_root: result.witness ? result.witness.root : null,
3541
+ }, null, 2)
3542
+ }]
3543
+ };
3544
+ }
3545
+
3546
+ case 'decompile_url': {
3547
+ const decompiler = require('../src/decompiler/index.js');
3548
+ const urlStr = args.url;
3549
+ // Basic URL validation
3550
+ if (!urlStr.startsWith('http://') && !urlStr.startsWith('https://')) {
3551
+ throw new Error('URL must start with http:// or https://');
3552
+ }
3553
+ const result = await decompiler.decompileUrl(urlStr, {
3554
+ minConfidence: args.min_confidence || 0.3
3555
+ });
3556
+ return {
3557
+ content: [{
3558
+ type: 'text',
3559
+ text: JSON.stringify({
3560
+ success: true,
3561
+ url: result.url,
3562
+ modules: result.modules.map(m => ({
3563
+ name: m.name, fragments: m.fragments, confidence: m.confidence,
3564
+ contentPreview: m.content.slice(0, 500) + (m.content.length > 500 ? '...' : ''),
3565
+ })),
3566
+ metrics: result.metrics,
3567
+ witness_root: result.witness ? result.witness.root : null,
3568
+ }, null, 2)
3569
+ }]
3570
+ };
3571
+ }
3572
+
3573
+ case 'decompile_search': {
3574
+ const decompiler = require('../src/decompiler/index.js');
3575
+ let result;
3576
+ if (args.path) {
3577
+ const safePath = validateRvfPath(args.path);
3578
+ result = decompiler.decompileFile(safePath);
3579
+ } else if (args.package) {
3580
+ result = await decompiler.decompilePackage(args.package, args.version || undefined);
3581
+ } else {
3582
+ throw new Error('Either "package" or "path" must be provided');
3583
+ }
3584
+
3585
+ const query = args.query;
3586
+ let regex;
3587
+ try { regex = new RegExp(query, 'gi'); } catch { regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); }
3588
+
3589
+ const matches = [];
3590
+ for (const mod of result.modules) {
3591
+ const lines = mod.content.split('\n');
3592
+ for (let i = 0; i < lines.length; i++) {
3593
+ if (regex.test(lines[i])) {
3594
+ matches.push({
3595
+ module: mod.name,
3596
+ line: i + 1,
3597
+ content: lines[i].trim().slice(0, 200),
3598
+ });
3599
+ regex.lastIndex = 0;
3600
+ }
3601
+ if (matches.length >= 50) break;
3602
+ }
3603
+ if (matches.length >= 50) break;
3604
+ }
3605
+
3606
+ return {
3607
+ content: [{
3608
+ type: 'text',
3609
+ text: JSON.stringify({
3610
+ success: true,
3611
+ query,
3612
+ total_matches: matches.length,
3613
+ matches,
3614
+ }, null, 2)
3615
+ }]
3616
+ };
3617
+ }
3618
+
3619
+ case 'decompile_diff': {
3620
+ const decompiler = require('../src/decompiler/index.js');
3621
+ const [resultA, resultB] = await Promise.all([
3622
+ decompiler.decompilePackage(args.package, args.version_a),
3623
+ decompiler.decompilePackage(args.package, args.version_b),
3624
+ ]);
3625
+
3626
+ const namesA = new Set(resultA.modules.map(m => m.name));
3627
+ const namesB = new Set(resultB.modules.map(m => m.name));
3628
+ const added = [...namesB].filter(n => !namesA.has(n));
3629
+ const removed = [...namesA].filter(n => !namesB.has(n));
3630
+ const common = [...namesA].filter(n => namesB.has(n));
3631
+
3632
+ // Compare declarations in common modules
3633
+ const changedDeclarations = [];
3634
+ for (const name of common) {
3635
+ const modA = resultA.modules.find(m => m.name === name);
3636
+ const modB = resultB.modules.find(m => m.name === name);
3637
+ if (modA && modB && modA.content !== modB.content) {
3638
+ changedDeclarations.push({
3639
+ module: name,
3640
+ sizeChange: modB.content.length - modA.content.length,
3641
+ fragmentsA: modA.fragments,
3642
+ fragmentsB: modB.fragments,
3643
+ });
3644
+ }
3645
+ }
3646
+
3647
+ return {
3648
+ content: [{
3649
+ type: 'text',
3650
+ text: JSON.stringify({
3651
+ success: true,
3652
+ package: args.package,
3653
+ version_a: args.version_a,
3654
+ version_b: args.version_b,
3655
+ added_modules: added,
3656
+ removed_modules: removed,
3657
+ common_modules: common.length,
3658
+ changed_declarations: changedDeclarations,
3659
+ metrics_a: resultA.metrics.source,
3660
+ metrics_b: resultB.metrics.source,
3661
+ }, null, 2)
3662
+ }]
3663
+ };
3664
+ }
3665
+
3666
+ case 'decompile_witness': {
3667
+ const decompiler = require('../src/decompiler/index.js');
3668
+ const witnessPath = validateRvfPath(args.witness_path);
3669
+ const witnessData = JSON.parse(fs.readFileSync(witnessPath, 'utf-8'));
3670
+
3671
+ let sourceContent = undefined;
3672
+ if (args.source_path) {
3673
+ const sourcePath = validateRvfPath(args.source_path);
3674
+ sourceContent = fs.readFileSync(sourcePath, 'utf-8');
3675
+ }
3676
+
3677
+ const verification = decompiler.verifyWitnessChain(witnessData, sourceContent);
3678
+ return {
3679
+ content: [{
3680
+ type: 'text',
3681
+ text: JSON.stringify({
3682
+ success: true,
3683
+ ...verification,
3684
+ }, null, 2)
3685
+ }]
3686
+ };
3687
+ }
3688
+
3420
3689
  default:
3421
3690
  return {
3422
3691
  content: [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvector",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "description": "Self-learning vector database for Node.js — hybrid search, Graph RAG, FlashAttention-3, DiskANN, 50+ attention mechanisms",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -76,6 +76,7 @@
76
76
  "@ruvector/sona": "^0.1.4",
77
77
  "chalk": "^4.1.2",
78
78
  "commander": "^11.1.0",
79
+ "js-beautify": "^1.15.4",
79
80
  "ora": "^5.4.1"
80
81
  },
81
82
  "optionalDependencies": {
@@ -88,18 +89,25 @@
88
89
  "files": [
89
90
  "bin/",
90
91
  "dist/",
92
+ "src/decompiler/",
91
93
  "README.md",
92
94
  "LICENSE"
93
95
  ],
94
96
  "peerDependencies": {
95
97
  "@ruvector/pi-brain": ">=0.1.0",
96
- "@ruvector/ruvllm": ">=2.0.0",
97
- "@ruvector/router": ">=0.1.0"
98
+ "@ruvector/router": ">=0.1.0",
99
+ "@ruvector/ruvllm": ">=2.0.0"
98
100
  },
99
101
  "peerDependenciesMeta": {
100
- "@ruvector/pi-brain": { "optional": true },
101
- "@ruvector/ruvllm": { "optional": true },
102
- "@ruvector/router": { "optional": true }
102
+ "@ruvector/pi-brain": {
103
+ "optional": true
104
+ },
105
+ "@ruvector/ruvllm": {
106
+ "optional": true
107
+ },
108
+ "@ruvector/router": {
109
+ "optional": true
110
+ }
103
111
  },
104
112
  "engines": {
105
113
  "node": ">=18.0.0"