@evgeny.skarlat/cli 0.0.22-beta.7
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 +36 -0
- package/bin/renre-kit.js +222 -0
- package/package.json +37 -0
- package/scripts/postinstall.mjs +101 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @evgeny.skarlat/cli
|
|
2
|
+
|
|
3
|
+
RenreKit CLI — lightweight plugin-driven development CLI.
|
|
4
|
+
|
|
5
|
+
This is the **meta package**. It does not contain a binary by itself; instead
|
|
6
|
+
it lists per-platform packages in `optionalDependencies` and npm filters those
|
|
7
|
+
by `os` + `cpu` so only the matching one installs.
|
|
8
|
+
|
|
9
|
+
The `renre-kit` command on your `PATH` is a small Node dispatcher that resolves
|
|
10
|
+
the installed platform package and execs its native binary.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @evgeny.skarlat/cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Supported platforms
|
|
19
|
+
|
|
20
|
+
| OS | Arch | Package |
|
|
21
|
+
| ------- | ----- | -------------------------------- |
|
|
22
|
+
| Linux | x64 | `@evgeny.skarlat/cli-linux-x64` |
|
|
23
|
+
| Linux | arm64 | `@evgeny.skarlat/cli-linux-arm64` |
|
|
24
|
+
| macOS | x64 | `@evgeny.skarlat/cli-darwin-x64` |
|
|
25
|
+
| macOS | arm64 | `@evgeny.skarlat/cli-darwin-arm64` |
|
|
26
|
+
| Windows | x64 | `@evgeny.skarlat/cli-win32-x64` |
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
See `renre-kit-architecture/adr/distribution/`:
|
|
31
|
+
|
|
32
|
+
- ADR-001 — JFrog npm registry as sole channel
|
|
33
|
+
- ADR-002 — Bun `--compile` for cross-platform single-file binaries
|
|
34
|
+
- ADR-003 — Platform-`optionalDependencies` package layout
|
|
35
|
+
- ADR-004 — Embed the web dashboard inside the CLI binary
|
|
36
|
+
- ADR-005 — `rnrk` Python lifecycle manager relegated to contributor path
|
package/bin/renre-kit.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Cross-platform dispatcher for the @renre-kit/cli meta package (ADR-003).
|
|
3
|
+
//
|
|
4
|
+
// Resolves the per-OS / per-CPU / per-libc platform package that npm
|
|
5
|
+
// filtered in via `optionalDependencies`, locates its native binary, and
|
|
6
|
+
// execs it with the caller's argv + environment.
|
|
7
|
+
//
|
|
8
|
+
// Resolution order (first hit wins):
|
|
9
|
+
// 1. $RENRE_KIT_BIN_PATH — explicit override, useful for local development
|
|
10
|
+
// against a freshly built binary without reinstalling.
|
|
11
|
+
// 2. On linux: -musl variant if running on musl libc (Alpine etc.), then
|
|
12
|
+
// the glibc variant; on glibc the glibc variant first, then musl as a
|
|
13
|
+
// defensive fallback. (Older npm without `libc`-filtering may install
|
|
14
|
+
// both, so the dispatcher decides at runtime.)
|
|
15
|
+
// 3. On macOS / Windows: the single `<os>-<arch>` variant.
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
import { spawnSync } from 'node:child_process';
|
|
18
|
+
import { platform as osPlatform, arch as osArch } from 'node:os';
|
|
19
|
+
import { existsSync } from 'node:fs';
|
|
20
|
+
import { dirname, join } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
|
|
25
|
+
function platformKey() {
|
|
26
|
+
const p = osPlatform();
|
|
27
|
+
const a = osArch();
|
|
28
|
+
if (p === 'win32') return ['win32', a];
|
|
29
|
+
if (p === 'darwin') return ['darwin', a];
|
|
30
|
+
if (p === 'linux') return ['linux', a];
|
|
31
|
+
return [p, a];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Detect whether the running Linux host uses musl libc (Alpine and similar
|
|
36
|
+
* minimal distros) rather than glibc. Two heuristics:
|
|
37
|
+
* 1. `/etc/alpine-release` exists -> musl
|
|
38
|
+
* 2. `ldd --version` output contains "musl"
|
|
39
|
+
* Returns false on any other platform.
|
|
40
|
+
*/
|
|
41
|
+
function isMuslLinux() {
|
|
42
|
+
if (osPlatform() !== 'linux') return false;
|
|
43
|
+
try {
|
|
44
|
+
if (existsSync('/etc/alpine-release')) return true;
|
|
45
|
+
} catch {
|
|
46
|
+
/* permission denied or similar — fall through */
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const res = spawnSync('ldd', ['--version'], { encoding: 'utf8', timeout: 1500 });
|
|
50
|
+
const text = (res.stdout || '') + (res.stderr || '');
|
|
51
|
+
if (text.toLowerCase().includes('musl')) return true;
|
|
52
|
+
} catch {
|
|
53
|
+
/* ldd missing — assume glibc */
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function candidatePackageNames(os, arch) {
|
|
59
|
+
if (os === 'linux') {
|
|
60
|
+
const glibc = `@evgeny.skarlat/cli-linux-${arch}`;
|
|
61
|
+
const musl = `@evgeny.skarlat/cli-linux-${arch}-musl`;
|
|
62
|
+
return isMuslLinux() ? [musl, glibc] : [glibc, musl];
|
|
63
|
+
}
|
|
64
|
+
return [`@evgeny.skarlat/cli-${os}-${arch}`];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolvePlatformPackageDir(pkgName) {
|
|
68
|
+
try {
|
|
69
|
+
const pkgJsonPath = require.resolve(`${pkgName}/package.json`);
|
|
70
|
+
return dirname(pkgJsonPath);
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function findBinary(os, arch) {
|
|
77
|
+
const binaryName = os === 'win32' ? 'renre-kit.exe' : 'renre-kit';
|
|
78
|
+
const candidates = candidatePackageNames(os, arch);
|
|
79
|
+
for (const pkg of candidates) {
|
|
80
|
+
const pkgDir = resolvePlatformPackageDir(pkg);
|
|
81
|
+
if (!pkgDir) continue;
|
|
82
|
+
const binaryPath = join(pkgDir, 'bin', binaryName);
|
|
83
|
+
if (existsSync(binaryPath)) {
|
|
84
|
+
return { binaryPath, pkg };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { binaryPath: null, pkg: candidates[0] };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the on-disk path to `@github/copilot/index.js`, which the bundled
|
|
92
|
+
* `@github/copilot-sdk` inside the Bun binary needs to spawn the Copilot CLI.
|
|
93
|
+
*
|
|
94
|
+
* `import.meta.resolve` from inside a Bun-compiled binary (virtual fs at
|
|
95
|
+
* `/$bunfs/root/`) cannot see `node_modules/@github/copilot/`, so the SDK
|
|
96
|
+
* falls back to the `COPILOT_CLI_PATH` env var. We compute it here from the
|
|
97
|
+
* Node-side dispatcher, which has normal filesystem access.
|
|
98
|
+
*
|
|
99
|
+
* We use `require.resolve.paths()` to enumerate candidate node_modules dirs
|
|
100
|
+
* rather than `require.resolve('@github/copilot/package.json')` — the latter
|
|
101
|
+
* fails because `@github/copilot` ships an `exports` field that doesn't
|
|
102
|
+
* expose `./package.json`. This mirrors the SDK's own fallback (see
|
|
103
|
+
* `@github/copilot-sdk/dist/client.js:getBundledCliPath`).
|
|
104
|
+
*
|
|
105
|
+
* Returns `null` when the package is not installed — copilot is optional, so
|
|
106
|
+
* we don't fail the launch.
|
|
107
|
+
*/
|
|
108
|
+
export function resolveCopilotCliPath(req) {
|
|
109
|
+
try {
|
|
110
|
+
const paths = req.resolve.paths('@github/copilot') ?? [];
|
|
111
|
+
for (const base of paths) {
|
|
112
|
+
const candidate = join(base, '@github', 'copilot', 'index.js');
|
|
113
|
+
if (existsSync(candidate)) return candidate;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Locate the `node_modules` directory that contains `node-pty` and return
|
|
123
|
+
* the dir itself (not the package dir). The Bun-compiled binary marks
|
|
124
|
+
* `node-pty` as `--external`, so the native `conpty.node` (Windows) and
|
|
125
|
+
* `pty.node` (POSIX) bindings have to be resolved from disk at runtime.
|
|
126
|
+
* We surface the parent `node_modules` via `NODE_PATH` so Bun's runtime
|
|
127
|
+
* resolver picks up the prebuilt binding shipped by the npm package.
|
|
128
|
+
*
|
|
129
|
+
* Returns `null` when node-pty isn't installed — the terminal feature
|
|
130
|
+
* degrades to "unavailable" rather than crashing.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveNodePtyNodeModules(req) {
|
|
133
|
+
try {
|
|
134
|
+
const paths = req.resolve.paths('node-pty') ?? [];
|
|
135
|
+
for (const base of paths) {
|
|
136
|
+
if (existsSync(join(base, 'node-pty', 'package.json'))) return base;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build the child process environment. Injects `COPILOT_CLI_PATH` and
|
|
146
|
+
* prepends to `NODE_PATH` only when the caller hasn't set them explicitly —
|
|
147
|
+
* an explicit value from the user's shell or CI always wins.
|
|
148
|
+
*/
|
|
149
|
+
export function buildChildEnv(parentEnv, { copilotCliPath, nodePtyNodeModules } = {}) {
|
|
150
|
+
const env = { ...parentEnv };
|
|
151
|
+
if (copilotCliPath && !env.COPILOT_CLI_PATH) {
|
|
152
|
+
env.COPILOT_CLI_PATH = copilotCliPath;
|
|
153
|
+
}
|
|
154
|
+
if (nodePtyNodeModules) {
|
|
155
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
156
|
+
env.NODE_PATH = env.NODE_PATH
|
|
157
|
+
? `${nodePtyNodeModules}${sep}${env.NODE_PATH}`
|
|
158
|
+
: nodePtyNodeModules;
|
|
159
|
+
}
|
|
160
|
+
return env;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function execAndExit(binaryPath) {
|
|
164
|
+
const childEnv = buildChildEnv(process.env, {
|
|
165
|
+
copilotCliPath: resolveCopilotCliPath(require),
|
|
166
|
+
nodePtyNodeModules: resolveNodePtyNodeModules(require),
|
|
167
|
+
});
|
|
168
|
+
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
|
169
|
+
stdio: 'inherit',
|
|
170
|
+
env: childEnv,
|
|
171
|
+
windowsHide: false,
|
|
172
|
+
});
|
|
173
|
+
if (result.error) {
|
|
174
|
+
process.stderr.write(`renre-kit: failed to launch native binary: ${result.error.message}\n`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
process.exit(result.status ?? 0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function main() {
|
|
181
|
+
const [os, arch] = platformKey();
|
|
182
|
+
|
|
183
|
+
// 1. Explicit override — bypasses package resolution entirely. Useful when
|
|
184
|
+
// pointing at a locally-built binary during development.
|
|
185
|
+
const envOverride = process.env.RENRE_KIT_BIN_PATH;
|
|
186
|
+
if (envOverride) {
|
|
187
|
+
if (!existsSync(envOverride)) {
|
|
188
|
+
process.stderr.write(
|
|
189
|
+
`\nrenre-kit: RENRE_KIT_BIN_PATH points to a file that does not exist:\n ${envOverride}\n\n`,
|
|
190
|
+
);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
execAndExit(envOverride);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 2. Resolve via optionalDependencies candidates.
|
|
198
|
+
const { binaryPath, pkg } = findBinary(os, arch);
|
|
199
|
+
if (!binaryPath) {
|
|
200
|
+
const candidates = candidatePackageNames(os, arch);
|
|
201
|
+
process.stderr.write(
|
|
202
|
+
`\nrenre-kit: no native binary installed for ${os}/${arch}` +
|
|
203
|
+
(os === 'linux' ? ` (${isMuslLinux() ? 'musl' : 'glibc'})` : '') +
|
|
204
|
+
`.\n` +
|
|
205
|
+
`Looked for: ${candidates.join(', ')}\n` +
|
|
206
|
+
`Reinstall with: npm install -g @evgeny.skarlat/cli\n` +
|
|
207
|
+
`Or set RENRE_KIT_BIN_PATH to a local binary you trust.\n\n`,
|
|
208
|
+
);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (process.env.RENRE_KIT_DEBUG_DISPATCH) {
|
|
213
|
+
process.stderr.write(`[renre-kit-dispatch] launching ${pkg} -> ${binaryPath}\n`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
execAndExit(binaryPath);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Only auto-run when invoked as a script. Tests import this module to exercise
|
|
220
|
+
// the helper functions without triggering the dispatch + process.exit chain.
|
|
221
|
+
const invokedAsScript = process.argv[1] === fileURLToPath(import.meta.url);
|
|
222
|
+
if (invokedAsScript) main();
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@evgeny.skarlat/cli",
|
|
3
|
+
"version": "0.0.22-beta.7",
|
|
4
|
+
"description": "RenreKit CLI — lightweight plugin-driven development CLI",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"renre-kit": "bin/renre-kit.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/renre-kit.js",
|
|
12
|
+
"scripts/postinstall.mjs",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@github/copilot": "^1.0.36"
|
|
23
|
+
},
|
|
24
|
+
"optionalDependencies": {
|
|
25
|
+
"node-pty": "^1.1.0",
|
|
26
|
+
"@evgeny.skarlat/cli-darwin-arm64": "0.0.22-beta.7",
|
|
27
|
+
"@evgeny.skarlat/cli-darwin-x64": "0.0.22-beta.7",
|
|
28
|
+
"@evgeny.skarlat/cli-linux-arm64": "0.0.22-beta.7",
|
|
29
|
+
"@evgeny.skarlat/cli-linux-arm64-musl": "0.0.22-beta.7",
|
|
30
|
+
"@evgeny.skarlat/cli-linux-x64": "0.0.22-beta.7",
|
|
31
|
+
"@evgeny.skarlat/cli-linux-x64-musl": "0.0.22-beta.7",
|
|
32
|
+
"@evgeny.skarlat/cli-win32-x64": "0.0.22-beta.7"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Postinstall verification for @renre-kit/cli.
|
|
4
|
+
*
|
|
5
|
+
* Confirms that npm picked up the right per-platform package from
|
|
6
|
+
* `optionalDependencies`. If it didn't, prints a clear actionable message
|
|
7
|
+
* and exits 0 anyway — failing the install would leave the user worse off
|
|
8
|
+
* (they couldn't even rerun `npm install` against a fixed registry without
|
|
9
|
+
* uninstalling the meta first).
|
|
10
|
+
*
|
|
11
|
+
* Detection logic mirrors bin/renre-kit.js so the same candidates are
|
|
12
|
+
* tried.
|
|
13
|
+
*/
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { platform as osPlatform, arch as osArch } from 'node:os';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
function isMuslLinux() {
|
|
22
|
+
if (osPlatform() !== 'linux') return false;
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync('/etc/alpine-release')) return true;
|
|
25
|
+
} catch {
|
|
26
|
+
/* fall through */
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const res = spawnSync('ldd', ['--version'], { encoding: 'utf8', timeout: 1500 });
|
|
30
|
+
const text = (res.stdout || '') + (res.stderr || '');
|
|
31
|
+
if (text.toLowerCase().includes('musl')) return true;
|
|
32
|
+
} catch {
|
|
33
|
+
/* ldd missing */
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function expectedCandidates() {
|
|
39
|
+
const p = osPlatform();
|
|
40
|
+
const a = osArch();
|
|
41
|
+
if (p === 'linux') {
|
|
42
|
+
const glibc = `@evgeny.skarlat/cli-linux-${a}`;
|
|
43
|
+
const musl = `@evgeny.skarlat/cli-linux-${a}-musl`;
|
|
44
|
+
return isMuslLinux() ? [musl, glibc] : [glibc, musl];
|
|
45
|
+
}
|
|
46
|
+
if (p === 'darwin') return [`@evgeny.skarlat/cli-darwin-${a}`];
|
|
47
|
+
if (p === 'win32') return [`@evgeny.skarlat/cli-win32-${a}`];
|
|
48
|
+
return [`@evgeny.skarlat/cli-${p}-${a}`];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function canResolve(pkg) {
|
|
52
|
+
try {
|
|
53
|
+
require.resolve(`${pkg}/package.json`);
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function main() {
|
|
61
|
+
if (process.env.RENRE_KIT_SKIP_POSTINSTALL_CHECK) return;
|
|
62
|
+
|
|
63
|
+
const candidates = expectedCandidates();
|
|
64
|
+
const resolved = candidates.find(canResolve);
|
|
65
|
+
if (resolved) {
|
|
66
|
+
if (process.env.RENRE_KIT_DEBUG_DISPATCH) {
|
|
67
|
+
process.stderr.write(`[renre-kit-postinstall] resolved ${resolved}\n`);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const p = osPlatform();
|
|
73
|
+
const a = osArch();
|
|
74
|
+
const libc = p === 'linux' ? (isMuslLinux() ? 'musl' : 'glibc') : null;
|
|
75
|
+
process.stderr.write(
|
|
76
|
+
[
|
|
77
|
+
'',
|
|
78
|
+
`renre-kit: WARNING — no platform binary package was installed.`,
|
|
79
|
+
` Detected host: ${p}/${a}${libc ? ` (${libc})` : ''}`,
|
|
80
|
+
` Looked for: ${candidates.join(', ')}`,
|
|
81
|
+
'',
|
|
82
|
+
` This usually means npm filtered out the optionalDependency for an`,
|
|
83
|
+
` unsupported os/cpu/libc combination, or the registry mirror did not`,
|
|
84
|
+
` carry the platform package.`,
|
|
85
|
+
'',
|
|
86
|
+
` Fix by reinstalling once the right package is available:`,
|
|
87
|
+
` npm install -g @evgeny.skarlat/cli`,
|
|
88
|
+
` Or set RENRE_KIT_BIN_PATH to a binary you trust to bypass resolution.`,
|
|
89
|
+
'',
|
|
90
|
+
].join('\n'),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
main();
|
|
96
|
+
} catch (err) {
|
|
97
|
+
// Never fail the install on a verification hiccup.
|
|
98
|
+
process.stderr.write(
|
|
99
|
+
`renre-kit: postinstall verification error (non-fatal): ${err?.message ?? err}\n`,
|
|
100
|
+
);
|
|
101
|
+
}
|