@niroai/niro 0.17.0 → 1.0.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 +136 -0
- package/README.md +123 -302
- package/bin/niro.js +12 -1
- package/docs/agent-antigravity.md +37 -0
- package/docs/agent-claude-code.md +48 -0
- package/docs/agent-claude-desktop.md +47 -0
- package/docs/agent-cline.md +42 -0
- package/docs/agent-codex.md +45 -0
- package/docs/agent-copilot-cli.md +42 -0
- package/docs/agent-cursor.md +44 -0
- package/docs/agent-factory.md +42 -0
- package/docs/agent-gemini-cli.md +42 -0
- package/docs/agent-goose.md +42 -0
- package/docs/agent-kiro.md +37 -0
- package/docs/agent-opencode.md +37 -0
- package/docs/agent-vscode.md +45 -0
- package/docs/agent-windsurf.md +38 -0
- package/docs/answer-shapes.md +69 -0
- package/docs/builds-and-updates.md +68 -0
- package/docs/cloud-README.md +372 -0
- package/docs/configuring-a-folder.md +51 -0
- package/docs/connect-ci.md +24 -0
- package/docs/connect-overview.md +20 -0
- package/docs/connect-review-bots.md +17 -0
- package/docs/connect-team-server.md +12 -0
- package/docs/connect-terminal.md +49 -0
- package/docs/connect-web-agents.md +9 -0
- package/docs/connect-your-ai-assistant.md +59 -175
- package/docs/coverage-audit-and-consent.md +51 -0
- package/docs/evaluate-what-niro-did.md +271 -0
- package/docs/faq-assistants-and-mcp.md +122 -0
- package/docs/faq-feedback-and-receipts.md +33 -0
- package/docs/faq-indexing.md +127 -0
- package/docs/faq-install-and-setup.md +103 -0
- package/docs/faq-licence-and-plans.md +86 -0
- package/docs/faq-login-and-account.md +69 -0
- package/docs/faq-offline.md +34 -0
- package/docs/faq-performance.md +33 -0
- package/docs/faq-privacy-and-security.md +113 -0
- package/docs/faq-uninstall.md +47 -0
- package/docs/faq-updates.md +59 -0
- package/docs/faq-worktrees-and-branches.md +59 -0
- package/docs/faq-wrong-or-missing-answers.md +275 -0
- package/docs/hosted-only.md +33 -0
- package/docs/how-the-graph-works.md +47 -0
- package/docs/index.md +157 -0
- package/docs/licence-and-plans.md +72 -0
- package/docs/lifecycle-debug.md +48 -0
- package/docs/lifecycle-onboarding.md +32 -0
- package/docs/lifecycle-overview.md +77 -0
- package/docs/lifecycle-plan-and-design.md +44 -0
- package/docs/lifecycle-review.md +30 -0
- package/docs/lifecycle-security-and-privacy.md +35 -0
- package/docs/lifecycle-test.md +28 -0
- package/docs/lifecycle-write-code.md +35 -0
- package/docs/llms-full.txt +8015 -0
- package/docs/llms.txt +126 -0
- package/docs/playbook-add-a-feature.md +108 -0
- package/docs/playbook-change-safely.md +93 -0
- package/docs/playbook-data-and-events.md +107 -0
- package/docs/playbook-debug-after-a-change.md +96 -0
- package/docs/playbook-debug-config.md +95 -0
- package/docs/playbook-debug-exception.md +124 -0
- package/docs/playbook-debug-failing-service-call.md +119 -0
- package/docs/playbook-debug-slow-path.md +80 -0
- package/docs/playbook-debug-wrong-value.md +137 -0
- package/docs/playbook-onboard.md +95 -0
- package/docs/playbook-review-a-change.md +72 -0
- package/docs/playbook-test-a-change.md +73 -0
- package/docs/reference-cli.md +113 -0
- package/docs/reference-config-files.md +42 -0
- package/docs/reference-engine-commands.md +56 -0
- package/docs/reference-environment-variables.md +89 -0
- package/docs/reference-exit-codes.md +67 -0
- package/docs/reference-limits.md +75 -0
- package/docs/reference-mcp-tools.md +326 -0
- package/docs/reference-messages.md +180 -0
- package/docs/secrets-and-masking.md +48 -0
- package/docs/supported-c.md +130 -0
- package/docs/supported-cpp.md +157 -0
- package/docs/supported-csharp.md +211 -0
- package/docs/supported-go.md +258 -0
- package/docs/supported-java.md +226 -0
- package/docs/supported-javascript.md +223 -0
- package/docs/supported-kotlin.md +124 -0
- package/docs/supported-overview.md +89 -0
- package/docs/supported-php.md +157 -0
- package/docs/supported-python.md +224 -0
- package/docs/supported-ruby.md +162 -0
- package/docs/supported-rust.md +150 -0
- package/docs/supported-swift.md +133 -0
- package/docs/supported-typescript.md +220 -0
- package/docs/tokens-speed-and-accuracy.md +99 -0
- package/docs/troubleshooting.md +42 -0
- package/docs/uninstall.md +74 -0
- package/docs/what-leaves-your-machine.md +71 -0
- package/docs/where-things-are-stored.md +59 -0
- package/docs/worktrees-and-branches.md +55 -0
- package/package.json +5 -3
- package/src/commands/add-repo.js +5 -0
- package/src/commands/auditSend.js +67 -0
- package/src/commands/build.js +6 -0
- package/src/commands/docsSend.js +73 -0
- package/src/commands/engine.js +120 -0
- package/src/commands/engineRoutes.js +105 -0
- package/src/commands/guide.js +254 -0
- package/src/commands/init.js +6 -2
- package/src/commands/initLocal.js +96 -0
- package/src/commands/journalSend.js +47 -0
- package/src/commands/login.js +89 -12
- package/src/commands/mcp.js +22 -6
- package/src/commands/plan.js +99 -0
- package/src/commands/repoPush.js +33 -0
- package/src/commands/requestIntegration.js +38 -0
- package/src/commands/share.js +56 -0
- package/src/commands/start.js +451 -371
- package/src/commands/status.js +57 -0
- package/src/commands/takeover.js +6 -0
- package/src/commands/uninstall.js +180 -0
- package/src/commands/upgrade.js +38 -8
- package/src/commands/watch.js +420 -6
- package/src/index.js +174 -3
- package/src/lib/agentApi.js +32 -1
- package/src/lib/api.js +20 -6
- package/src/lib/auditUpload.js +293 -0
- package/src/lib/branchDrift.js +149 -0
- package/src/lib/choose.js +116 -0
- package/src/lib/cloudGuard.js +74 -0
- package/src/lib/credentials.js +2 -2
- package/src/lib/docsMarked.js +55 -0
- package/src/lib/docsState.js +213 -0
- package/src/lib/docsUpload.js +481 -0
- package/src/lib/engine.js +619 -0
- package/src/lib/folderSync.js +2 -1
- package/src/lib/graphCheck.js +40 -0
- package/src/lib/guide.js +293 -0
- package/src/lib/guideSearch.js +252 -0
- package/src/lib/integrationRequest.js +89 -0
- package/src/lib/journalUpload.js +147 -0
- package/src/lib/languages.js +129 -0
- package/src/lib/lease.js +375 -0
- package/src/lib/localBuild.js +285 -0
- package/src/lib/localEngine.js +210 -0
- package/src/lib/localOnboard.js +124 -0
- package/src/lib/localWorktrees.js +243 -0
- package/src/lib/machineCheck.js +58 -0
- package/src/lib/mcpClients.js +10 -1
- package/src/lib/migrationNotice.js +118 -0
- package/src/lib/modelFetch.js +153 -0
- package/src/lib/nestedOutput.js +23 -9
- package/src/lib/onboardingLog.js +34 -0
- package/src/lib/planUsage.js +165 -0
- package/src/lib/progressBar.js +46 -0
- package/src/lib/prompt.js +1 -1
- package/src/lib/redact.js +155 -0
- package/src/lib/release.js +33 -0
- package/src/lib/repoPush.js +495 -0
- package/src/lib/secret.js +1 -1
- package/src/lib/service.js +372 -63
- package/src/lib/shareCode.js +367 -0
- package/src/lib/shareExclusions.js +114 -0
- package/src/lib/shareFiles.js +129 -0
- package/src/lib/spinner.js +9 -5
- package/src/lib/watchDaemon.js +33 -10
- package/src/lib/watchState.js +12 -1
- package/src/lib/windowsPaths.js +67 -0
- package/src/mcp/lineCheck.js +382 -0
- package/src/mcp/lineName.js +92 -0
- package/src/mcp/localBackend.js +751 -0
- package/src/mcp/localMode.js +383 -0
- package/src/mcp/localSkill.js +189 -0
- package/src/mcp/server.js +610 -79
- package/src/mcp/setup.js +519 -131
- package/src/mcp/shellSearchHook.js +509 -0
- package/src/mcp/teardown.js +173 -91
- package/src/mcp/usingNiro.js +39 -0
- package/docs/working-on-branches.md +0 -162
package/src/mcp/teardown.js
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* - the niro section in markdown config (CLAUDE.md / AGENTS.md / Windsurf rules)
|
|
12
12
|
* - the generated skills (~/.claude/skills, the shared ~/.agents/skills)
|
|
13
13
|
* - Cursor's "niro:*" allowlist entry and project rule
|
|
14
|
-
* - (with --all) the mcpServerUrl fallback in ~/.niro/config.json
|
|
15
14
|
*
|
|
16
15
|
* It deliberately LEAVES ~/.niro/credentials.json (your `niro login` session) alone —
|
|
17
16
|
* use `niro logout` for that — and never deletes a user's own CLAUDE.md/AGENTS.md, only
|
|
@@ -33,11 +32,14 @@ const path = require("path");
|
|
|
33
32
|
const readline = require("readline");
|
|
34
33
|
const { execSync } = require("child_process");
|
|
35
34
|
const { formatPrompt } = require("../lib/prompt");
|
|
35
|
+
const { choose } = require("../lib/choose");
|
|
36
|
+
const color = require("../lib/color");
|
|
36
37
|
const {
|
|
37
38
|
CLIENTS,
|
|
38
39
|
NIRO_MD_MARKER,
|
|
39
|
-
|
|
40
|
+
NIRO_ALL_MD_SECTIONS,
|
|
40
41
|
isNiroStopHook,
|
|
42
|
+
isNiroLineCheckHook,
|
|
41
43
|
isNiroPostCompactHook,
|
|
42
44
|
isNiroUserPromptHook,
|
|
43
45
|
isNiroPreToolUseHook,
|
|
@@ -45,6 +47,7 @@ const {
|
|
|
45
47
|
isNiroImpactHook,
|
|
46
48
|
userHome,
|
|
47
49
|
} = require("./setup");
|
|
50
|
+
const { isNiroShellSearchHook } = require("./shellSearchHook");
|
|
48
51
|
|
|
49
52
|
// ─── pure helpers (unit-tested — no fs, no $HOME) ────────────────────────────
|
|
50
53
|
|
|
@@ -55,14 +58,22 @@ const {
|
|
|
55
58
|
* Primary path: delete the EXACT section text install wrote. Because we match the whole known
|
|
56
59
|
* block (not "marker → next heading / EOF"), a user's own notes added BELOW the block survive,
|
|
57
60
|
* the file is restored byte-for-byte, duplicate blocks are all removed, and a differently-worded
|
|
58
|
-
* example of the marker elsewhere is untouched. Both LF and CRLF copies are tried.
|
|
61
|
+
* example of the marker elsewhere is untouched. Both LF and CRLF copies are tried. The text cut out
|
|
62
|
+
* starts and ends with a line break, so when the user's text touches it on both sides one line
|
|
63
|
+
* break is put back; without it the line above and the line below were joined into one (check of
|
|
64
|
+
* feat/niro-guide, 2026-11-10: 8 of 91 user lines on upgrade, 10 on uninstall).
|
|
59
65
|
*
|
|
60
66
|
* Fallback (the section text drifted — older install version or a hand-edit): a BOUNDED removal
|
|
61
|
-
* that strips the marker line and the
|
|
62
|
-
*
|
|
63
|
-
*
|
|
67
|
+
* that strips the marker line and, in the lines under it up to a blank line or EOF, only lines some
|
|
68
|
+
* known section holds as a whole line. It refuses to touch a marker sitting inside a ``` code fence.
|
|
69
|
+
* Returns { content, removed }.
|
|
70
|
+
*
|
|
71
|
+
* The choice, where niro cannot tell where an unknown section ends: keep the line. A line no known
|
|
72
|
+
* section holds is never deleted, so a hand-edited Niro line, or one an older release worded
|
|
73
|
+
* differently, may stay behind; that is safer than deleting the user's own line glued below
|
|
74
|
+
* (Niraj, 2026-11-10: "sure"). Before this, the whole block went, the user's glued line with it.
|
|
64
75
|
*/
|
|
65
|
-
function removeMarkerSection(content, marker, knownSections =
|
|
76
|
+
function removeMarkerSection(content, marker, knownSections = NIRO_ALL_MD_SECTIONS) {
|
|
66
77
|
if (typeof content !== "string" || !content.includes(marker)) {
|
|
67
78
|
return { content, removed: false };
|
|
68
79
|
}
|
|
@@ -72,9 +83,11 @@ function removeMarkerSection(content, marker, knownSections = NIRO_MD_SECTIONS)
|
|
|
72
83
|
|
|
73
84
|
// Primary: exact removal of every known section (LF, then a CRLF copy).
|
|
74
85
|
for (const section of knownSections || []) {
|
|
75
|
-
for (const variant of [section, section.replace(/\n/g, "\r\n")]) {
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
for (const [variant, eol] of [[section, "\n"], [section.replace(/\n/g, "\r\n"), "\r\n"]]) {
|
|
87
|
+
let at;
|
|
88
|
+
while ((at = next.indexOf(variant)) !== -1) {
|
|
89
|
+
const after = at + variant.length;
|
|
90
|
+
next = next.slice(0, at) + (touchesTextOnBothSides(next, at, after) ? eol : "") + next.slice(after);
|
|
78
91
|
removed = true;
|
|
79
92
|
}
|
|
80
93
|
}
|
|
@@ -84,7 +97,7 @@ function removeMarkerSection(content, marker, knownSections = NIRO_MD_SECTIONS)
|
|
|
84
97
|
// handled; stop when no marker remains or a pass makes no progress (e.g. an in-fence marker
|
|
85
98
|
// we deliberately leave alone), which also prevents an infinite loop.
|
|
86
99
|
while (next.includes(marker)) {
|
|
87
|
-
const r = removeMarkerSectionBounded(next, marker);
|
|
100
|
+
const r = removeMarkerSectionBounded(next, marker, knownSections);
|
|
88
101
|
if (!r.removed || r.content === next) break;
|
|
89
102
|
next = r.content;
|
|
90
103
|
removed = true;
|
|
@@ -94,13 +107,27 @@ function removeMarkerSection(content, marker, knownSections = NIRO_MD_SECTIONS)
|
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
110
|
+
* True when the text cut out of `content` between `at` and `after` had the user's text right against it on both
|
|
111
|
+
* sides: something other than a line break just before, and something other than a line break just after. The cut
|
|
112
|
+
* text starts and ends with a line break, so the two lines would be joined unless one is put back.
|
|
100
113
|
*/
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
function touchesTextOnBothSides(content, at, after) {
|
|
115
|
+
if (at === 0 || after >= content.length) return false;
|
|
116
|
+
return content[at - 1] !== "\n" && content[after] !== "\n" && content[after] !== "\r";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Bounded fallback for removeMarkerSection: remove the marker line and, in the block under it (up to a blank
|
|
121
|
+
* line or EOF), every line some known section holds as a whole line (the "## Niro MCP" heading and body lines
|
|
122
|
+
* a release wrote). A line no known section holds is kept, so a user's line glued below an unknown section
|
|
123
|
+
* survives. A marker inside a code fence is treated as documentation
|
|
124
|
+
* and left untouched. A marker sharing its line with other text loses only the marker. Each kept line keeps
|
|
125
|
+
* its own line ending.
|
|
126
|
+
*/
|
|
127
|
+
function removeMarkerSectionBounded(content, marker, knownSections = NIRO_ALL_MD_SECTIONS) {
|
|
128
|
+
// Each line keeps its own line ending, so LF, CRLF and a last line with no line break all come back as they were.
|
|
129
|
+
const lines = content.split(/(?<=\n)/);
|
|
130
|
+
const text = (line) => line.replace(/\r?\n$/, "");
|
|
104
131
|
const markerLine = lines.findIndex((l) => l.includes(marker));
|
|
105
132
|
if (markerLine === -1) return { content, removed: false };
|
|
106
133
|
|
|
@@ -111,23 +138,33 @@ function removeMarkerSectionBounded(content, marker) {
|
|
|
111
138
|
}
|
|
112
139
|
if (fences % 2 === 1) return { content, removed: false };
|
|
113
140
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
end++; // contiguous niro body lines — stop at a blank line or the next heading
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
let begin = markerLine;
|
|
123
|
-
if (begin > 0 && lines[begin - 1].trim() === "") begin--; // drop install's separator blank line
|
|
141
|
+
// The marker shares its line with the user's text: that line keeps the user's text and loses only the marker.
|
|
142
|
+
const rest = lines[markerLine].split(marker).join("");
|
|
143
|
+
const markerAlone = text(rest).trim() === "";
|
|
144
|
+
if (!markerAlone) lines[markerLine] = rest;
|
|
124
145
|
|
|
125
|
-
|
|
126
|
-
|
|
146
|
+
const knownLines = new Set();
|
|
147
|
+
for (const section of knownSections || []) {
|
|
148
|
+
for (const line of section.split(/\r?\n/)) if (line.trim() !== "") knownLines.add(line.trimEnd());
|
|
149
|
+
}
|
|
150
|
+
// The block is the lines right under the marker, up to a blank line or EOF. Of those, only a line some release
|
|
151
|
+
// wrote word for word goes (the heading, a body line). A line no release wrote stays where it is: the user's
|
|
152
|
+
// own line glued below, and also a Niro line the user edited or an older release worded differently, because
|
|
153
|
+
// niro cannot tell those apart from the user's. Known lines after it still go, so an edited line leaves only
|
|
154
|
+
// itself behind, not the rest of the old body.
|
|
155
|
+
const drop = new Set();
|
|
156
|
+
if (markerAlone) {
|
|
157
|
+
drop.add(markerLine);
|
|
158
|
+
if (markerLine > 0 && text(lines[markerLine - 1]).trim() === "") drop.add(markerLine - 1); // install's separator blank line
|
|
159
|
+
}
|
|
160
|
+
for (let i = markerLine + 1; i < lines.length && text(lines[i]).trim() !== ""; i++) {
|
|
161
|
+
if (knownLines.has(text(lines[i]).trimEnd())) drop.add(i);
|
|
162
|
+
}
|
|
163
|
+
return { content: lines.filter((_, i) => !drop.has(i)).join(""), removed: true };
|
|
127
164
|
}
|
|
128
165
|
|
|
129
166
|
/**
|
|
130
|
-
* Strip niro's PostCompact, UserPromptSubmit, and PreToolUse (temp-project + explore-first + impact-gate) hook
|
|
167
|
+
* Strip niro's PostCompact, UserPromptSubmit, and PreToolUse (temp-project + explore-first + shell-search + impact-gate) hook
|
|
131
168
|
* entries from a parsed Claude Code settings object (mutates and returns it). Uses the same matchers
|
|
132
169
|
* install uses. Empty arrays / an empty `hooks` object are pruned so the file stays tidy. Other hooks
|
|
133
170
|
* are untouched. Returns { settings, removed } where `removed` is the count deleted.
|
|
@@ -154,14 +191,14 @@ function filterNiroHooks(settings) {
|
|
|
154
191
|
if (Array.isArray(hooks.PreToolUse)) {
|
|
155
192
|
const before = hooks.PreToolUse.length;
|
|
156
193
|
hooks.PreToolUse = hooks.PreToolUse.filter(
|
|
157
|
-
(h) => !isNiroPreToolUseHook(h) && !isNiroExploreHook(h) && !isNiroImpactHook(h),
|
|
194
|
+
(h) => !isNiroPreToolUseHook(h) && !isNiroExploreHook(h) && !isNiroImpactHook(h) && !isNiroShellSearchHook(h),
|
|
158
195
|
);
|
|
159
196
|
removed += before - hooks.PreToolUse.length;
|
|
160
197
|
if (hooks.PreToolUse.length === 0) delete hooks.PreToolUse;
|
|
161
198
|
}
|
|
162
199
|
if (Array.isArray(hooks.Stop)) {
|
|
163
200
|
const before = hooks.Stop.length;
|
|
164
|
-
hooks.Stop = hooks.Stop.filter((h) => !isNiroStopHook(h));
|
|
201
|
+
hooks.Stop = hooks.Stop.filter((h) => !isNiroStopHook(h) && !isNiroLineCheckHook(h));
|
|
165
202
|
removed += before - hooks.Stop.length;
|
|
166
203
|
if (hooks.Stop.length === 0) delete hooks.Stop;
|
|
167
204
|
}
|
|
@@ -377,6 +414,7 @@ function forceRemoveAgentsSkills() {
|
|
|
377
414
|
const home = userHome();
|
|
378
415
|
deleteFileIfPresent(path.join(home, ".agents", "skills", "niro-skill", "SKILL.md"), "shared niro skill");
|
|
379
416
|
deleteFileIfPresent(path.join(home, ".agents", "skills", "niro-cli", "SKILL.md"), "shared niro-cli skill");
|
|
417
|
+
deleteFileIfPresent(path.join(home, ".agents", "skills", "niro-local", "SKILL.md"), "shared niro-local skill");
|
|
380
418
|
}
|
|
381
419
|
|
|
382
420
|
// ─── per-client uninstallers (mirror setup.js's per-client installers) ────────
|
|
@@ -399,6 +437,15 @@ function uninstallClaudeCode() {
|
|
|
399
437
|
stripMarkerFromFile(path.join(home, ".claude", "CLAUDE.md"), "Claude Code memory");
|
|
400
438
|
deleteFileIfPresent(path.join(home, ".claude", "skills", "niro-skill", "SKILL.md"), "niro skill");
|
|
401
439
|
deleteFileIfPresent(path.join(home, ".claude", "skills", "niro-cli", "SKILL.md"), "niro-cli skill");
|
|
440
|
+
deleteFileIfPresent(path.join(home, ".claude", "skills", "niro-local", "SKILL.md"), "niro-local skill");
|
|
441
|
+
// The saved mode (`niro mcp install --local`) goes too, so a later plain install starts from the
|
|
442
|
+
// cloud default rather than silently from local mode (verifier, 2026-11-02).
|
|
443
|
+
try {
|
|
444
|
+
const config = require("../lib/config");
|
|
445
|
+
if (config.load().mcpMode !== undefined) config.save({ mcpMode: undefined });
|
|
446
|
+
} catch {
|
|
447
|
+
/* best effort, as above */
|
|
448
|
+
}
|
|
402
449
|
}
|
|
403
450
|
|
|
404
451
|
function uninstallCodex(opts = {}) {
|
|
@@ -545,38 +592,8 @@ const UNINSTALLERS = {
|
|
|
545
592
|
antigravity: uninstallAntigravity,
|
|
546
593
|
};
|
|
547
594
|
|
|
548
|
-
/**
|
|
549
|
-
* Drop the mcpServerUrl fallback install saved to ~/.niro/config.json. Only invoked for a
|
|
550
|
-
* full (--all) uninstall — it's a global, not per-client, value. apiUrl and any other keys
|
|
551
|
-
* are preserved; credentials.json is never touched here.
|
|
552
|
-
*/
|
|
553
|
-
function removeGlobalMcpServerUrl() {
|
|
554
|
-
let cfg;
|
|
555
|
-
try {
|
|
556
|
-
cfg = require("../lib/config");
|
|
557
|
-
} catch {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
try {
|
|
561
|
-
const current = cfg.load();
|
|
562
|
-
if (!Object.prototype.hasOwnProperty.call(current, "mcpServerUrl")) return;
|
|
563
|
-
delete current.mcpServerUrl;
|
|
564
|
-
fs.writeFileSync(cfg.FILE, JSON.stringify(current, null, 2) + "\n");
|
|
565
|
-
console.log(` Removed mcpServerUrl from ${cfg.FILE}`);
|
|
566
|
-
} catch (e) {
|
|
567
|
-
console.warn(` Could not update ~/.niro/config.json: ${e.message}`);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
595
|
// ─── interactive entry ───────────────────────────────────────────────────────
|
|
572
596
|
|
|
573
|
-
function printHeader() {
|
|
574
|
-
console.log("");
|
|
575
|
-
console.log(" Niro MCP Server Uninstall");
|
|
576
|
-
console.log(" -------------------------");
|
|
577
|
-
console.log("");
|
|
578
|
-
}
|
|
579
|
-
|
|
580
597
|
function printClients() {
|
|
581
598
|
console.log(" Clients:");
|
|
582
599
|
Object.keys(UNINSTALLERS).forEach((id) => {
|
|
@@ -604,18 +621,60 @@ function ask(rl, question, defaultValue) {
|
|
|
604
621
|
});
|
|
605
622
|
}
|
|
606
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Remove Niro from `targets`, one line per assistant. Each uninstaller's own step-by-step lines
|
|
626
|
+
* are kept back and shown only when that assistant fails, so a normal run reads as a checklist.
|
|
627
|
+
*/
|
|
607
628
|
function runUninstall(targets, isFull) {
|
|
608
|
-
|
|
629
|
+
let failed = 0;
|
|
630
|
+
for (const id of targets) {
|
|
631
|
+
const said = [];
|
|
632
|
+
const [log, warn] = [console.log, console.warn];
|
|
633
|
+
console.log = (...a) => said.push(a.join(" "));
|
|
634
|
+
console.warn = (...a) => said.push(a.join(" "));
|
|
635
|
+
try {
|
|
636
|
+
UNINSTALLERS[id]({ full: isFull });
|
|
637
|
+
console.log = log;
|
|
638
|
+
console.warn = warn;
|
|
639
|
+
console.log(`│ ${color.green("✓")} ${nameOf(id)}`);
|
|
640
|
+
} catch (err) {
|
|
641
|
+
console.log = log;
|
|
642
|
+
console.warn = warn;
|
|
643
|
+
failed += 1;
|
|
644
|
+
console.log(`│ ${color.yellow("⚠")} ${nameOf(id)}: ${err.message}`);
|
|
645
|
+
for (const line of said) if (line.trim()) console.log(`│ ${line.trim()}`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
609
648
|
if (isFull) {
|
|
610
|
-
// Every editor is gone
|
|
611
|
-
|
|
612
|
-
|
|
649
|
+
// Every editor is gone: remove the shared cross-agent skills. The saved mcpServerUrl stays: it
|
|
650
|
+
// is also where usage is sent, and removing it sent a dev or staging machine's usage to
|
|
651
|
+
// production (`niro uninstall` deletes ~/.niro, and it, anyway).
|
|
652
|
+
const [log] = [console.log];
|
|
653
|
+
console.log = () => {};
|
|
654
|
+
try {
|
|
655
|
+
forceRemoveAgentsSkills();
|
|
656
|
+
} finally {
|
|
657
|
+
console.log = log;
|
|
658
|
+
}
|
|
613
659
|
}
|
|
660
|
+
console.log("│");
|
|
661
|
+
console.log(`└ ${failed ? "Done, with the problems above." : "Done."} Restart ${targets.length === 1 ? nameOf(targets[0]) : "those assistants"} for it to take effect.`);
|
|
662
|
+
console.log("");
|
|
663
|
+
console.log(` Your sign-in and your graphs are kept. To remove Niro completely: ${color.cyan("niro uninstall")}`);
|
|
664
|
+
console.log("");
|
|
665
|
+
if (failed) process.exitCode = 1;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const nameOf = (id) => (CLIENTS[id] ? CLIENTS[id].name : id);
|
|
614
669
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
670
|
+
/** The assistants Niro is installed in on this machine, by id, each once. */
|
|
671
|
+
function installedIds() {
|
|
672
|
+
try {
|
|
673
|
+
const { configured } = require("../lib/mcpClients").configuredClients();
|
|
674
|
+
return [...new Set(configured.map((c) => c.id))].filter((id) => UNINSTALLERS[id]);
|
|
675
|
+
} catch {
|
|
676
|
+
return [];
|
|
677
|
+
}
|
|
619
678
|
}
|
|
620
679
|
|
|
621
680
|
async function main() {
|
|
@@ -629,52 +688,75 @@ async function main() {
|
|
|
629
688
|
else if (args[i] === "-y" || args[i] === "--yes") assumeYes = true;
|
|
630
689
|
}
|
|
631
690
|
|
|
632
|
-
printHeader();
|
|
633
|
-
|
|
634
691
|
const allIds = Object.keys(UNINSTALLERS);
|
|
635
692
|
let targets;
|
|
693
|
+
let picked = false;
|
|
694
|
+
|
|
695
|
+
// Nothing to do is one plain line, not a box around one sentence.
|
|
696
|
+
const interactive = !all && clientArg !== "all" && !clientArg && process.stdin.isTTY;
|
|
697
|
+
const installed = interactive ? installedIds() : null;
|
|
698
|
+
if (interactive && !installed.length) {
|
|
699
|
+
console.log(`\nNiro is not set up in any AI assistant on this machine, so there is nothing to remove. To set it up: ${color.cyan("niro mcp install")}\n`);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
console.log("");
|
|
704
|
+
console.log("┌ Remove Niro from your AI assistants");
|
|
705
|
+
console.log("│");
|
|
636
706
|
|
|
637
707
|
if (all || clientArg === "all") {
|
|
638
708
|
targets = allIds;
|
|
639
709
|
} else if (clientArg) {
|
|
640
|
-
|
|
641
|
-
|
|
710
|
+
const ids = clientArg.split(",").map((x) => x.trim()).filter(Boolean);
|
|
711
|
+
const unknown = ids.filter((id) => !UNINSTALLERS[id]);
|
|
712
|
+
if (unknown.length) {
|
|
713
|
+
console.error(`│ Unknown assistant: ${unknown.join(", ")}`);
|
|
642
714
|
printClients();
|
|
643
715
|
process.exit(1);
|
|
644
716
|
}
|
|
645
|
-
targets =
|
|
717
|
+
targets = ids;
|
|
646
718
|
} else if (!process.stdin.isTTY) {
|
|
647
719
|
// Non-interactive with no target: refuse rather than guess.
|
|
648
|
-
console.error("
|
|
720
|
+
console.error("│ No assistant named. Pass --client <id> or --all.");
|
|
649
721
|
printClients();
|
|
650
722
|
process.exit(1);
|
|
651
723
|
} else {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
724
|
+
console.log("◆ Which assistants should stop using Niro?");
|
|
725
|
+
targets = await choose({
|
|
726
|
+
prefix: "│ ",
|
|
727
|
+
items: installed.map((id) => ({ value: id, label: nameOf(id) })),
|
|
728
|
+
multi: true,
|
|
729
|
+
// Nothing ticked to start: pre-ticked, "space on each" unticked them all and Enter then
|
|
730
|
+
// took only the one under the cursor (Divya, 2026-11-05).
|
|
731
|
+
initial: [],
|
|
732
|
+
hint: "↑/↓ move · space tick · a all · enter remove",
|
|
733
|
+
});
|
|
734
|
+
picked = true;
|
|
735
|
+
if (!targets.length) {
|
|
736
|
+
console.log("└ Nothing picked. Nothing was changed.");
|
|
737
|
+
console.log("");
|
|
738
|
+
return;
|
|
661
739
|
}
|
|
662
740
|
}
|
|
663
741
|
|
|
664
|
-
|
|
742
|
+
// Everything that was set up is going, so the shared files go too.
|
|
743
|
+
const remaining = installedIds().filter((id) => !targets.includes(id));
|
|
744
|
+
const isFull = targets.length === allIds.length || remaining.length === 0;
|
|
665
745
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
console.log(
|
|
746
|
+
// Picking in the list above is the confirmation; a command line naming assistants asks once.
|
|
747
|
+
if (!picked && !assumeYes && process.stdin.isTTY) {
|
|
748
|
+
console.log(`│ This removes Niro from: ${targets.map(nameOf).join(", ")}`);
|
|
669
749
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
670
|
-
const ans = await ask(rl, "
|
|
750
|
+
const ans = await ask(rl, "│ Go ahead?", "yes");
|
|
671
751
|
rl.close();
|
|
672
752
|
if (!/^y/i.test(ans)) {
|
|
673
|
-
console.log("
|
|
753
|
+
console.log("└ Nothing was changed.");
|
|
754
|
+
console.log("");
|
|
674
755
|
return;
|
|
675
756
|
}
|
|
676
757
|
}
|
|
677
758
|
|
|
759
|
+
console.log("│");
|
|
678
760
|
runUninstall(targets, isFull);
|
|
679
761
|
}
|
|
680
762
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* "Using Niro": what a coding agent should do with Niro beyond code questions. Written into every instruction text a
|
|
5
|
+
* release without the cloud installs (the niro-skill, the CLAUDE.md section, the marked sections for Codex, Gemini
|
|
6
|
+
* CLI, Copilot CLI, Factory, OpenCode and Windsurf, and the Cursor, Cline and Kiro rules), and said by the released
|
|
7
|
+
* engine in its MCP instructions, which every one of the 14 assistants reads (niro-local
|
|
8
|
+
* `src/niro_local/mcp/using_niro.py`).
|
|
9
|
+
*
|
|
10
|
+
* Why (Niraj, 2026-11-11): 92 pages ship with niro (`niro guide`), and an agent asked a question about Niro never
|
|
11
|
+
* opened one, because no text it read named them; "this has to happen automatically part of the installation".
|
|
12
|
+
* How an agent finds a page (Niraj, 2026-11-11, "agreed"): it reads the list with its one-line descriptions and
|
|
13
|
+
* picks by meaning, because agents match meaning themselves, niro-cli carries no model, and the pages work offline.
|
|
14
|
+
* `--search` is named only for an exact message or word. Decision 01M75G5K5NYJDT1EWM3K2V3PTT.
|
|
15
|
+
*
|
|
16
|
+
* ONE text in each repository, equal word for word: test/usingNiro.test.js reads niro-local's copy and niro-local's
|
|
17
|
+
* tests/test_using_niro.py reads this one. Kept to a template literal with no `${}` so a reader outside JavaScript
|
|
18
|
+
* can take it out of this file. No blank line inside it: a marked section ends at its first blank line when an
|
|
19
|
+
* upgrade or an uninstall has to cut out a text it no longer knows word for word (teardown.js).
|
|
20
|
+
* Every command it names is checked against the CLI by that test.
|
|
21
|
+
*/
|
|
22
|
+
const USING_NIRO = `Using Niro:
|
|
23
|
+
- Code questions: use the niro tools, as above.
|
|
24
|
+
- A "how do I" question about Niro: run \`niro guide\`, which lists every page with one line on what it answers. Pick the page whose line fits the question, run \`niro guide <page>\`, and answer from it. The pages ship with niro and work offline. For work on code: playbook-debug-exception, playbook-change-safely, playbook-review-a-change, playbook-test-a-change.
|
|
25
|
+
- A Niro error message, or an exact word such as an error code: \`niro guide --search "<the message>"\` names the page that explains it.
|
|
26
|
+
- Code in a language, framework or tool Niro does not read: offer to ask Niro's team for it with the request_integration tool, which sends nothing until the user says yes. In a terminal: \`niro request-integration <name>\`.
|
|
27
|
+
- Managing Niro, in the terminal:
|
|
28
|
+
- index another folder: \`niro init <folder>\`, only when the user asks or says yes
|
|
29
|
+
- rebuild a folder's graph: \`niro build <folder>\`
|
|
30
|
+
- what is indexed here: \`niro status\`
|
|
31
|
+
- the plan and its line limit: \`niro plan\`
|
|
32
|
+
- what Niro did for the user: \`niro impact\`
|
|
33
|
+
- connect calls between services: \`niro route-rule unconnected\`, after reading \`niro guide configuring-a-folder\`
|
|
34
|
+
- anything else: \`niro guide\``;
|
|
35
|
+
|
|
36
|
+
/** The commands USING_NIRO names, each a real `niro` command (checked by test/usingNiro.test.js against src/index.js). */
|
|
37
|
+
const USING_NIRO_COMMANDS = ["guide", "request-integration", "init", "build", "status", "plan", "impact", "route-rule"];
|
|
38
|
+
|
|
39
|
+
module.exports = { USING_NIRO, USING_NIRO_COMMANDS };
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# Working on branches with Niro
|
|
2
|
-
|
|
3
|
-
Your team decides what Niro indexes: you create projects, and each project
|
|
4
|
-
covers the repos and branches you choose. One project might index main,
|
|
5
|
-
another might index a develop or release branch. That is up to you.
|
|
6
|
-
|
|
7
|
-
When you ask a question, Niro picks the project that matches your repo and
|
|
8
|
-
the branch you are currently on. This page is about the moment when NO
|
|
9
|
-
project matches: you created a new branch and changed code that no project
|
|
10
|
-
has indexed yet. Two minutes to read.
|
|
11
|
-
|
|
12
|
-
## The short version
|
|
13
|
-
|
|
14
|
-
There is one command to remember:
|
|
15
|
-
|
|
16
|
-
niro new-temp-project
|
|
17
|
-
|
|
18
|
-
Run it in your repo folder when Niro asks for it. That is all.
|
|
19
|
-
|
|
20
|
-
## What you will see
|
|
21
|
-
|
|
22
|
-
You create a branch that no project covers and start changing code.
|
|
23
|
-
Then you ask your AI assistant something about the code.
|
|
24
|
-
|
|
25
|
-
Niro notices that no project has indexed this branch. Instead of giving
|
|
26
|
-
you a wrong answer based on some other branch, it replies with something
|
|
27
|
-
like:
|
|
28
|
-
|
|
29
|
-
I cannot answer this safely. Your branch has diverged
|
|
30
|
-
from the branch Niro indexed.
|
|
31
|
-
|
|
32
|
-
Your assistant will offer to fix this by running:
|
|
33
|
-
|
|
34
|
-
niro new-temp-project
|
|
35
|
-
|
|
36
|
-
Say yes, or run it yourself in the repo folder. It takes a few seconds.
|
|
37
|
-
|
|
38
|
-
## What the command does
|
|
39
|
-
|
|
40
|
-
It creates your own private copy of the project inside Niro.
|
|
41
|
-
|
|
42
|
-
- The copy follows your branch, including changes you have not committed.
|
|
43
|
-
- Every save updates the copy, as long as "niro watch" is running.
|
|
44
|
-
- Your original project is not changed. It keeps its own index.
|
|
45
|
-
- The copy is fully separate and disposable.
|
|
46
|
-
|
|
47
|
-
From then on, answers come from YOUR code.
|
|
48
|
-
|
|
49
|
-
The commands follow too. `niro status` and `niro build` act on the copy for the branch you are on,
|
|
50
|
-
not on the shared project, so "is my index up to date" and "rebuild it" mean what you expect while
|
|
51
|
-
you are working on a branch. Name a project explicitly if you want a different one.
|
|
52
|
-
|
|
53
|
-
`niro delete` and `niro remove --project` will not delete a private copy. They send you to
|
|
54
|
-
`niro discard-temp-project`, which removes the copy AND points your folder back at the shared
|
|
55
|
-
project. Deleting it any other way leaves your folder aimed at something that is gone.
|
|
56
|
-
|
|
57
|
-
## Switching branches
|
|
58
|
-
|
|
59
|
-
Nothing to do. Niro checks which branch you are on for every question.
|
|
60
|
-
|
|
61
|
-
- On your new branch: answers come from your private copy.
|
|
62
|
-
- On a branch one of your projects already indexes: answers come
|
|
63
|
-
from that project.
|
|
64
|
-
|
|
65
|
-
## When your work is merged
|
|
66
|
-
|
|
67
|
-
Run this in the repo folder:
|
|
68
|
-
|
|
69
|
-
niro discard-temp-project
|
|
70
|
-
|
|
71
|
-
It deletes your private copy. Your local files are never touched.
|
|
72
|
-
If you forget, Niro deletes unused copies by itself after two weeks.
|
|
73
|
-
|
|
74
|
-
## Tidying up copies that were deleted for you
|
|
75
|
-
|
|
76
|
-
When a copy goes after two weeks, or somebody deletes it in the web app,
|
|
77
|
-
your machine is not told. The folder is left pointing at a copy that is
|
|
78
|
-
no longer there. Nothing breaks, but "niro new-temp-project" in another
|
|
79
|
-
worktree of the same repo will say the slot is already taken.
|
|
80
|
-
|
|
81
|
-
Run this from anywhere to sort it out:
|
|
82
|
-
|
|
83
|
-
niro doctor sync-registry
|
|
84
|
-
|
|
85
|
-
You will rarely need to type it. The background watcher runs the same
|
|
86
|
-
check every six hours by itself.
|
|
87
|
-
|
|
88
|
-
It sorts out three things, and treats them differently:
|
|
89
|
-
|
|
90
|
-
- A folder pointing at a private copy that has gone goes back to the
|
|
91
|
-
original project, and carries on syncing.
|
|
92
|
-
- A folder pointing at a project that has gone is only unregistered when
|
|
93
|
-
the repo has gone too. A project can be deleted while its repos are
|
|
94
|
-
moved somewhere else, and that folder is still syncing perfectly well.
|
|
95
|
-
- A ".niro-project" file naming something the server does not know is
|
|
96
|
-
repaired where possible. That file can hold a project's NAME rather
|
|
97
|
-
than its id, so a renamed project looks exactly like a deleted one.
|
|
98
|
-
Niro checks your folder's git remote instead: if that points at one
|
|
99
|
-
project, the file is rewritten to that project's id, which a future
|
|
100
|
-
rename cannot break. If the remote points at nothing either, the file
|
|
101
|
-
names nothing and is removed.
|
|
102
|
-
|
|
103
|
-
Not if the file is committed to git, though. Then it is shared with
|
|
104
|
-
everyone who clones the repo, and a project that means nothing to you
|
|
105
|
-
may be a colleague's. Niro tells you and leaves the choice to you.
|
|
106
|
-
"niro info" mentions it too, when you are standing in that folder.
|
|
107
|
-
|
|
108
|
-
Copies that are still there are left alone, and so is anything it could
|
|
109
|
-
not check, so a dropped connection never costs you a working folder. Add
|
|
110
|
-
"--dry-run" to see the list without changing anything.
|
|
111
|
-
|
|
112
|
-
## When you run out of private copies
|
|
113
|
-
|
|
114
|
-
You get a limited number of private copies of each project. When you hit
|
|
115
|
-
the limit, Niro says which branches are holding them, and the CLI adds
|
|
116
|
-
where each one is checked out on this machine:
|
|
117
|
-
|
|
118
|
-
You already have 5 working copies of this project, which is
|
|
119
|
-
the limit (5). They are for: feature/a, feature/b, ...
|
|
120
|
-
|
|
121
|
-
You have 5 working copies of this project on this machine:
|
|
122
|
-
feature/a /home/you/work/service
|
|
123
|
-
feature/b /home/you/work/service-wt2
|
|
124
|
-
|
|
125
|
-
Run "niro discard-temp-project" in one you have finished with, then try
|
|
126
|
-
again. Copies nobody has touched for a while are cleared automatically,
|
|
127
|
-
so this usually sorts itself out.
|
|
128
|
-
|
|
129
|
-
## A feature that spans several repos
|
|
130
|
-
|
|
131
|
-
Run "niro new-temp-project" in each repo folder you branch.
|
|
132
|
-
All of them join the same private copy, so questions that cross repos
|
|
133
|
-
(for example: what breaks in service B if I change service A?) see all
|
|
134
|
-
of your changes together.
|
|
135
|
-
|
|
136
|
-
## Common questions
|
|
137
|
-
|
|
138
|
-
Does this change my original project?
|
|
139
|
-
No. The original project and its index stay exactly as they were.
|
|
140
|
-
The copy is separate; deleting it later loses nothing.
|
|
141
|
-
|
|
142
|
-
Does this work with my AI assistant and my git host?
|
|
143
|
-
Yes. Any assistant that supports MCP (Claude Code, Cursor, Windsurf,
|
|
144
|
-
Codex, and others) and any git server (GitHub, GitLab, Bitbucket,
|
|
145
|
-
self-hosted).
|
|
146
|
-
|
|
147
|
-
Is creating a copy slow?
|
|
148
|
-
No. Niro copies the existing index instead of re-reading your code.
|
|
149
|
-
It normally takes seconds.
|
|
150
|
-
|
|
151
|
-
I just created a branch and have not changed anything. Will Niro nag me?
|
|
152
|
-
No. Your code is still identical to the branch you started from, so Niro
|
|
153
|
-
answers normally. It only steps in once your code actually differs.
|
|
154
|
-
|
|
155
|
-
We already have projects for main and develop. Do I need this?
|
|
156
|
-
Not while you are on main or develop: Niro routes to the matching project
|
|
157
|
-
by itself. You only need a temporary project for a branch that none of
|
|
158
|
-
your projects cover.
|
|
159
|
-
|
|
160
|
-
What if I really want an answer from the shared project while on my branch?
|
|
161
|
-
Tell your assistant so. The answer will be clearly labeled as describing
|
|
162
|
-
the indexed branch, not your code.
|