@hyperlogue/r3 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/LICENSE +21 -0
- package/README.md +39 -0
- package/launch.mjs +128 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hyperlogue
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @hyperlogue/r3
|
|
2
|
+
|
|
3
|
+
**Review. Revise. Resolve.** — a local-first review tool for AI-generated code and docs.
|
|
4
|
+
|
|
5
|
+
## Try it
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bunx @hyperlogue/r3 create --working # or: npx @hyperlogue/r3 create --working
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This package is a tiny launcher. The native `r3` binary for your platform ships
|
|
12
|
+
as a per-platform **optional dependency** (`@hyperlogue/r3-darwin-arm64`,
|
|
13
|
+
`-linux-x64`, …), so your package manager installs only the one that matches your
|
|
14
|
+
OS/CPU — no download at run time, and `npx @hyperlogue/r3@x.y.z` deterministically
|
|
15
|
+
runs that version's binary. The launcher just resolves the installed binary and
|
|
16
|
+
execs it, forwarding argv, stdio, and the exit code.
|
|
17
|
+
|
|
18
|
+
The binary is fully self-contained (it embeds its runtime, deps, and web UI), so
|
|
19
|
+
`bunx` and `npx` behave identically.
|
|
20
|
+
|
|
21
|
+
## Supported platforms
|
|
22
|
+
|
|
23
|
+
macOS and Linux (glibc), `arm64` / `x64`. On anything else the launcher tells you
|
|
24
|
+
to [build from source](https://github.com/hyperlogue/r3).
|
|
25
|
+
|
|
26
|
+
## Environment overrides
|
|
27
|
+
|
|
28
|
+
| Variable | Effect |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `R3_BINARY` | Path to a prebuilt `r3` binary to run directly, bypassing package resolution (dev / offline / air-gapped / a platform off the published matrix). |
|
|
31
|
+
|
|
32
|
+
If the launcher reports that the platform package "is not installed," it's almost
|
|
33
|
+
always a stale lockfile (a [known npm optional-dependencies
|
|
34
|
+
bug](https://github.com/npm/cli/issues/4828)) — remove `node_modules` and the
|
|
35
|
+
lockfile and reinstall, or grab a binary from
|
|
36
|
+
[GitHub Releases](https://github.com/hyperlogue/r3/releases) and point `R3_BINARY`
|
|
37
|
+
at it.
|
|
38
|
+
|
|
39
|
+
For the full tool docs, see the [project README](https://github.com/hyperlogue/r3#readme).
|
package/launch.mjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// r3 launcher — the npm package's only runtime file. It selects and execs the
|
|
3
|
+
// prebuilt native `r3` binary that ships in a per-platform optional-dependency
|
|
4
|
+
// package (`@hyperlogue/r3-<os>-<arch>`). npm/bun install only the package whose
|
|
5
|
+
// `os`/`cpu` match the host, so the matching binary is already on disk by the
|
|
6
|
+
// time this runs — no download, no network, no checksum dance. This file just
|
|
7
|
+
// resolves it and hands off, forwarding argv, stdio, and the exit code/signal.
|
|
8
|
+
//
|
|
9
|
+
// Deliberately plain, dependency-free Node ESM using only cross-runtime APIs, so
|
|
10
|
+
// the SAME file runs under both `bunx @hyperlogue/r3` (Bun) and `npx @hyperlogue/r3`
|
|
11
|
+
// (Node ≥18). The binary is fully self-contained (it embeds the Bun runtime +
|
|
12
|
+
// SPA), so whichever runtime ran this launcher is irrelevant to how r3 itself runs.
|
|
13
|
+
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { chmodSync, existsSync } from "node:fs";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { constants } from "node:os";
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
// `${process.platform}-${process.arch}` -> the optional-dependency package that
|
|
22
|
+
// carries that platform's binary. Keep in sync with scripts/stage-npm-packages.ts
|
|
23
|
+
// (the producer) and scripts/release-binaries.ts (the asset names).
|
|
24
|
+
const PACKAGES = {
|
|
25
|
+
"darwin-arm64": "@hyperlogue/r3-darwin-arm64",
|
|
26
|
+
"darwin-x64": "@hyperlogue/r3-darwin-x64",
|
|
27
|
+
"linux-x64": "@hyperlogue/r3-linux-x64",
|
|
28
|
+
"linux-arm64": "@hyperlogue/r3-linux-arm64",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const REPO = "https://github.com/hyperlogue/r3";
|
|
32
|
+
|
|
33
|
+
function fail(msg) {
|
|
34
|
+
process.stderr.write(`r3: ${msg}\n`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The published Linux binaries are glibc builds. On musl (Alpine) npm ≥9.6 skips
|
|
39
|
+
// the glibc package (via its `libc` field) so it won't resolve; older npm / Bun
|
|
40
|
+
// ignore that field and install it anyway, and it then dies at exec with a
|
|
41
|
+
// cryptic ENOENT (missing dynamic loader). Detect musl so both paths can say so.
|
|
42
|
+
function isMusl() {
|
|
43
|
+
if (process.platform !== "linux") return false;
|
|
44
|
+
try {
|
|
45
|
+
const report = process.report?.getReport?.();
|
|
46
|
+
return Boolean(report && !report.header?.glibcVersionRuntime);
|
|
47
|
+
} catch {
|
|
48
|
+
return false; // can't tell — assume glibc and let exec surface any mismatch
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function muslFail() {
|
|
53
|
+
fail(
|
|
54
|
+
`this looks like a musl/Alpine system, but only glibc Linux binaries are published.\n` +
|
|
55
|
+
` Build from source: ${REPO}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveBinary() {
|
|
60
|
+
// Escape hatch first — run a prebuilt binary on ANY platform (dev / testing /
|
|
61
|
+
// offline / a target off the published matrix), before the platform guard.
|
|
62
|
+
if (process.env.R3_BINARY) {
|
|
63
|
+
if (!existsSync(process.env.R3_BINARY)) fail(`R3_BINARY not found: ${process.env.R3_BINARY}`);
|
|
64
|
+
return process.env.R3_BINARY;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const key = `${process.platform}-${process.arch}`;
|
|
68
|
+
const pkg = PACKAGES[key];
|
|
69
|
+
if (!pkg) {
|
|
70
|
+
if (isMusl()) muslFail();
|
|
71
|
+
fail(
|
|
72
|
+
`no prebuilt binary for ${key}. Supported: ${Object.keys(PACKAGES).join(", ")}.\n` +
|
|
73
|
+
` Build from source: ${REPO}`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Refuse musl up front, whether or not the glibc package resolved: npm ≥9.6
|
|
78
|
+
// skips it (via `libc`) so resolve would fail, but older npm / Bun ignore that
|
|
79
|
+
// field and install it anyway, and it'd then ENOENT cryptically at exec.
|
|
80
|
+
if (isMusl()) muslFail();
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
// Resolves to node_modules/<pkg>/bin/r3 when the optional dependency installed.
|
|
84
|
+
return require.resolve(`${pkg}/bin/r3`);
|
|
85
|
+
} catch {
|
|
86
|
+
// The platform package didn't install. Common causes, in order of likelihood:
|
|
87
|
+
// a stale lockfile (npm optional-deps bug npm/cli#4828), a --no-optional /
|
|
88
|
+
// offline install, or a mirror that dropped the optional dep.
|
|
89
|
+
fail(
|
|
90
|
+
`the ${pkg} package for your platform (${key}) is not installed.\n` +
|
|
91
|
+
` This is usually a stale lockfile (npm optional-dependencies bug). Try:\n` +
|
|
92
|
+
` • reinstall: rm -rf node_modules package-lock.json && npm install\n` +
|
|
93
|
+
` • clear the npx cache, then retry: npx --yes @hyperlogue/r3 …\n` +
|
|
94
|
+
` • run a prebuilt binary directly: set R3_BINARY=/path/to/r3\n` +
|
|
95
|
+
` • download one from ${REPO}/releases`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const bin = resolveBinary();
|
|
101
|
+
// Best-effort exec bit — the packaged binary ships 0755, but a filesystem or a
|
|
102
|
+
// tarball tool that dropped the mode would otherwise EACCES on spawn.
|
|
103
|
+
try {
|
|
104
|
+
chmodSync(bin, 0o755);
|
|
105
|
+
} catch {}
|
|
106
|
+
|
|
107
|
+
const child = spawn(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
108
|
+
// Forward termination signals so `kill <launcher-pid>` (or a supervisor) reaches
|
|
109
|
+
// the r3 child instead of orphaning it — matters for long-blocking `r3 watch`.
|
|
110
|
+
// (SIGINT/Ctrl-C already reaches the child via the shared process group.)
|
|
111
|
+
for (const sig of ["SIGTERM", "SIGHUP"]) {
|
|
112
|
+
process.on(sig, () => {
|
|
113
|
+
try {
|
|
114
|
+
child.kill(sig);
|
|
115
|
+
} catch {}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
child.on("error", (err) => fail(`failed to exec ${bin}: ${err.message}`));
|
|
119
|
+
child.on("exit", (code, signal) => {
|
|
120
|
+
if (signal) {
|
|
121
|
+
// Re-raise so our exit status mirrors the child's; then fall back to the
|
|
122
|
+
// conventional 128+signum for signals the runtime ignores (e.g. SIGPIPE),
|
|
123
|
+
// where the self-kill is a no-op and we'd otherwise exit 0.
|
|
124
|
+
process.kill(process.pid, signal);
|
|
125
|
+
process.exit(128 + (constants.signals[signal] || 0));
|
|
126
|
+
}
|
|
127
|
+
process.exit(code ?? 0);
|
|
128
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hyperlogue/r3",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Review. Revise. Resolve. — a local-first review tool for AI-generated code and docs. Runs the matching prebuilt native r3 binary (shipped as a per-platform optional dependency); works with `bunx @hyperlogue/r3` and `npx @hyperlogue/r3`.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"r3": "launch.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"launch.mjs"
|
|
15
|
+
],
|
|
16
|
+
"optionalDependencies": {
|
|
17
|
+
"@hyperlogue/r3-darwin-arm64": "0.1.0",
|
|
18
|
+
"@hyperlogue/r3-darwin-x64": "0.1.0",
|
|
19
|
+
"@hyperlogue/r3-linux-x64": "0.1.0",
|
|
20
|
+
"@hyperlogue/r3-linux-arm64": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/hyperlogue/r3.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/hyperlogue/r3#readme",
|
|
30
|
+
"bugs": "https://github.com/hyperlogue/r3/issues",
|
|
31
|
+
"keywords": [
|
|
32
|
+
"code-review",
|
|
33
|
+
"ai",
|
|
34
|
+
"local-first",
|
|
35
|
+
"cli",
|
|
36
|
+
"diff"
|
|
37
|
+
]
|
|
38
|
+
}
|