repotracer 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/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # repotracer
2
+
3
+ RepoTracer is an MCP server whose `repo_scout` tool runs an isolated, read-only Luna process and returns validated source citations to Codex Sol.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ npx repotracer setup
9
+ ```
10
+
11
+ The installer downloads the native binary, verifies its SHA-256 checksum, copies it to `~/.repotracer/bin/repotracer`, registers the stdio MCP server, and adds the Codex routing skill.
12
+
13
+ Codex must already be installed and signed in. RepoTracer reuses that login and does not require another API key.
14
+
15
+ ```bash
16
+ npm install -g @openai/codex
17
+ codex login
18
+ npx repotracer setup
19
+ repotracer doctor
20
+ ```
21
+
22
+ Preview without changing files:
23
+
24
+ ```bash
25
+ npx repotracer setup --dry-run
26
+ ```
27
+
28
+ ## Commands
29
+
30
+ ```bash
31
+ repotracer "where is auth handled?"
32
+ repotracer scout "trace refresh token rotation"
33
+ repotracer doctor
34
+ repotracer status
35
+ repotracer uninstall --yes
36
+ ```
37
+
38
+ ## Permanent install
39
+
40
+ ```bash
41
+ npm install -g repotracer
42
+ # or
43
+ cargo install --git https://github.com/repotracer/repotracer --locked repotracer
44
+ ```
45
+
46
+ Supported platforms: macOS arm64 and x64, Linux arm64 and x64, and Windows x64. Node.js 18 or newer.
47
+
48
+ Read the [documentation and benchmarks](https://github.com/repotracer/repotracer).
49
+
50
+ MIT licensed.
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawnSync } = require('child_process');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ function platformKey() {
10
+ const p = process.platform;
11
+ const a = process.arch;
12
+ if (p === 'darwin' && a === 'arm64') return 'darwin-arm64';
13
+ if (p === 'darwin' && a === 'x64') return 'darwin-x64';
14
+ if (p === 'linux' && a === 'x64') return 'linux-x64';
15
+ if (p === 'linux' && a === 'arm64') return 'linux-arm64';
16
+ if (p === 'win32' && a === 'x64') return 'windows-x64';
17
+ return `${p}-${a}`;
18
+ }
19
+
20
+ function findBinary() {
21
+ const ext = process.platform === 'win32' ? '.exe' : '';
22
+ const name = `repotracer${ext}`;
23
+ // Vendored binary first. The target/ paths only exist in a source checkout and
24
+ // let the benchmark harness run this launcher without publishing. A bare name on
25
+ // PATH is deliberately NOT a candidate: silently running an unrelated or stale
26
+ // `repotracer` is worse than failing with instructions.
27
+ const candidates = [
28
+ process.env.REPOTRACER_BIN,
29
+ path.join(__dirname, '..', 'vendor', platformKey(), name),
30
+ path.join(__dirname, '..', '..', '..', 'target', 'release', name),
31
+ path.join(__dirname, '..', '..', '..', 'target', 'debug', name),
32
+ ].filter(Boolean);
33
+
34
+ return candidates.find(candidate => fs.existsSync(candidate)) || null;
35
+ }
36
+
37
+ function persistForSetup(bin, args, home = os.homedir()) {
38
+ if (!path.isAbsolute(bin) || !args.includes('setup') || args.includes('--dry-run')) return bin;
39
+
40
+ const name = process.platform === 'win32' ? 'repotracer.exe' : 'repotracer';
41
+ const directory = path.join(home, '.repotracer', 'bin');
42
+ const destination = path.join(directory, name);
43
+ if (path.resolve(bin) === path.resolve(destination)) return bin;
44
+
45
+ const temporary = `${destination}.tmp-${process.pid}`;
46
+ fs.mkdirSync(directory, { recursive: true });
47
+ try {
48
+ fs.copyFileSync(bin, temporary);
49
+ if (process.platform !== 'win32') fs.chmodSync(temporary, 0o755);
50
+ try {
51
+ fs.renameSync(temporary, destination);
52
+ } catch (error) {
53
+ if (!['EEXIST', 'EPERM'].includes(error.code)) throw error;
54
+ fs.rmSync(destination, { force: true });
55
+ fs.renameSync(temporary, destination);
56
+ }
57
+ } finally {
58
+ fs.rmSync(temporary, { force: true });
59
+ }
60
+ // The setup TUI reports the final binary path itself; stay quiet unless debugging.
61
+ if (process.env.REPOTRACER_DEBUG) console.log(`repotracer: installed CLI at ${destination}`);
62
+ return destination;
63
+ }
64
+
65
+ function main(args = process.argv.slice(2)) {
66
+ const found = findBinary();
67
+ if (!found) {
68
+ console.error(`repotracer: native binary not found for ${platformKey()}.`);
69
+ console.error('Reinstall with: npm install -g repotracer');
70
+ return 1;
71
+ }
72
+
73
+ const bin = persistForSetup(found, args);
74
+ const result = spawnSync(bin, args, { stdio: 'inherit' });
75
+ if (result.error) console.error(`repotracer: could not start: ${result.error.message}`);
76
+ return result.status == null ? 1 : result.status;
77
+ }
78
+
79
+ if (require.main === module) process.exit(main());
80
+
81
+ module.exports = { findBinary, main, persistForSetup, platformKey };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "repotracer",
3
+ "version": "0.1.0",
4
+ "description": "Local repository scout for AI coding agents. Small models search. Big models solve.",
5
+ "bin": {
6
+ "repotracer": "bin/repotracer.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "scripts/",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "postinstall": "node scripts/fetch-binary.js",
15
+ "test": "node --test test/*.test.js"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "coding-agent",
20
+ "codex",
21
+ "repository",
22
+ "code-search",
23
+ "local-ai"
24
+ ],
25
+ "license": "MIT",
26
+ "homepage": "https://github.com/repotracer/repotracer#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/repotracer/repotracer/issues"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/repotracer/repotracer.git",
33
+ "directory": "packages/npm"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "engines": {
39
+ "node": ">=18"
40
+ }
41
+ }
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const crypto = require('crypto');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const pkg = require('../package.json');
8
+
9
+ function platformKey(platform = process.platform, arch = process.arch) {
10
+ const supported = {
11
+ 'darwin-arm64': 'darwin-arm64',
12
+ 'darwin-x64': 'darwin-x64',
13
+ 'linux-arm64': 'linux-arm64',
14
+ 'linux-x64': 'linux-x64',
15
+ 'win32-x64': 'windows-x64',
16
+ };
17
+ const key = supported[`${platform}-${arch}`];
18
+ if (!key) throw new Error(`unsupported platform: ${platform}-${arch}`);
19
+ return key;
20
+ }
21
+
22
+ async function download(fetchImpl, url) {
23
+ const response = await fetchImpl(url, {
24
+ headers: { 'User-Agent': `repotracer-npm/${pkg.version}` },
25
+ });
26
+ if (!response.ok) throw new Error(`${url} returned HTTP ${response.status}`);
27
+ return Buffer.from(await response.arrayBuffer());
28
+ }
29
+
30
+ function expectedChecksum(sums, asset) {
31
+ for (const line of sums.split(/\r?\n/)) {
32
+ const match = line.trim().match(/^([a-fA-F0-9]{64})\s+(.+)$/);
33
+ if (match && path.basename(match[2]) === asset) return match[1].toLowerCase();
34
+ }
35
+ throw new Error(`${asset} is missing from SHA256SUMS`);
36
+ }
37
+
38
+ async function install(options = {}) {
39
+ const platform = options.platform || process.platform;
40
+ const arch = options.arch || process.arch;
41
+ const key = platformKey(platform, arch);
42
+ const executable = platform === 'win32' ? 'repotracer.exe' : 'repotracer';
43
+ const asset = `repotracer-${key}${executable.endsWith('.exe') ? '.exe' : ''}`;
44
+ const root = options.root || path.join(__dirname, '..');
45
+ const baseUrl = options.baseUrl
46
+ || process.env.REPOTRACER_RELEASE_BASE_URL
47
+ || `https://github.com/repotracer/repotracer/releases/download/v${pkg.version}`;
48
+ const fetchImpl = options.fetchImpl || globalThis.fetch;
49
+
50
+ if (!fetchImpl) throw new Error('Node.js 18 or newer is required');
51
+ if (!options.quiet) console.log(`repotracer: downloading v${pkg.version} for ${key}...`);
52
+
53
+ const [binary, sums] = await Promise.all([
54
+ download(fetchImpl, `${baseUrl}/${asset}`),
55
+ download(fetchImpl, `${baseUrl}/SHA256SUMS`),
56
+ ]);
57
+ const expected = expectedChecksum(sums.toString('utf8'), asset);
58
+ const actual = crypto.createHash('sha256').update(binary).digest('hex');
59
+ if (actual !== expected) throw new Error(`checksum mismatch for ${asset}`);
60
+
61
+ const directory = path.join(root, 'vendor', key);
62
+ const destination = path.join(directory, executable);
63
+ const temporary = `${destination}.tmp-${process.pid}`;
64
+ fs.mkdirSync(directory, { recursive: true });
65
+ try {
66
+ fs.writeFileSync(temporary, binary, { flag: 'wx', mode: 0o755 });
67
+ try {
68
+ fs.renameSync(temporary, destination);
69
+ } catch (error) {
70
+ if (!['EEXIST', 'EPERM'].includes(error.code)) throw error;
71
+ fs.rmSync(destination, { force: true });
72
+ fs.renameSync(temporary, destination);
73
+ }
74
+ if (process.platform !== 'win32') fs.chmodSync(destination, 0o755);
75
+ } finally {
76
+ fs.rmSync(temporary, { force: true });
77
+ }
78
+
79
+ if (!options.quiet) console.log(`repotracer: installed ${destination}`);
80
+ return destination;
81
+ }
82
+
83
+ if (require.main === module) {
84
+ install().catch((error) => {
85
+ console.error(`repotracer: installation failed: ${error.message}`);
86
+ console.error(`repotracer: GitHub Release v${pkg.version} must exist before npm installation.`);
87
+ process.exit(1);
88
+ });
89
+ }
90
+
91
+ module.exports = { expectedChecksum, install, platformKey };