@redstone-md/nodule 0.1.0

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/install.js ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Nodule — postinstall script.
4
+ *
5
+ * Downloads the platform-appropriate Nodule binary from the latest GitHub
6
+ * Release. Falls back to `go install` if download fails.
7
+ */
8
+
9
+ const https = require('https');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const os = require('os');
13
+ const { spawnSync } = require('child_process');
14
+
15
+ const GITHUB_REPO = 'redstone-md/nodule';
16
+ const BIN_DIR = __dirname;
17
+
18
+ function getTarget() {
19
+ const platform = os.platform();
20
+ const arch = os.arch();
21
+
22
+ let goos, goarch, ext = '';
23
+
24
+ switch (platform) {
25
+ case 'linux': goos = 'linux'; break;
26
+ case 'darwin': goos = 'darwin'; break;
27
+ case 'win32': goos = 'windows'; ext = '.exe'; break;
28
+ default:
29
+ console.warn('nodule: unsupported platform ' + platform + ', skipping download');
30
+ console.warn('nodule: install manually: go install github.com/redstone-md/nodule/cmd/nodule@latest');
31
+ return null;
32
+ }
33
+
34
+ switch (arch) {
35
+ case 'x64': goarch = 'amd64'; break;
36
+ case 'arm64': goarch = 'arm64'; break;
37
+ case 'ia32': goarch = '386'; break;
38
+ default:
39
+ console.warn('nodule: unsupported arch ' + arch + ', skipping download');
40
+ return null;
41
+ }
42
+
43
+ const assetName = 'nodule-' + goos + '-' + goarch + ext;
44
+ return { goos: goos, goarch: goarch, ext: ext, assetName: assetName };
45
+ }
46
+
47
+ function fetchLatestReleaseTag() {
48
+ return new Promise(function(resolve, reject) {
49
+ var url = 'https://api.github.com/repos/' + GITHUB_REPO + '/releases/latest';
50
+ https.get(url, { headers: { 'User-Agent': 'nodule-installer' } }, function(res) {
51
+ if (res.statusCode !== 200) {
52
+ return reject(new Error('GitHub API returned ' + res.statusCode));
53
+ }
54
+ var body = '';
55
+ res.on('data', function(chunk) { body += chunk; });
56
+ res.on('end', function() {
57
+ try { resolve(JSON.parse(body)); }
58
+ catch (e) { reject(e); }
59
+ });
60
+ }).on('error', reject);
61
+ });
62
+ }
63
+
64
+ function downloadAsset(url, destPath) {
65
+ return new Promise(function(resolve, reject) {
66
+ var file = fs.createWriteStream(destPath);
67
+ https.get(url, { headers: { 'User-Agent': 'nodule-installer' } }, function(res) {
68
+ if (res.statusCode === 301 || res.statusCode === 302) {
69
+ file.close();
70
+ fs.unlinkSync(destPath);
71
+ return downloadAsset(res.headers.location, destPath).then(resolve, reject);
72
+ }
73
+ if (res.statusCode !== 200) {
74
+ file.close();
75
+ fs.unlinkSync(destPath);
76
+ return reject(new Error('Download failed: HTTP ' + res.statusCode));
77
+ }
78
+ res.pipe(file);
79
+ file.on('finish', function() { file.close(resolve); });
80
+ }).on('error', function(err) {
81
+ file.close();
82
+ try { fs.unlinkSync(destPath); } catch (e) {}
83
+ reject(err);
84
+ });
85
+ });
86
+ }
87
+
88
+ async function main() {
89
+ var target = getTarget();
90
+ if (!target) return;
91
+
92
+ var destPath = path.join(BIN_DIR, 'nodule-' + target.goos + '-' + target.goarch + target.ext);
93
+
94
+ if (fs.existsSync(destPath)) {
95
+ console.log('nodule: binary already present, skipping download');
96
+ return;
97
+ }
98
+
99
+ console.log('nodule: downloading binary for ' + target.goos + '/' + target.goarch + '...');
100
+
101
+ try {
102
+ var release = await fetchLatestReleaseTag();
103
+ var asset = (release.assets || []).find(function(a) { return a.name === target.assetName; });
104
+ if (!asset) {
105
+ throw new Error('asset ' + target.assetName + ' not found in release ' + release.tag_name);
106
+ }
107
+
108
+ await downloadAsset(asset.browser_download_url, destPath);
109
+
110
+ if (target.goos !== 'windows') {
111
+ fs.chmodSync(destPath, 0o755);
112
+ }
113
+
114
+ console.log('nodule: installed ' + release.tag_name);
115
+ } catch (err) {
116
+ console.warn('nodule: could not download binary (' + err.message + ')');
117
+ console.warn('nodule: falling back to go install...');
118
+
119
+ var result = spawnSync('go', [
120
+ 'install', 'github.com/' + GITHUB_REPO + '/cmd/nodule@latest'
121
+ ], { stdio: 'inherit' });
122
+
123
+ if (result.status !== 0) {
124
+ console.warn('nodule: go install failed. Install manually:');
125
+ console.warn(' go install github.com/redstone-md/nodule/cmd/nodule@latest');
126
+ } else {
127
+ console.log('nodule: installed via go install (ensure $GOPATH/bin is in PATH)');
128
+ }
129
+ }
130
+ }
131
+
132
+ main();
package/bin/nodule.js ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Nodule — binary launcher.
4
+ *
5
+ * Runs the platform-specific Nodule MCP server binary with stdio passthrough.
6
+ * This is a thin wrapper: the Go binary handles all MCP protocol logic.
7
+ *
8
+ * All environment variables (NODULE_LLM_PROVIDER, NODULE_API_KEY, etc.)
9
+ * are inherited from the parent process — Nodule is fully BYOM/BYOK.
10
+ */
11
+
12
+ const { spawn } = require('child_process');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const fs = require('fs');
16
+
17
+ function getBinaryPath() {
18
+ const platform = os.platform();
19
+ const arch = os.arch();
20
+
21
+ let goos, ext = '';
22
+ switch (platform) {
23
+ case 'linux': goos = 'linux'; break;
24
+ case 'darwin': goos = 'darwin'; break;
25
+ case 'win32': goos = 'windows'; ext = '.exe'; break;
26
+ default: throw new Error(`unsupported platform: ${platform}`);
27
+ }
28
+
29
+ let goarch;
30
+ switch (arch) {
31
+ case 'x64': goarch = 'amd64'; break;
32
+ case 'arm64': goarch = 'arm64'; break;
33
+ case 'ia32': goarch = '386'; break;
34
+ default: throw new Error(`unsupported arch: ${arch}`);
35
+ }
36
+
37
+ const binaryName = `nodule-${goos}-${goarch}${ext}`;
38
+ return path.join(__dirname, binaryName);
39
+ }
40
+
41
+ function main() {
42
+ let binaryPath = getBinaryPath();
43
+
44
+ // If the platform binary doesn't exist, try PATH lookup (go install case)
45
+ if (!fs.existsSync(binaryPath)) {
46
+ const isWindows = os.platform() === 'win32';
47
+ binaryPath = isWindows ? 'nodule.exe' : 'nodule';
48
+ }
49
+
50
+ const child = spawn(binaryPath, process.argv.slice(2), {
51
+ stdio: 'inherit',
52
+ env: process.env,
53
+ });
54
+
55
+ child.on('error', (err) => {
56
+ if (err.code === 'ENOENT') {
57
+ process.stderr.write(
58
+ 'nodule: binary not found. Reinstall with `npm install nodule` or `go install github.com/redstone-md/nodule/cmd/nodule@latest`\n'
59
+ );
60
+ } else {
61
+ process.stderr.write(`nodule: ${err.message}\n`);
62
+ }
63
+ process.exit(1);
64
+ });
65
+
66
+ child.on('exit', (code, signal) => {
67
+ if (signal) {
68
+ process.kill(process.pid, signal);
69
+ } else {
70
+ process.exit(code ?? 1);
71
+ }
72
+ });
73
+ }
74
+
75
+ main();
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@redstone-md/nodule",
3
+ "version": "0.1.0",
4
+ "description": "Local MCP server providing a bounce_idea tool for independent LLM-based code critique (BYOM/BYOK)",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/redstone-md/nodule.git",
9
+ "directory": "npm"
10
+ },
11
+ "homepage": "https://github.com/redstone-md/nodule#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/redstone-md/nodule/issues"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "llm",
19
+ "ai",
20
+ "code-review",
21
+ "critic",
22
+ "gemini",
23
+ "openai",
24
+ "ollama"
25
+ ],
26
+ "bin": {
27
+ "nodule": "bin/nodule.js"
28
+ },
29
+ "scripts": {
30
+ "postinstall": "node bin/install.js"
31
+ },
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "files": [
36
+ "bin/"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }