@nemus-cli/nemus 0.3.3 → 0.4.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/CHANGELOG.md CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-09-01
11
+
12
+ ### Changed
13
+
14
+ - **First-time setup now runs right after install.** On a fresh `npm install -g`
15
+ in an interactive terminal, the postinstall step launches `nemus configure`
16
+ automatically (it installs the shell integration + optional MCP server and
17
+ prints the activation reminder) instead of only suggesting you run it. It runs
18
+ **only on a first install** (skipped once `~/.nemus/config.json` exists, so
19
+ upgrades never re-prompt) and **only when a controlling terminal is reachable**
20
+ (via `/dev/tty`, since npm pipes postinstall stdio) — piped/scripted/CI installs,
21
+ or `NEMUS_SKIP_CONFIGURE=1`, fall back to the previous non-interactive path
22
+ (shell integration + a printed tip), so an install can never hang.
23
+
10
24
  ## [0.3.3] - 2026-08-30
11
25
 
12
26
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nemus-cli/nemus",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "workspaces": [
5
5
  "packages/*"
6
6
  ],
@@ -12,8 +12,9 @@
12
12
 
13
13
  'use strict';
14
14
 
15
- const { execSync } = require('child_process');
15
+ const { execSync, execFileSync } = require('child_process');
16
16
  const fs = require('fs');
17
+ const os = require('os');
17
18
  const path = require('path');
18
19
 
19
20
  // Skip in CI environments
@@ -21,6 +22,72 @@ if (process.env.CI) process.exit(0);
21
22
 
22
23
  const PKG_ROOT = path.join(__dirname, '..');
23
24
  const SHELL_SCRIPT = path.join(PKG_ROOT, 'install-shell-integration.sh');
25
+ const CLI_BIN = path.join(PKG_ROOT, 'bin', 'workspace.js');
26
+
27
+ // ── First-time interactive setup ─────────────────────────────────────────────
28
+ // On a FRESH install, run `nemus configure` right away so setup happens after
29
+ // install instead of the user having to remember to. `configure` is a superset
30
+ // of this script's work — it installs the shell integration + (optionally) the
31
+ // MCP server and prints the source reminder — so on success we exit here.
32
+ //
33
+ // npm runs postinstall with stdio PIPED (not a TTY) unless `--foreground-scripts`
34
+ // is set, so we can't gate on process.std*.isTTY (it's falsy during a normal
35
+ // `npm i -g`, so the prompt would never fire). Instead we reach the controlling
36
+ // terminal directly via /dev/tty — openable during an interactive install even
37
+ // though npm piped our own stdio — and wire configure's stdio to it. With no
38
+ // controlling terminal (piped/scripted installs) the open fails and we fall
39
+ // through to the non-interactive tip path, so an install can never hang.
40
+ function cacheDir() {
41
+ return (
42
+ process.env.NEMUS_CACHE_DIR ||
43
+ process.env.WORKSPACE_MANAGER_CACHE_DIR ||
44
+ path.join(os.homedir(), '.nemus')
45
+ );
46
+ }
47
+
48
+ // NEMUS_SKIP_CONFIGURE opts out — but only for real values: `0`/`false`/empty do
49
+ // NOT skip (a user setting `=0` means "don't skip").
50
+ function optedOut() {
51
+ const v = (process.env.NEMUS_SKIP_CONFIGURE || '').trim().toLowerCase();
52
+ return v !== '' && v !== '0' && v !== 'false';
53
+ }
54
+
55
+ // The config marker `configure` writes (utils/config.ts: saveUserConfig ->
56
+ // <NEMUS_CACHE_DIR|WORKSPACE_MANAGER_CACHE_DIR|~/.nemus>/config.json). Same
57
+ // precedence as here, so its presence reliably means "already configured".
58
+ function alreadyConfigured() {
59
+ return fs.existsSync(path.join(cacheDir(), 'config.json'));
60
+ }
61
+
62
+ // A read/write fd on the controlling terminal, or null if there isn't one.
63
+ function openControllingTty() {
64
+ if (process.platform === 'win32') return null; // no /dev/tty; shell integration is bash/zsh anyway
65
+ try {
66
+ return fs.openSync('/dev/tty', 'r+');
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ if (!optedOut() && fs.existsSync(CLI_BIN) && !alreadyConfigured()) {
73
+ const tty = openControllingTty();
74
+ if (tty !== null) {
75
+ try {
76
+ // argv form (no shell) so an odd package path with quotes/$/backticks is
77
+ // safe; stdio wired to the real terminal so inquirer can prompt.
78
+ // process.execPath (not 'node') — node may not be on PATH during a
79
+ // lifecycle script (nvm/Windows), and an ENOENT here would be swallowed by
80
+ // the catch, silently skipping configure in the very case this targets.
81
+ execFileSync(process.execPath, [CLI_BIN, 'configure'], { stdio: [tty, tty, tty] });
82
+ fs.closeSync(tty);
83
+ process.exit(0); // configure handled shell integration + reminder
84
+ } catch {
85
+ try { fs.closeSync(tty); } catch { /* ignore */ }
86
+ // Canceled / failed — fall through to the standard setup + tip below so the
87
+ // install still leaves things in a working state.
88
+ }
89
+ }
90
+ }
24
91
 
25
92
  const shell = process.env.SHELL || '';
26
93
  const shellType = shell.endsWith('/zsh') ? 'zsh'