nunmai 0.1.2 → 0.1.4
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 +6 -3
- package/bin/nunmai.js +102 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,10 +15,13 @@ engine; the first run opens the AI-account wizard (Claude, ChatGPT, Kimi,
|
|
|
15
15
|
Gemini, OpenRouter).
|
|
16
16
|
|
|
17
17
|
- macOS, Linux, Windows 10/11 (PowerShell), Android (Termux).
|
|
18
|
-
- **Local install** (`npm install nunmai`, no `-g`): run it with `npx nunmai`.
|
|
18
|
+
- **Local install** (`npm install nunmai`, no `-g`): run it once with `npx nunmai`.
|
|
19
19
|
npm ≥ 11.19 blocks dependency install scripts by default, so the engine is
|
|
20
|
-
installed on
|
|
21
|
-
|
|
20
|
+
installed on that first run. The launcher then puts a plain `nunmai` command
|
|
21
|
+
on your PATH itself — `~/.local/bin` on macOS/Linux, `%LOCALAPPDATA%\nunmai\bin`
|
|
22
|
+
(registered on the user PATH) on Windows — so after opening a new terminal
|
|
23
|
+
`nunmai` just works. To skip even that first `npx`, allow the script:
|
|
24
|
+
`npm install nunmai --allow-scripts=nunmai`.
|
|
22
25
|
- If npm skipped the install script (`--ignore-scripts`, `CI` set, or the
|
|
23
26
|
allow-scripts policy above), the install simply happens on the first run.
|
|
24
27
|
- After `nunmai uninstall`, the `nunmai` command falls back to this launcher —
|
package/bin/nunmai.js
CHANGED
|
@@ -33,11 +33,27 @@ const IS_WIN = process.platform === "win32";
|
|
|
33
33
|
const SELF = fs.realpathSync(__filename);
|
|
34
34
|
const DRY = process.env.NUNMAI_BOOTSTRAP_DRY_RUN === "1";
|
|
35
35
|
|
|
36
|
+
// Marker stamped into the Windows fallback shim (a .cmd that re-enters this
|
|
37
|
+
// file). findLauncher() must never treat that shim as the engine launcher or
|
|
38
|
+
// `nunmai` would recurse into itself forever.
|
|
39
|
+
const WIN_SHIM_MARKER = "rem nunmai-npm-shim";
|
|
40
|
+
|
|
41
|
+
function winBinDir() {
|
|
42
|
+
// Same managed dir install.ps1 uses for the real launchers (Set-PathVariable):
|
|
43
|
+
// %NUNMAI_HOME%\bin, defaulting to %LOCALAPPDATA%\nunmai\bin.
|
|
44
|
+
const home = os.homedir();
|
|
45
|
+
const nunmaiHome = process.env.NUNMAI_HOME || path.join(process.env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "nunmai");
|
|
46
|
+
return path.join(nunmaiHome, "bin");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isWinShim(p) {
|
|
50
|
+
try { return p.toLowerCase().endsWith(".cmd") && fs.readFileSync(p, "utf8").includes(WIN_SHIM_MARKER); } catch (_) { return false; }
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
function candidates() {
|
|
37
54
|
const home = os.homedir();
|
|
38
55
|
if (IS_WIN) {
|
|
39
|
-
const
|
|
40
|
-
const bin = path.join(nunmaiHome, "bin");
|
|
56
|
+
const bin = winBinDir();
|
|
41
57
|
return [path.join(bin, "nunmai.exe"), path.join(bin, "nunmai.cmd")];
|
|
42
58
|
}
|
|
43
59
|
const list = [];
|
|
@@ -52,6 +68,7 @@ function findLauncher() {
|
|
|
52
68
|
for (const p of candidates()) {
|
|
53
69
|
try {
|
|
54
70
|
if (fs.realpathSync(p) === SELF) continue; // never recurse into this shim
|
|
71
|
+
if (IS_WIN && isWinShim(p)) continue; // ...nor into the Windows .cmd shim
|
|
55
72
|
fs.accessSync(p, fs.constants.X_OK);
|
|
56
73
|
return p;
|
|
57
74
|
} catch (_) { /* not there */ }
|
|
@@ -64,7 +81,7 @@ function ensureFallbackLauncher() {
|
|
|
64
81
|
// ./node_modules/.bin, which is not on PATH. Make sure `nunmai` resolves
|
|
65
82
|
// anyway by linking this shim into ~/.local/bin (or $PREFIX/bin on Termux).
|
|
66
83
|
// The real installer replaces the link with the engine launcher (ln -sf).
|
|
67
|
-
if (IS_WIN) return
|
|
84
|
+
if (IS_WIN) return ensureWinFallbackLauncher();
|
|
68
85
|
const binDir = process.env.PREFIX && fs.existsSync(path.join(process.env.PREFIX, "bin"))
|
|
69
86
|
? path.join(process.env.PREFIX, "bin")
|
|
70
87
|
: path.join(os.homedir(), ".local", "bin");
|
|
@@ -83,6 +100,51 @@ function ensureFallbackLauncher() {
|
|
|
83
100
|
}
|
|
84
101
|
}
|
|
85
102
|
|
|
103
|
+
function ensureWinFallbackLauncher() {
|
|
104
|
+
// Windows twin of the symlink above: drop a nunmai.cmd shim into the managed
|
|
105
|
+
// bin dir and register that dir on the User PATH, so a plain `nunmai` works
|
|
106
|
+
// after a local `npm install nunmai` (where npm >= 11.19 skips postinstall
|
|
107
|
+
// and node_modules\.bin is not on PATH). install.ps1 later overwrites the
|
|
108
|
+
// shim with the real launcher (Install-NunmaiCommandLaunchers) and keeps the
|
|
109
|
+
// same PATH entry, so nothing here has to be undone.
|
|
110
|
+
const binDir = winBinDir();
|
|
111
|
+
const target = path.join(binDir, "nunmai.cmd");
|
|
112
|
+
try {
|
|
113
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
114
|
+
// Never clobber a real engine launcher.
|
|
115
|
+
if (fs.existsSync(path.join(binDir, "nunmai.exe"))) return null;
|
|
116
|
+
if (fs.existsSync(target) && !isWinShim(target)) return null;
|
|
117
|
+
const body = `@echo off\r\n${WIN_SHIM_MARKER}: ${SELF}\r\n"${process.execPath}" "${SELF}" %*\r\n`;
|
|
118
|
+
fs.writeFileSync(target, body);
|
|
119
|
+
ensureWinUserPath(binDir);
|
|
120
|
+
return target;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error(`nunmai: could not stage launcher into ${binDir}: ${e.message}`);
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function ensureWinUserPath(binDir) {
|
|
128
|
+
// Persist binDir at the front of the User PATH (registry), the same way
|
|
129
|
+
// install.ps1's Set-PathVariable does. Idempotent; best effort.
|
|
130
|
+
const norm = (p) => p.trim().replace(/[\\/]+$/, "").toLowerCase();
|
|
131
|
+
const onPath = (process.env.PATH || "").split(";").some((p) => norm(p) === norm(binDir));
|
|
132
|
+
if (onPath || DRY) return;
|
|
133
|
+
const ps = [
|
|
134
|
+
`$d='${binDir.replace(/'/g, "''")}'`,
|
|
135
|
+
"$cur=[Environment]::GetEnvironmentVariable('Path','User')",
|
|
136
|
+
"$items=@(); if ($cur) { $items=@($cur -split ';') }",
|
|
137
|
+
"if (-not ($items | Where-Object { $_.TrimEnd('\\') -ieq $d })) { [Environment]::SetEnvironmentVariable('Path', ((@($d) + $items) -join ';'), 'User'); exit 0 }",
|
|
138
|
+
"exit 3",
|
|
139
|
+
].join("; ");
|
|
140
|
+
const r = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", ps], { stdio: "ignore" });
|
|
141
|
+
if (r.status === 0) {
|
|
142
|
+
console.log(`nunmai: added ${binDir} to your user PATH (open a new terminal, then run \`nunmai\`).`);
|
|
143
|
+
} else if (r.status !== 3) {
|
|
144
|
+
console.error(`nunmai: could not update the user PATH automatically. Add this folder to PATH to use \`nunmai\` directly: ${binDir}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
86
148
|
function ensurePathLine(binDir) {
|
|
87
149
|
// Add ~/.local/bin to PATH in the user's shell rc if it is not there already.
|
|
88
150
|
if (binDir !== path.join(os.homedir(), ".local", "bin")) return;
|
|
@@ -168,12 +230,49 @@ function postinstall() {
|
|
|
168
230
|
return 0;
|
|
169
231
|
}
|
|
170
232
|
|
|
233
|
+
// Commands that must never trigger an engine install when the engine is
|
|
234
|
+
// absent: a user asking to remove, or merely inspect, Nunmai should not be
|
|
235
|
+
// handed a multi-minute full install first.
|
|
236
|
+
function handleWithoutEngine(argv) {
|
|
237
|
+
const cmd = (argv[0] || "").toLowerCase();
|
|
238
|
+
if (cmd === "uninstall") {
|
|
239
|
+
let removed = false;
|
|
240
|
+
if (IS_WIN) {
|
|
241
|
+
// Drop our own .cmd shim (never a real launcher — those carry no marker).
|
|
242
|
+
const shim = path.join(winBinDir(), "nunmai.cmd");
|
|
243
|
+
if (isWinShim(shim)) { try { fs.unlinkSync(shim); removed = true; } catch (_) { /* best effort */ } }
|
|
244
|
+
}
|
|
245
|
+
console.log("Nunmai Engine is not installed on this machine — nothing to remove." + (removed ? " (Removed the launcher shim.)" : ""));
|
|
246
|
+
console.log("To remove this npm launcher too: npm uninstall -g nunmai (or `npm uninstall nunmai` for a local install)");
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
250
|
+
console.log(`nunmai launcher ${require("../package.json").version} (engine not installed — run \`nunmai\` to install)`);
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
254
|
+
console.log([
|
|
255
|
+
"Nunmai Engine is not installed on this machine yet.",
|
|
256
|
+
"",
|
|
257
|
+
" nunmai install the engine, then start it",
|
|
258
|
+
" nunmai --version show the launcher version",
|
|
259
|
+
" nunmai uninstall remove the engine (nothing to remove right now)",
|
|
260
|
+
"",
|
|
261
|
+
`Manual install: ${manualHint().trim()}`,
|
|
262
|
+
].join("\n"));
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
171
268
|
function main() {
|
|
172
269
|
const argv = process.argv.slice(2);
|
|
173
270
|
if (argv[0] === "--bootstrap-postinstall") process.exit(postinstall());
|
|
174
271
|
|
|
175
272
|
let launcher = findLauncher();
|
|
176
273
|
if (!launcher) {
|
|
274
|
+
const handled = handleWithoutEngine(argv);
|
|
275
|
+
if (handled !== null) process.exit(handled);
|
|
177
276
|
const code = install(false);
|
|
178
277
|
if (code !== 0) {
|
|
179
278
|
console.error(`\nnunmai: installer exited with code ${code}. You can retry it manually:\n${manualHint()}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nunmai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Nunmai Engine — `npm i nunmai` (global or local) installs the engine with all features and dependencies; run `nunmai` to start.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://nunmai.in",
|