@niroai/niro 0.9.0 → 0.10.1
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 +69 -0
- package/README.md +131 -211
- package/package.json +1 -1
- package/src/commands/ab.js +198 -0
- package/src/commands/add-project.js +11 -11
- package/src/commands/add-repo.js +23 -23
- package/src/commands/build.js +5 -5
- package/src/commands/delete.js +3 -3
- package/src/commands/edit.js +11 -11
- package/src/commands/info.js +2 -2
- package/src/commands/init.js +108 -87
- package/src/commands/login.js +25 -5
- package/src/commands/projects.js +1 -1
- package/src/commands/release.js +7 -7
- package/src/commands/remove.js +5 -5
- package/src/commands/start.js +567 -0
- package/src/commands/takeover.js +19 -19
- package/src/commands/watch.js +35 -35
- package/src/index.js +57 -3
- package/src/lib/folderSync.js +13 -8
- package/src/lib/moduleExcludeSuggestions.js +5 -5
- package/src/lib/nestedOutput.js +49 -0
- package/src/lib/onboardTarget.js +5 -5
- package/src/lib/prompt.js +3 -0
- package/src/lib/service.js +20 -12
- package/src/lib/spinner.js +28 -7
- package/src/lib/watchDaemon.js +9 -9
- package/src/mcp/setup.js +198 -160
- package/src/mcp/teardown.js +53 -53
package/src/lib/prompt.js
CHANGED
|
@@ -18,6 +18,9 @@ function rl() {
|
|
|
18
18
|
* Also shared by the MCP setup/teardown wizards' own ask() helpers.
|
|
19
19
|
*/
|
|
20
20
|
function formatPrompt(question, defaultValue) {
|
|
21
|
+
// Questions asked from inside a nested step sit at that step's depth. readline writes
|
|
22
|
+
// straight to stdout, so withIndent's console wrappers never see them.
|
|
23
|
+
question = " ".repeat(require("./nestedOutput").currentIndent()) + question;
|
|
21
24
|
return defaultValue != null && defaultValue !== ""
|
|
22
25
|
? `${question} ${color.dim(`(default: ${defaultValue})`)}: `
|
|
23
26
|
: `${question}: `;
|
package/src/lib/service.js
CHANGED
|
@@ -68,26 +68,30 @@ function install() {
|
|
|
68
68
|
// Stop any existing instance so we can re-register cleanly
|
|
69
69
|
spawnSync("pm2", ["delete", PROCESS_NAME], { shell: true, stdio: "pipe" });
|
|
70
70
|
|
|
71
|
-
//
|
|
71
|
+
// PM2's own output is captured, not inherited. Inheriting it put ~55 lines into the middle
|
|
72
|
+
// of setup: a box-drawing process table, the entire generated systemd unit file, and the
|
|
73
|
+
// user's PATH twice. None of it is ours and none of it is actionable while it is working.
|
|
74
|
+
// It IS printed on failure, where it is the only diagnostic there is.
|
|
72
75
|
const startResult = spawnSync(
|
|
73
76
|
"pm2",
|
|
74
77
|
["start", niroBin, "--name", PROCESS_NAME, "--", "watch"],
|
|
75
|
-
{ shell: true,
|
|
78
|
+
{ shell: true, encoding: "utf8" }
|
|
76
79
|
);
|
|
77
80
|
if (startResult.status !== 0) {
|
|
81
|
+
console.error(color.dim(`${startResult.stdout || ""}${startResult.stderr || ""}`));
|
|
78
82
|
throw new Error("pm2 start failed");
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
// Persist the process list so PM2 restores it after a manual `pm2 resurrect`
|
|
82
|
-
spawnSync("pm2", ["save"], { shell: true,
|
|
86
|
+
spawnSync("pm2", ["save"], { shell: true, encoding: "utf8" });
|
|
83
87
|
|
|
84
88
|
// Wire into OS startup — pm2 startup detects systemd / launchd / etc.
|
|
85
89
|
// It prints the exact privileged command we need to run.
|
|
86
90
|
console.log(
|
|
87
|
-
`\n
|
|
91
|
+
`\n ${color.bold("Setting up auto-start on login")} — PM2 will ask for your password to register`
|
|
88
92
|
);
|
|
89
93
|
console.log(
|
|
90
|
-
`
|
|
94
|
+
` with the system startup manager (${getInitSystem()}). This only needs to happen once.\n`
|
|
91
95
|
);
|
|
92
96
|
|
|
93
97
|
const startupProbe = spawnSync("pm2", ["startup"], {
|
|
@@ -97,21 +101,25 @@ function install() {
|
|
|
97
101
|
|
|
98
102
|
const setupCmd = extractSetupCommand(startupProbe.stdout + startupProbe.stderr);
|
|
99
103
|
if (setupCmd) {
|
|
100
|
-
console.log(`
|
|
104
|
+
console.log(` Running: ${color.dim(setupCmd)}\n`);
|
|
105
|
+
// stdio is split: stdin stays connected so sudo can still prompt for the password, but
|
|
106
|
+
// PM2's forty lines of generated unit file are captured and only shown if it fails.
|
|
101
107
|
const r = spawnSync(setupCmd, {
|
|
102
108
|
shell: true,
|
|
103
|
-
stdio: "inherit",
|
|
109
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
110
|
+
encoding: "utf8",
|
|
104
111
|
});
|
|
105
112
|
if (r.status !== 0) {
|
|
113
|
+
console.error(color.dim(`${r.stdout || ""}${r.stderr || ""}`));
|
|
106
114
|
console.log(
|
|
107
115
|
color.yellow(
|
|
108
|
-
`
|
|
116
|
+
` ⚠ Startup hook failed (exit ${r.status}). You can run it manually:\n ${setupCmd}`
|
|
109
117
|
)
|
|
110
118
|
);
|
|
111
119
|
}
|
|
112
120
|
} else {
|
|
113
121
|
// PM2 may print nothing if startup is already configured
|
|
114
|
-
console.log(`
|
|
122
|
+
console.log(` ${color.green("✓")} Startup already configured.`);
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
return true;
|
|
@@ -127,7 +135,7 @@ function uninstall() {
|
|
|
127
135
|
spawnSync("pm2", ["save"], { shell: true, stdio: "inherit" });
|
|
128
136
|
|
|
129
137
|
console.log(
|
|
130
|
-
`\n
|
|
138
|
+
`\n ${color.bold("Removing auto-start hook")} — PM2 may ask for your password.\n`
|
|
131
139
|
);
|
|
132
140
|
const unstartupResult = spawnSync("pm2", ["unstartup"], {
|
|
133
141
|
shell: true,
|
|
@@ -137,7 +145,7 @@ function uninstall() {
|
|
|
137
145
|
unstartupResult.stdout + unstartupResult.stderr
|
|
138
146
|
);
|
|
139
147
|
if (setupCmd) {
|
|
140
|
-
console.log(`
|
|
148
|
+
console.log(` Running: ${color.dim(setupCmd)}\n`);
|
|
141
149
|
spawnSync(setupCmd, { shell: true, stdio: "inherit" });
|
|
142
150
|
}
|
|
143
151
|
|
|
@@ -149,7 +157,7 @@ function uninstall() {
|
|
|
149
157
|
*/
|
|
150
158
|
function status() {
|
|
151
159
|
if (!pm2Available()) {
|
|
152
|
-
console.log(color.yellow("
|
|
160
|
+
console.log(color.yellow(" PM2 is not installed. Run: npm install -g pm2"));
|
|
153
161
|
return;
|
|
154
162
|
}
|
|
155
163
|
spawnSync("pm2", ["show", PROCESS_NAME], { shell: true, stdio: "inherit" });
|
package/src/lib/spinner.js
CHANGED
|
@@ -22,6 +22,17 @@ const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "
|
|
|
22
22
|
const FRAME_MS = 120;
|
|
23
23
|
const HEARTBEAT_MS = 30_000;
|
|
24
24
|
const ERASE_LINE = "\r\x1b[2K";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Current nesting depth as spaces.
|
|
28
|
+
*
|
|
29
|
+
* The spinner writes straight to the stream, so withIndent's console wrappers never see it.
|
|
30
|
+
* Left alone, the build status and its "Build completed" line print flush left in the middle
|
|
31
|
+
* of an indented step, which is where they were.
|
|
32
|
+
*/
|
|
33
|
+
function pad() {
|
|
34
|
+
return " ".repeat(require("./nestedOutput").currentIndent());
|
|
35
|
+
}
|
|
25
36
|
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
26
37
|
|
|
27
38
|
/**
|
|
@@ -40,8 +51,18 @@ function fit(line, width) {
|
|
|
40
51
|
* Labels can carry backend text (e.g. build log messages); an embedded
|
|
41
52
|
* newline would defeat the single-line overwrite, so flatten whitespace.
|
|
42
53
|
*/
|
|
54
|
+
/**
|
|
55
|
+
* Flatten a label to one line, but KEEP its leading indent.
|
|
56
|
+
*
|
|
57
|
+
* The old version trimmed both ends, which silently ate the indent callers use to place the
|
|
58
|
+
* label in the output tree: " Waiting for build..." came out flush left, four columns
|
|
59
|
+
* off from every line around it. Interior whitespace still collapses, because a newline would
|
|
60
|
+
* defeat the single-line overwrite.
|
|
61
|
+
*/
|
|
43
62
|
function sanitize(label) {
|
|
44
|
-
|
|
63
|
+
const text = String(label);
|
|
64
|
+
const indent = /^[ \t]*/.exec(text)[0].replace(/\t/g, " ");
|
|
65
|
+
return indent + text.replace(/\s+/g, " ").trim();
|
|
45
66
|
}
|
|
46
67
|
|
|
47
68
|
function start(label, opts = {}) {
|
|
@@ -58,9 +79,9 @@ function start(label, opts = {}) {
|
|
|
58
79
|
let stopped = false;
|
|
59
80
|
|
|
60
81
|
if (!isTty) {
|
|
61
|
-
stream.write(`${label}\n`);
|
|
82
|
+
stream.write(`${pad()}${label}\n`);
|
|
62
83
|
const timer = setInterval(() => {
|
|
63
|
-
stream.write(`${label} ${color.dim(`(still waiting — ${elapsed()}s)`)}\n`);
|
|
84
|
+
stream.write(`${pad()}${label} ${color.dim(`(still waiting — ${elapsed()}s)`)}\n`);
|
|
64
85
|
}, heartbeatMs);
|
|
65
86
|
if (timer.unref) timer.unref();
|
|
66
87
|
return {
|
|
@@ -68,13 +89,13 @@ function start(label, opts = {}) {
|
|
|
68
89
|
newLabel = sanitize(newLabel);
|
|
69
90
|
if (stopped || newLabel === label) return;
|
|
70
91
|
label = newLabel;
|
|
71
|
-
stream.write(`${label}\n`);
|
|
92
|
+
stream.write(`${pad()}${label}\n`);
|
|
72
93
|
},
|
|
73
94
|
stop(finalLine) {
|
|
74
95
|
clearInterval(timer);
|
|
75
96
|
if (stopped) return;
|
|
76
97
|
stopped = true;
|
|
77
|
-
if (finalLine) stream.write(finalLine + "\n");
|
|
98
|
+
if (finalLine) stream.write(pad() + finalLine + "\n");
|
|
78
99
|
},
|
|
79
100
|
};
|
|
80
101
|
}
|
|
@@ -83,7 +104,7 @@ function start(label, opts = {}) {
|
|
|
83
104
|
const render = () => {
|
|
84
105
|
const frame = FRAMES[i = (i + 1) % FRAMES.length];
|
|
85
106
|
const line = `${color.cyan(frame)} ${label} ${color.dim("(" + elapsed() + "s)")}`;
|
|
86
|
-
stream.write(ERASE_LINE + fit(line, (stream.columns || 80) - 1));
|
|
107
|
+
stream.write(ERASE_LINE + pad() + fit(line, (stream.columns || 80) - 1 - pad().length));
|
|
87
108
|
};
|
|
88
109
|
|
|
89
110
|
render();
|
|
@@ -101,7 +122,7 @@ function start(label, opts = {}) {
|
|
|
101
122
|
if (stopped) return;
|
|
102
123
|
stopped = true;
|
|
103
124
|
stream.write(ERASE_LINE);
|
|
104
|
-
if (finalLine) stream.write(finalLine + "\n");
|
|
125
|
+
if (finalLine) stream.write(pad() + finalLine + "\n");
|
|
105
126
|
},
|
|
106
127
|
};
|
|
107
128
|
}
|
package/src/lib/watchDaemon.js
CHANGED
|
@@ -33,7 +33,7 @@ const color = require("./color");
|
|
|
33
33
|
*/
|
|
34
34
|
async function ensureWatchDaemon({ skipPrompts = false } = {}) {
|
|
35
35
|
if (!service.pm2Available()) {
|
|
36
|
-
console.log(`\n
|
|
36
|
+
console.log(`\n ${color.yellow("⚠")} PM2 is not installed. Niro uses PM2 to keep your synced folders`);
|
|
37
37
|
console.log(` up to date in the background, including after a reboot.`);
|
|
38
38
|
|
|
39
39
|
if (skipPrompts || prompt.isNonInteractive()) {
|
|
@@ -49,29 +49,29 @@ async function ensureWatchDaemon({ skipPrompts = false } = {}) {
|
|
|
49
49
|
|
|
50
50
|
console.log("");
|
|
51
51
|
if (!service.installPm2()) {
|
|
52
|
-
console.log(`\n
|
|
52
|
+
console.log(`\n ${color.yellow("⚠")} PM2 install failed (a permission error is the usual cause).`);
|
|
53
53
|
console.log(` Try manually: ${color.cyan("sudo npm install -g pm2")} then ${color.cyan("niro service install")}`);
|
|
54
54
|
return { ok: false, reason: "pm2_install_failed" };
|
|
55
55
|
}
|
|
56
|
-
console.log(`
|
|
56
|
+
console.log(` ${color.green("✓")} PM2 installed.`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
console.log(`\n
|
|
59
|
+
console.log(`\n ${color.cyan("●")} Installing niro-watch daemon (keeps your synced folders up to date, auto-starts on login)...`);
|
|
60
60
|
try {
|
|
61
61
|
// install() returns false when pm2 turns out not to be runnable after
|
|
62
62
|
// all — e.g. npm installed it into a global bin dir that isn't on PATH
|
|
63
63
|
// (npm exits 0 for that). Treat it as the failure it is; the old code's
|
|
64
64
|
// unchecked call here is how "✓ Done." lied to customers.
|
|
65
65
|
if (!service.install()) {
|
|
66
|
-
console.log(`
|
|
67
|
-
console.log(`
|
|
66
|
+
console.log(` ${color.yellow("⚠")} PM2 was installed but isn't runnable from this shell (its global bin`);
|
|
67
|
+
console.log(` directory may not be on your PATH). Open a NEW terminal and run ${color.cyan("niro service install")}.`);
|
|
68
68
|
return { ok: false, reason: "daemon_install_failed" };
|
|
69
69
|
}
|
|
70
|
-
console.log(`
|
|
70
|
+
console.log(` ${color.green("✓")} niro-watch is running and will start automatically on login.`);
|
|
71
71
|
return { ok: true };
|
|
72
72
|
} catch (err) {
|
|
73
|
-
console.log(`
|
|
74
|
-
console.log(`
|
|
73
|
+
console.log(` ${color.yellow("⚠")} Could not install the watch daemon: ${err.message}`);
|
|
74
|
+
console.log(` Run ${color.cyan("niro service install")} to try again, or ${color.cyan("niro watch")} to sync manually.`);
|
|
75
75
|
return { ok: false, reason: "daemon_install_failed" };
|
|
76
76
|
}
|
|
77
77
|
}
|