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 +50 -0
- package/bin/repotracer.js +81 -0
- package/package.json +41 -0
- package/scripts/fetch-binary.js +91 -0
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 };
|