@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/mcp/setup.js
CHANGED
|
@@ -163,16 +163,16 @@ function writeManagedSkill(absPath, content, label) {
|
|
|
163
163
|
if (!fs.existsSync(absPath)) {
|
|
164
164
|
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
165
165
|
fs.writeFileSync(absPath, content);
|
|
166
|
-
console.log(`
|
|
166
|
+
console.log(` ${label} added: ${absPath}`);
|
|
167
167
|
return "added";
|
|
168
168
|
}
|
|
169
169
|
const existing = fs.readFileSync(absPath, "utf-8");
|
|
170
170
|
if (existing === content) {
|
|
171
|
-
console.log(`
|
|
171
|
+
console.log(` ${label} already up to date. Skipping (${absPath}).`);
|
|
172
172
|
return "unchanged";
|
|
173
173
|
}
|
|
174
174
|
fs.writeFileSync(absPath, content);
|
|
175
|
-
console.log(`
|
|
175
|
+
console.log(` ${label} updated: ${absPath}`);
|
|
176
176
|
return "updated";
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -355,7 +355,7 @@ function ensureCursorPermissionsForNiro() {
|
|
|
355
355
|
if (!data || typeof data !== "object") data = {};
|
|
356
356
|
}
|
|
357
357
|
} catch (e) {
|
|
358
|
-
console.warn(`
|
|
358
|
+
console.warn(` Could not read ${permPath}: ${e.message}`);
|
|
359
359
|
console.warn(' Fix the file manually or merge: { "mcpAllowlist": ["niro:*"] }\n');
|
|
360
360
|
return;
|
|
361
361
|
}
|
|
@@ -366,21 +366,21 @@ function ensureCursorPermissionsForNiro() {
|
|
|
366
366
|
data = { ...data, mcpAllowlist: ["niro:*"] };
|
|
367
367
|
} else if (hasKey && Array.isArray(data.mcpAllowlist)) {
|
|
368
368
|
if (allowlistAlreadyCoversAllNiroTools(data.mcpAllowlist)) {
|
|
369
|
-
console.log("
|
|
369
|
+
console.log(" Cursor MCP allowlist already includes Niro (`niro:*` or broader).\n");
|
|
370
370
|
return;
|
|
371
371
|
}
|
|
372
372
|
data.mcpAllowlist = [...data.mcpAllowlist, "niro:*"];
|
|
373
373
|
} else if (hasKey && !Array.isArray(data.mcpAllowlist)) {
|
|
374
|
-
console.warn("
|
|
374
|
+
console.warn(" ~/.cursor/permissions.json has invalid mcpAllowlist, so it is not being modified.");
|
|
375
375
|
console.warn(' Add this string after fixing JSON: \"niro:*\"\n');
|
|
376
376
|
return;
|
|
377
377
|
} else if (Object.keys(data).length > 0) {
|
|
378
378
|
console.log("");
|
|
379
|
-
console.log("
|
|
379
|
+
console.log(" ~/.cursor/permissions.json exists but has no \"mcpAllowlist\" entry.");
|
|
380
380
|
console.log(
|
|
381
|
-
' Cursor replaces the Settings MCP allowlist when you add \"mcpAllowlist\"
|
|
381
|
+
' Cursor replaces the Settings MCP allowlist when you add \"mcpAllowlist\", so the auto-merge was skipped to avoid dropping other MCP tools.',
|
|
382
382
|
);
|
|
383
|
-
console.log("
|
|
383
|
+
console.log(" Merge manually (include other MCP servers if you need them), e.g.:");
|
|
384
384
|
console.log(' \"mcpAllowlist\": [\"niro:*\", \"github:*\"]\n');
|
|
385
385
|
return;
|
|
386
386
|
} else {
|
|
@@ -388,19 +388,19 @@ function ensureCursorPermissionsForNiro() {
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
if (!data.mcpAllowlist || !allowlistAlreadyCoversAllNiroTools(data.mcpAllowlist)) {
|
|
391
|
-
console.warn("
|
|
391
|
+
console.warn(" [niro-setup] Unexpected state writing permissions.json; skipping.\n");
|
|
392
392
|
return;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
try {
|
|
396
396
|
fs.mkdirSync(path.dirname(permPath), { recursive: true });
|
|
397
397
|
fs.writeFileSync(permPath, JSON.stringify(data, null, 2) + "\n");
|
|
398
|
-
console.log(`
|
|
398
|
+
console.log(` Cursor MCP permissions updated: ${permPath}`);
|
|
399
399
|
console.log(' • Added pattern \"niro:*\" so Agent can run all Niro tools without approval prompts.');
|
|
400
|
-
console.log("
|
|
401
|
-
console.log("
|
|
400
|
+
console.log(" • Enable Cursor Agent Auto-run / sandbox auto-run: \"Ask every time\" ignores allowlists.");
|
|
401
|
+
console.log(" • Reference: https://cursor.com/docs/reference/permissions\n");
|
|
402
402
|
} catch (e) {
|
|
403
|
-
console.warn(`
|
|
403
|
+
console.warn(` Could not write ${permPath}: ${e.message}\n`);
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
406
|
|
|
@@ -534,21 +534,52 @@ function hasShellMetacharacters(value) {
|
|
|
534
534
|
|
|
535
535
|
function printHeader() {
|
|
536
536
|
console.log("");
|
|
537
|
-
console.log("
|
|
538
|
-
console.log("
|
|
537
|
+
console.log(" Niro MCP Server Setup");
|
|
538
|
+
console.log(" ---------------------");
|
|
539
539
|
console.log("");
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
542
|
+
/** How many clients to name before summarising the rest. */
|
|
543
|
+
const CLIENTS_SHOWN = 5;
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Run a client's own CLI and print its output at our indent level.
|
|
547
|
+
*
|
|
548
|
+
* These tools print at column 0, so inheriting their stdout put unindented lines in the
|
|
549
|
+
* middle of an indented block and destroyed the only cue for what belongs to what. On
|
|
550
|
+
* failure the output is printed as-is: a broken command is not the moment to be tidy.
|
|
551
|
+
*/
|
|
552
|
+
function runClientCommand(command) {
|
|
553
|
+
const out = execSync(command, { stdio: ["inherit", "pipe", "pipe"], encoding: "utf8" });
|
|
554
|
+
String(out || "")
|
|
555
|
+
.split("\n")
|
|
556
|
+
.filter((l) => l.trim())
|
|
557
|
+
.forEach((l) => console.log(` ${l.trim()}`));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* The first few by name, then a count.
|
|
562
|
+
*
|
|
563
|
+
* All fourteen used to print, with each one's config-file path in brackets: sixteen lines for
|
|
564
|
+
* a question nearly everyone answers with the default. The full list is one flag away, and
|
|
565
|
+
* anyone hunting for a client they cannot see is exactly the person who will use it.
|
|
566
|
+
*/
|
|
567
|
+
function printClients({ all = false } = {}) {
|
|
568
|
+
const entries = Object.entries(CLIENTS);
|
|
569
|
+
const shown = all ? entries : entries.slice(0, CLIENTS_SHOWN);
|
|
570
|
+
console.log(all ? " All supported clients:" : " Clients:");
|
|
571
|
+
shown.forEach(([id, client]) => {
|
|
572
|
+
console.log(` ${id.padEnd(18)} ${client.name}${all ? ` (${client.description})` : ""}`);
|
|
546
573
|
});
|
|
574
|
+
const rest = entries.length - shown.length;
|
|
575
|
+
if (rest > 0) {
|
|
576
|
+
console.log(` ...and ${rest} more. See them all with: niro mcp install --list`);
|
|
577
|
+
}
|
|
547
578
|
console.log("");
|
|
548
579
|
}
|
|
549
580
|
|
|
550
581
|
async function setupClaudeCode(apiKey, mcpServerUrl) {
|
|
551
|
-
console.log("\n
|
|
582
|
+
console.log("\n Configuring Claude Code...\n");
|
|
552
583
|
|
|
553
584
|
const cmd = [
|
|
554
585
|
"claude", "mcp", "add",
|
|
@@ -563,12 +594,12 @@ async function setupClaudeCode(apiKey, mcpServerUrl) {
|
|
|
563
594
|
cmd.push("--", "niro", "mcp", "serve");
|
|
564
595
|
|
|
565
596
|
try {
|
|
566
|
-
|
|
567
|
-
console.log("
|
|
597
|
+
runClientCommand(cmd.join(" "));
|
|
598
|
+
console.log(" MCP server registered globally in Claude Code.\n");
|
|
568
599
|
} catch (err) {
|
|
569
|
-
console.error("
|
|
570
|
-
console.error("
|
|
571
|
-
console.error(`
|
|
600
|
+
console.error(" Failed to register MCP server. Is Claude Code installed?");
|
|
601
|
+
console.error(" Install: https://claude.ai/code\n");
|
|
602
|
+
console.error(` Manual command:\n ${cmd.join(" ")}\n`);
|
|
572
603
|
return false;
|
|
573
604
|
}
|
|
574
605
|
|
|
@@ -608,7 +639,7 @@ function addClaudeCodePostCompactHook() {
|
|
|
608
639
|
const alreadyPresent = settings.hooks.PostCompact.some(isNiroPostCompactHook);
|
|
609
640
|
|
|
610
641
|
if (alreadyPresent) {
|
|
611
|
-
console.log("
|
|
642
|
+
console.log(" PostCompact hook already configured. Skipping.\n");
|
|
612
643
|
return;
|
|
613
644
|
}
|
|
614
645
|
|
|
@@ -621,11 +652,10 @@ function addClaudeCodePostCompactHook() {
|
|
|
621
652
|
try {
|
|
622
653
|
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
623
654
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
624
|
-
console.log("
|
|
625
|
-
console.log(" → Claude will be reminded about Niro tools after context compaction.\n");
|
|
655
|
+
console.log(" PostCompact hook added to ~/.claude/settings.json\n");
|
|
626
656
|
} catch (err) {
|
|
627
|
-
console.warn(`
|
|
628
|
-
console.warn("
|
|
657
|
+
console.warn(` Could not write ~/.claude/settings.json: ${err.message}`);
|
|
658
|
+
console.warn(" Add manually to the PostCompact array in ~/.claude/settings.json:\n");
|
|
629
659
|
console.warn(JSON.stringify(exampleEntry, null, 2) + "\n");
|
|
630
660
|
}
|
|
631
661
|
}
|
|
@@ -649,18 +679,17 @@ function addClaudeCodeMemory() {
|
|
|
649
679
|
|
|
650
680
|
const { content, action } = upsertNiroMarkerSection(existing, section);
|
|
651
681
|
if (action === "unchanged") {
|
|
652
|
-
console.log("
|
|
682
|
+
console.log(" CLAUDE.md entry already up to date. Skipping.\n");
|
|
653
683
|
return;
|
|
654
684
|
}
|
|
655
685
|
|
|
656
686
|
try {
|
|
657
687
|
fs.mkdirSync(path.dirname(claudeMdPath), { recursive: true });
|
|
658
688
|
fs.writeFileSync(claudeMdPath, content);
|
|
659
|
-
console.log(`
|
|
660
|
-
console.log(" → Claude will see Niro tool guidance in every session.\n");
|
|
689
|
+
console.log(` Niro reminder ${action === "refreshed" ? "refreshed in" : "added to"} ~/.claude/CLAUDE.md\n`);
|
|
661
690
|
} catch (err) {
|
|
662
|
-
console.warn(`
|
|
663
|
-
console.warn("
|
|
691
|
+
console.warn(` Could not write ~/.claude/CLAUDE.md: ${err.message}`);
|
|
692
|
+
console.warn(" Add manually to ~/.claude/CLAUDE.md:\n");
|
|
664
693
|
console.warn(section + "\n");
|
|
665
694
|
}
|
|
666
695
|
}
|
|
@@ -718,7 +747,7 @@ function addClaudeCodeUserPromptHook() {
|
|
|
718
747
|
(h) => Array.isArray(h.hooks) && h.hooks.some((i) => i && i.command === hookCommand),
|
|
719
748
|
);
|
|
720
749
|
if (alreadyCurrent) {
|
|
721
|
-
console.log("
|
|
750
|
+
console.log(" UserPromptSubmit hook already up to date. Skipping.\n");
|
|
722
751
|
return;
|
|
723
752
|
}
|
|
724
753
|
|
|
@@ -732,11 +761,9 @@ function addClaudeCodeUserPromptHook() {
|
|
|
732
761
|
try {
|
|
733
762
|
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
734
763
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
735
|
-
console.log(`
|
|
736
|
-
console.log(" → Claude is reminded about Niro before each prompt, but only in repos");
|
|
737
|
-
console.log(" that have a .niro-project marker (no nagging in un-indexed folders).\n");
|
|
764
|
+
console.log(` UserPromptSubmit hook ${hadStale ? "upgraded" : "added"} in ~/.claude/settings.json`);
|
|
738
765
|
} catch (err) {
|
|
739
|
-
console.warn(`
|
|
766
|
+
console.warn(` Could not write ~/.claude/settings.json: ${err.message}`);
|
|
740
767
|
console.warn(' Add manually to the UserPromptSubmit array in ~/.claude/settings.json:\n');
|
|
741
768
|
console.warn(JSON.stringify({ hooks: [{ type: "command", command: hookCommand }] }, null, 2) + "\n");
|
|
742
769
|
}
|
|
@@ -815,10 +842,10 @@ function removeStaleNiroStopHook() {
|
|
|
815
842
|
|
|
816
843
|
try {
|
|
817
844
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
818
|
-
console.log("
|
|
819
|
-
console.log("
|
|
845
|
+
console.log(" Removed the old Stop hook. The end-of-task line now comes from Niro directly,");
|
|
846
|
+
console.log(" without the \"Stop says:\" prefix Claude Code adds to hook output.\n");
|
|
820
847
|
} catch (err) {
|
|
821
|
-
console.warn(`
|
|
848
|
+
console.warn(` Could not update ~/.claude/settings.json: ${err.message}`);
|
|
822
849
|
}
|
|
823
850
|
}
|
|
824
851
|
|
|
@@ -844,7 +871,7 @@ function addClaudeCodePreToolUseHook() {
|
|
|
844
871
|
(h) => Array.isArray(h.hooks) && h.hooks.some((i) => i && i.command === NIRO_PRETOOL_COMMAND),
|
|
845
872
|
);
|
|
846
873
|
if (alreadyCurrent) {
|
|
847
|
-
console.log("
|
|
874
|
+
console.log(" PreToolUse temporary project hook already up to date. Skipping.\n");
|
|
848
875
|
return;
|
|
849
876
|
}
|
|
850
877
|
|
|
@@ -855,12 +882,10 @@ function addClaudeCodePreToolUseHook() {
|
|
|
855
882
|
try {
|
|
856
883
|
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
857
884
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
858
|
-
console.log(`
|
|
859
|
-
console.log(" → Before editing a Niro-indexed repo, Claude is reminded once/session to run the");
|
|
860
|
-
console.log(" should_create_temp_project check (non-blocking; silent in un-indexed repos).\n");
|
|
885
|
+
console.log(` PreToolUse temporary project hook ${hadStale ? "upgraded" : "added"} in ~/.claude/settings.json`);
|
|
861
886
|
} catch (err) {
|
|
862
|
-
console.warn(`
|
|
863
|
-
console.warn("
|
|
887
|
+
console.warn(` Could not write ~/.claude/settings.json: ${err.message}`);
|
|
888
|
+
console.warn(" Add manually to the PreToolUse array in ~/.claude/settings.json:\n");
|
|
864
889
|
console.warn(JSON.stringify(entry, null, 2) + "\n");
|
|
865
890
|
}
|
|
866
891
|
}
|
|
@@ -945,7 +970,7 @@ function addClaudeCodeExploreHook() {
|
|
|
945
970
|
(h) => Array.isArray(h.hooks) && h.hooks.some((i) => i && i.command === NIRO_EXPLORE_COMMAND),
|
|
946
971
|
);
|
|
947
972
|
if (alreadyCurrent) {
|
|
948
|
-
console.log("
|
|
973
|
+
console.log(" PreToolUse explore-first hook already up to date. Skipping.\n");
|
|
949
974
|
return;
|
|
950
975
|
}
|
|
951
976
|
|
|
@@ -956,12 +981,10 @@ function addClaudeCodeExploreHook() {
|
|
|
956
981
|
try {
|
|
957
982
|
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
958
983
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
959
|
-
console.log(`
|
|
960
|
-
console.log(" → On every native Grep/Glob/Read in a niro-indexed repo, Claude is reminded to prefer");
|
|
961
|
-
console.log(" niro tools — even mid-task and late in a long session, not just for discovery questions.\n");
|
|
984
|
+
console.log(` PreToolUse explore-first hook ${hadStale ? "upgraded" : "added"} in ~/.claude/settings.json`);
|
|
962
985
|
} catch (err) {
|
|
963
|
-
console.warn(`
|
|
964
|
-
console.warn("
|
|
986
|
+
console.warn(` Could not write ~/.claude/settings.json: ${err.message}`);
|
|
987
|
+
console.warn(" Add manually to the PreToolUse array in ~/.claude/settings.json:\n");
|
|
965
988
|
console.warn(JSON.stringify(entry, null, 2) + "\n");
|
|
966
989
|
}
|
|
967
990
|
}
|
|
@@ -1348,11 +1371,10 @@ function addClaudeCodeSkill() {
|
|
|
1348
1371
|
try {
|
|
1349
1372
|
const result = writeManagedSkill(skillPath, NIRO_SKILL, "niro skill");
|
|
1350
1373
|
if (result === "added" || result === "updated") {
|
|
1351
|
-
console.log(" → Claude loads the full Niro tool-routing playbook on demand.");
|
|
1352
1374
|
}
|
|
1353
1375
|
console.log("");
|
|
1354
1376
|
} catch (err) {
|
|
1355
|
-
console.warn(`
|
|
1377
|
+
console.warn(` Could not write ~/.claude/skills/niro-skill/SKILL.md: ${err.message}\n`);
|
|
1356
1378
|
}
|
|
1357
1379
|
}
|
|
1358
1380
|
|
|
@@ -1366,11 +1388,10 @@ function addClaudeCodeCliSkill() {
|
|
|
1366
1388
|
try {
|
|
1367
1389
|
const result = writeManagedSkill(skillPath, NIRO_CLI_SKILL, "niro-cli skill");
|
|
1368
1390
|
if (result === "added" || result === "updated") {
|
|
1369
|
-
console.log(" → Claude can drive the niro CLI from chat (status / onboard / edit / rebuild / remove).");
|
|
1370
1391
|
}
|
|
1371
1392
|
console.log("");
|
|
1372
1393
|
} catch (err) {
|
|
1373
|
-
console.warn(`
|
|
1394
|
+
console.warn(` Could not write ~/.claude/skills/niro-cli/SKILL.md: ${err.message}\n`);
|
|
1374
1395
|
}
|
|
1375
1396
|
}
|
|
1376
1397
|
|
|
@@ -1432,16 +1453,16 @@ via MCP tools.
|
|
|
1432
1453
|
function ensureCursorRule() {
|
|
1433
1454
|
const rulePath = path.join(process.cwd(), ".cursor", "rules", "niro.mdc");
|
|
1434
1455
|
if (fs.existsSync(rulePath)) {
|
|
1435
|
-
console.log("
|
|
1456
|
+
console.log(" Cursor rule already present. Skipping (.cursor/rules/niro.mdc).\n");
|
|
1436
1457
|
return;
|
|
1437
1458
|
}
|
|
1438
1459
|
try {
|
|
1439
1460
|
fs.mkdirSync(path.dirname(rulePath), { recursive: true });
|
|
1440
1461
|
fs.writeFileSync(rulePath, NIRO_CURSOR_RULE);
|
|
1441
|
-
console.log(`
|
|
1442
|
-
console.log("
|
|
1462
|
+
console.log(` Cursor rule added: ${rulePath}`);
|
|
1463
|
+
console.log(" • Project-scoped. For other repos, copy it or re-run setup there.\n");
|
|
1443
1464
|
} catch (err) {
|
|
1444
|
-
console.warn(`
|
|
1465
|
+
console.warn(` Could not write .cursor/rules/niro.mdc: ${err.message}\n`);
|
|
1445
1466
|
}
|
|
1446
1467
|
}
|
|
1447
1468
|
|
|
@@ -1450,7 +1471,7 @@ function ensureCursorRule() {
|
|
|
1450
1471
|
* the [mcp_servers.niro] table into ~/.codex/config.toml. Mirrors setupClaudeCode.
|
|
1451
1472
|
*/
|
|
1452
1473
|
async function setupCodex(apiKey, mcpServerUrl) {
|
|
1453
|
-
console.log("\n
|
|
1474
|
+
console.log("\n Configuring Codex...\n");
|
|
1454
1475
|
|
|
1455
1476
|
const cmd = [
|
|
1456
1477
|
"codex", "mcp", "add",
|
|
@@ -1463,11 +1484,11 @@ async function setupCodex(apiKey, mcpServerUrl) {
|
|
|
1463
1484
|
|
|
1464
1485
|
try {
|
|
1465
1486
|
execSync(cmd.join(" "), { stdio: "inherit" });
|
|
1466
|
-
console.log("
|
|
1487
|
+
console.log(" MCP server registered in Codex (~/.codex/config.toml).\n");
|
|
1467
1488
|
} catch (err) {
|
|
1468
|
-
console.error("
|
|
1469
|
-
console.error("
|
|
1470
|
-
console.error(`
|
|
1489
|
+
console.error(" Failed to register MCP server. Is the Codex CLI installed?");
|
|
1490
|
+
console.error(" Install: https://developers.openai.com/codex\n");
|
|
1491
|
+
console.error(` Manual command:\n ${cmd.join(" ")}\n`);
|
|
1471
1492
|
return false;
|
|
1472
1493
|
}
|
|
1473
1494
|
|
|
@@ -1574,19 +1595,19 @@ function clearCodexPerToolApprovals(cfgPath) {
|
|
|
1574
1595
|
// Refuse to write anything that lost the niro server itself. Cheap, and the failure it guards
|
|
1575
1596
|
// against is the one that matters.
|
|
1576
1597
|
if (!rewritten.includes("[mcp_servers.niro]")) {
|
|
1577
|
-
console.warn("
|
|
1598
|
+
console.warn(" Skipped clearing Codex tool approvals: the edit would have dropped [mcp_servers.niro].\n");
|
|
1578
1599
|
return 0;
|
|
1579
1600
|
}
|
|
1580
1601
|
|
|
1581
1602
|
try {
|
|
1582
1603
|
writeCodexConfigVerified(cfgPath, rewritten);
|
|
1583
1604
|
} catch (err) {
|
|
1584
|
-
console.warn(`
|
|
1605
|
+
console.warn(` Could not clear Codex per-tool approvals: ${err.message}\n`);
|
|
1585
1606
|
return 0;
|
|
1586
1607
|
}
|
|
1587
1608
|
|
|
1588
|
-
console.log(`
|
|
1589
|
-
console.log("
|
|
1609
|
+
console.log(` Cleared ${matches.length} per-tool approval override${matches.length === 1 ? "" : "s"} for niro.`);
|
|
1610
|
+
console.log(" → those beat default_tools_approval_mode and made every call prompt.\n");
|
|
1590
1611
|
return matches.length;
|
|
1591
1612
|
}
|
|
1592
1613
|
|
|
@@ -1612,12 +1633,12 @@ function codexConfigPath() {
|
|
|
1612
1633
|
function hardenCodexServerConfig() {
|
|
1613
1634
|
const cfgPath = codexConfigPath();
|
|
1614
1635
|
if (!cfgPath) {
|
|
1615
|
-
console.warn("
|
|
1636
|
+
console.warn(" Could not resolve ~/.codex/config.toml. Skipping approval-mode hardening.\n");
|
|
1616
1637
|
return;
|
|
1617
1638
|
}
|
|
1618
1639
|
|
|
1619
|
-
console.log("
|
|
1620
|
-
console.log("
|
|
1640
|
+
console.log(" Tip: for a hard dependency, add `required = true` under [mcp_servers.niro]");
|
|
1641
|
+
console.log(" (Codex then fails startup if niro is unreachable; off by default).\n");
|
|
1621
1642
|
|
|
1622
1643
|
// Clear per-tool overrides FIRST. Codex writes a [mcp_servers.niro.tools.<name>] table with
|
|
1623
1644
|
// approval_mode = "approve" whenever the developer answers a prompt with "approve once" rather
|
|
@@ -1633,7 +1654,7 @@ function hardenCodexServerConfig() {
|
|
|
1633
1654
|
|
|
1634
1655
|
const located = locateCodexNiroTable(cfgPath);
|
|
1635
1656
|
if (!located) {
|
|
1636
|
-
console.warn("
|
|
1657
|
+
console.warn(" [mcp_servers.niro] not found in config.toml. Skipping approval-mode hardening.\n");
|
|
1637
1658
|
return;
|
|
1638
1659
|
}
|
|
1639
1660
|
const { toml, bodyStart, bodyEnd, body } = located;
|
|
@@ -1641,7 +1662,7 @@ function hardenCodexServerConfig() {
|
|
|
1641
1662
|
const existing = body.match(/^[ \t]*default_tools_approval_mode[ \t]*=[ \t]*"([^"]*)"/m);
|
|
1642
1663
|
|
|
1643
1664
|
if (existing && existing[1] !== "auto") {
|
|
1644
|
-
console.log(`
|
|
1665
|
+
console.log(` Codex approval mode already set to "${existing[1]}", so your choice is left alone.\n`);
|
|
1645
1666
|
return;
|
|
1646
1667
|
}
|
|
1647
1668
|
|
|
@@ -1669,7 +1690,7 @@ function hardenCodexServerConfig() {
|
|
|
1669
1690
|
message.forEach((line) => console.log(line));
|
|
1670
1691
|
console.log("");
|
|
1671
1692
|
} catch (err) {
|
|
1672
|
-
console.warn(`
|
|
1693
|
+
console.warn(` Could not update the Codex approval mode: ${err.message}\n`);
|
|
1673
1694
|
}
|
|
1674
1695
|
}
|
|
1675
1696
|
|
|
@@ -1718,18 +1739,18 @@ function addCodexMemory() {
|
|
|
1718
1739
|
|
|
1719
1740
|
const { content, action } = upsertNiroMarkerSection(existing, section);
|
|
1720
1741
|
if (action === "unchanged") {
|
|
1721
|
-
console.log("
|
|
1742
|
+
console.log(" AGENTS.md entry already up to date. Skipping.\n");
|
|
1722
1743
|
return;
|
|
1723
1744
|
}
|
|
1724
1745
|
|
|
1725
1746
|
try {
|
|
1726
1747
|
fs.mkdirSync(path.dirname(agentsPath), { recursive: true });
|
|
1727
1748
|
fs.writeFileSync(agentsPath, content);
|
|
1728
|
-
console.log(`
|
|
1729
|
-
console.log("
|
|
1749
|
+
console.log(` Niro reminder ${action === "refreshed" ? "refreshed in" : "added to"} ~/.codex/AGENTS.md`);
|
|
1750
|
+
console.log(" → Codex will see Niro tool guidance across sessions.\n");
|
|
1730
1751
|
} catch (err) {
|
|
1731
|
-
console.warn(`
|
|
1732
|
-
console.warn("
|
|
1752
|
+
console.warn(` Could not write ~/.codex/AGENTS.md: ${err.message}`);
|
|
1753
|
+
console.warn(" Add manually to ~/.codex/AGENTS.md:\n");
|
|
1733
1754
|
console.warn(section + "\n");
|
|
1734
1755
|
}
|
|
1735
1756
|
}
|
|
@@ -1745,11 +1766,11 @@ function addAgentsSkill() {
|
|
|
1745
1766
|
try {
|
|
1746
1767
|
const result = writeManagedSkill(skillPath, NIRO_SKILL, "Shared niro-skill");
|
|
1747
1768
|
if (result === "added" || result === "updated") {
|
|
1748
|
-
console.log("
|
|
1769
|
+
console.log(" • Read by Codex and Windsurf (cross-agent skills location).");
|
|
1749
1770
|
}
|
|
1750
1771
|
console.log("");
|
|
1751
1772
|
} catch (err) {
|
|
1752
|
-
console.warn(`
|
|
1773
|
+
console.warn(` Could not write ~/.agents/skills/niro-skill/SKILL.md: ${err.message}\n`);
|
|
1753
1774
|
}
|
|
1754
1775
|
}
|
|
1755
1776
|
|
|
@@ -1763,11 +1784,11 @@ function addAgentsCliSkill() {
|
|
|
1763
1784
|
try {
|
|
1764
1785
|
const result = writeManagedSkill(skillPath, NIRO_CLI_SKILL, "Shared niro-cli skill");
|
|
1765
1786
|
if (result === "added" || result === "updated") {
|
|
1766
|
-
console.log("
|
|
1787
|
+
console.log(" • Read by Codex and Windsurf (cross-agent skills location).");
|
|
1767
1788
|
}
|
|
1768
1789
|
console.log("");
|
|
1769
1790
|
} catch (err) {
|
|
1770
|
-
console.warn(`
|
|
1791
|
+
console.warn(` Could not write ~/.agents/skills/niro-cli/SKILL.md: ${err.message}\n`);
|
|
1771
1792
|
}
|
|
1772
1793
|
}
|
|
1773
1794
|
|
|
@@ -1788,16 +1809,16 @@ function addWindsurfGlobalRule() {
|
|
|
1788
1809
|
}
|
|
1789
1810
|
const { content, action } = upsertNiroMarkerSection(existing, section);
|
|
1790
1811
|
if (action === "unchanged") {
|
|
1791
|
-
console.log("
|
|
1812
|
+
console.log(" Windsurf global rule already up to date. Skipping.\n");
|
|
1792
1813
|
return;
|
|
1793
1814
|
}
|
|
1794
1815
|
try {
|
|
1795
1816
|
fs.mkdirSync(path.dirname(rulePath), { recursive: true });
|
|
1796
1817
|
fs.writeFileSync(rulePath, content);
|
|
1797
|
-
console.log(`
|
|
1798
|
-
console.log("
|
|
1818
|
+
console.log(` Niro reminder ${action === "refreshed" ? "refreshed in" : "added to"} ~/.codeium/windsurf/memories/global_rules.md`);
|
|
1819
|
+
console.log(" → Windsurf stays aware of Niro across sessions.\n");
|
|
1799
1820
|
} catch (err) {
|
|
1800
|
-
console.warn(`
|
|
1821
|
+
console.warn(` Could not write Windsurf global_rules.md: ${err.message}\n`);
|
|
1801
1822
|
}
|
|
1802
1823
|
}
|
|
1803
1824
|
|
|
@@ -1816,15 +1837,15 @@ function appendMarkerSectionToFile(absPath, label, section = NIRO_GENERIC_MD_SEC
|
|
|
1816
1837
|
}
|
|
1817
1838
|
const { content, action } = upsertNiroMarkerSection(existing, section);
|
|
1818
1839
|
if (action === "unchanged") {
|
|
1819
|
-
console.log(`
|
|
1840
|
+
console.log(` ${label} entry already up to date. Skipping.\n`);
|
|
1820
1841
|
return;
|
|
1821
1842
|
}
|
|
1822
1843
|
try {
|
|
1823
1844
|
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
1824
1845
|
fs.writeFileSync(absPath, content);
|
|
1825
|
-
console.log(`
|
|
1846
|
+
console.log(` Niro reminder ${action === "refreshed" ? "refreshed in" : "added to"} ${absPath}\n`);
|
|
1826
1847
|
} catch (err) {
|
|
1827
|
-
console.warn(`
|
|
1848
|
+
console.warn(` Could not write ${absPath}: ${err.message}\n`);
|
|
1828
1849
|
}
|
|
1829
1850
|
}
|
|
1830
1851
|
|
|
@@ -1876,12 +1897,12 @@ function addClineRule() {
|
|
|
1876
1897
|
try {
|
|
1877
1898
|
const result = writeManagedSkill(rulePath, NIRO_CLINE_RULE, "Cline rule");
|
|
1878
1899
|
if (result === "added" || result === "updated") {
|
|
1879
|
-
console.log("
|
|
1900
|
+
console.log(" • Global: applies across all your repos in Cline.");
|
|
1880
1901
|
}
|
|
1881
1902
|
console.log("");
|
|
1882
1903
|
} catch (err) {
|
|
1883
|
-
console.warn(`
|
|
1884
|
-
console.warn(`
|
|
1904
|
+
console.warn(` Could not write Cline rule: ${err.message}`);
|
|
1905
|
+
console.warn(` If Cline uses ~/Cline/Rules instead of ~/Documents/Cline/Rules on your system, copy it there.\n`);
|
|
1885
1906
|
}
|
|
1886
1907
|
}
|
|
1887
1908
|
|
|
@@ -1915,11 +1936,11 @@ function addKiroSteering() {
|
|
|
1915
1936
|
try {
|
|
1916
1937
|
const result = writeManagedSkill(rulePath, NIRO_KIRO_STEERING, "Kiro steering file");
|
|
1917
1938
|
if (result === "added" || result === "updated") {
|
|
1918
|
-
console.log("
|
|
1939
|
+
console.log(" • Global: applies across all your repos in Kiro.");
|
|
1919
1940
|
}
|
|
1920
1941
|
console.log("");
|
|
1921
1942
|
} catch (err) {
|
|
1922
|
-
console.warn(`
|
|
1943
|
+
console.warn(` Could not write Kiro steering file: ${err.message}\n`);
|
|
1923
1944
|
}
|
|
1924
1945
|
}
|
|
1925
1946
|
|
|
@@ -1928,7 +1949,7 @@ function addKiroSteering() {
|
|
|
1928
1949
|
* `-s user` makes it a global (not project) registration, matching every other client here.
|
|
1929
1950
|
*/
|
|
1930
1951
|
async function setupGeminiCli(apiKey, mcpServerUrl) {
|
|
1931
|
-
console.log("\n
|
|
1952
|
+
console.log("\n Configuring Gemini CLI...\n");
|
|
1932
1953
|
|
|
1933
1954
|
const cmd = ["gemini", "mcp", "add", "-s", "user"];
|
|
1934
1955
|
if (apiKey) cmd.push("-e", `NIRO_API_KEY=${apiKey}`);
|
|
@@ -1937,16 +1958,16 @@ async function setupGeminiCli(apiKey, mcpServerUrl) {
|
|
|
1937
1958
|
|
|
1938
1959
|
try {
|
|
1939
1960
|
execSync(cmd.join(" "), { stdio: "inherit" });
|
|
1940
|
-
console.log("
|
|
1961
|
+
console.log(" MCP server registered in Gemini CLI (~/.gemini/settings.json).\n");
|
|
1941
1962
|
} catch (err) {
|
|
1942
|
-
console.error("
|
|
1943
|
-
console.error("
|
|
1944
|
-
console.error(`
|
|
1963
|
+
console.error(" Failed to register MCP server. Is the Gemini CLI installed?");
|
|
1964
|
+
console.error(" Install: https://github.com/google-gemini/gemini-cli\n");
|
|
1965
|
+
console.error(` Manual command:\n ${cmd.join(" ")}\n`);
|
|
1945
1966
|
return false;
|
|
1946
1967
|
}
|
|
1947
1968
|
|
|
1948
1969
|
addGeminiMemory();
|
|
1949
|
-
console.log("
|
|
1970
|
+
console.log(" Run `/mcp reload` inside Gemini CLI so the server + memory reload.\n");
|
|
1950
1971
|
return true;
|
|
1951
1972
|
}
|
|
1952
1973
|
|
|
@@ -1954,7 +1975,7 @@ async function setupGeminiCli(apiKey, mcpServerUrl) {
|
|
|
1954
1975
|
* Registers the niro MCP entry with the GitHub Copilot CLI (writes ~/.copilot/mcp-config.json).
|
|
1955
1976
|
*/
|
|
1956
1977
|
async function setupCopilotCli(apiKey, mcpServerUrl) {
|
|
1957
|
-
console.log("\n
|
|
1978
|
+
console.log("\n Configuring GitHub Copilot CLI...\n");
|
|
1958
1979
|
|
|
1959
1980
|
const cmd = ["copilot", "mcp", "add", "niro"];
|
|
1960
1981
|
if (apiKey) cmd.push("--env", `NIRO_API_KEY=${apiKey}`);
|
|
@@ -1963,15 +1984,15 @@ async function setupCopilotCli(apiKey, mcpServerUrl) {
|
|
|
1963
1984
|
|
|
1964
1985
|
try {
|
|
1965
1986
|
execSync(cmd.join(" "), { stdio: "inherit" });
|
|
1966
|
-
console.log("
|
|
1987
|
+
console.log(" MCP server registered in Copilot CLI (~/.copilot/mcp-config.json).\n");
|
|
1967
1988
|
} catch (err) {
|
|
1968
|
-
console.error("
|
|
1969
|
-
console.error(`
|
|
1989
|
+
console.error(" Failed to register MCP server. Is the Copilot CLI installed (npm i -g @github/copilot)?\n");
|
|
1990
|
+
console.error(` Manual command:\n ${cmd.join(" ")}\n`);
|
|
1970
1991
|
return false;
|
|
1971
1992
|
}
|
|
1972
1993
|
|
|
1973
1994
|
addCopilotCliMemory();
|
|
1974
|
-
console.log("
|
|
1995
|
+
console.log(" Run `/mcp` inside the Copilot CLI to verify. Servers are available immediately.\n");
|
|
1975
1996
|
return true;
|
|
1976
1997
|
}
|
|
1977
1998
|
|
|
@@ -1982,7 +2003,7 @@ async function setupCopilotCli(apiKey, mcpServerUrl) {
|
|
|
1982
2003
|
* ("niro mcp serve"), not user input, so static quoting is safe.
|
|
1983
2004
|
*/
|
|
1984
2005
|
async function setupFactory(apiKey, mcpServerUrl) {
|
|
1985
|
-
console.log("\n
|
|
2006
|
+
console.log("\n Configuring Factory (droid)...\n");
|
|
1986
2007
|
|
|
1987
2008
|
const cmd = ["droid", "mcp", "add", "niro", '"niro mcp serve"'];
|
|
1988
2009
|
if (apiKey) cmd.push("--env", `NIRO_API_KEY=${apiKey}`);
|
|
@@ -1990,16 +2011,16 @@ async function setupFactory(apiKey, mcpServerUrl) {
|
|
|
1990
2011
|
|
|
1991
2012
|
try {
|
|
1992
2013
|
execSync(cmd.join(" "), { stdio: "inherit" });
|
|
1993
|
-
console.log("
|
|
2014
|
+
console.log(" MCP server registered in Factory (~/.factory/mcp.json).\n");
|
|
1994
2015
|
} catch (err) {
|
|
1995
|
-
console.error("
|
|
1996
|
-
console.error("
|
|
1997
|
-
console.error(`
|
|
2016
|
+
console.error(" Failed to register MCP server. Is the droid CLI installed?");
|
|
2017
|
+
console.error(" Install: https://docs.factory.ai\n");
|
|
2018
|
+
console.error(` Manual command:\n ${cmd.join(" ")}\n`);
|
|
1998
2019
|
return false;
|
|
1999
2020
|
}
|
|
2000
2021
|
|
|
2001
2022
|
addFactoryMemory();
|
|
2002
|
-
console.log("
|
|
2023
|
+
console.log(" droid reloads automatically when ~/.factory/mcp.json changes.\n");
|
|
2003
2024
|
return true;
|
|
2004
2025
|
}
|
|
2005
2026
|
|
|
@@ -2040,9 +2061,9 @@ function gooseConfigPath() {
|
|
|
2040
2061
|
}
|
|
2041
2062
|
|
|
2042
2063
|
async function setupGoose(apiKey, mcpServerUrl) {
|
|
2043
|
-
console.log("\n
|
|
2064
|
+
console.log("\n Configuring Goose...\n");
|
|
2044
2065
|
const configPath = gooseConfigPath();
|
|
2045
|
-
console.log(`
|
|
2066
|
+
console.log(` Config: ${configPath}\n`);
|
|
2046
2067
|
|
|
2047
2068
|
const niroBlock = buildGooseNiroBlock(apiKey, mcpServerUrl);
|
|
2048
2069
|
let existing = "";
|
|
@@ -2051,7 +2072,7 @@ async function setupGoose(apiKey, mcpServerUrl) {
|
|
|
2051
2072
|
existedBefore = fs.existsSync(configPath);
|
|
2052
2073
|
if (existedBefore) existing = fs.readFileSync(configPath, "utf-8");
|
|
2053
2074
|
} catch (err) {
|
|
2054
|
-
console.warn(`
|
|
2075
|
+
console.warn(` Could not read ${configPath}: ${err.message}\n`);
|
|
2055
2076
|
}
|
|
2056
2077
|
|
|
2057
2078
|
// Matches exactly the " niro:" line install writes (2-space indent, nothing trailing) —
|
|
@@ -2059,7 +2080,7 @@ async function setupGoose(apiKey, mcpServerUrl) {
|
|
|
2059
2080
|
// match an unrelated key named "niro" anywhere in the file and wrongly report "already
|
|
2060
2081
|
// installed" without ever writing the real entry.
|
|
2061
2082
|
if (/^ {2}niro:\s*$/m.test(existing)) {
|
|
2062
|
-
console.log("
|
|
2083
|
+
console.log(" A `niro` extension entry already exists in config.yaml. Skipping (edit it by hand if it needs updating).\n");
|
|
2063
2084
|
return true;
|
|
2064
2085
|
}
|
|
2065
2086
|
|
|
@@ -2086,22 +2107,22 @@ async function setupGoose(apiKey, mcpServerUrl) {
|
|
|
2086
2107
|
// else: hasOtherExtensionsForm — next stays null, falls through to the manual-paste path.
|
|
2087
2108
|
|
|
2088
2109
|
if (next === null) {
|
|
2089
|
-
console.log("
|
|
2090
|
-
console.log("
|
|
2110
|
+
console.log(" Your config.yaml has an `extensions:` key in a form this installer doesn't");
|
|
2111
|
+
console.log(" auto-edit (to avoid risking a duplicate key). Add this block under it by hand:\n");
|
|
2091
2112
|
console.log(niroBlock);
|
|
2092
|
-
console.log(`\n
|
|
2113
|
+
console.log(`\n (full file: ${configPath})\n`);
|
|
2093
2114
|
return false;
|
|
2094
2115
|
}
|
|
2095
2116
|
|
|
2096
2117
|
try {
|
|
2097
2118
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
2098
2119
|
fs.writeFileSync(configPath, next);
|
|
2099
|
-
console.log(`
|
|
2100
|
-
console.log("
|
|
2120
|
+
console.log(` MCP server added to Goose config: ${configPath}\n`);
|
|
2121
|
+
console.log(" Restart Goose (or start a new session) so the extension loads.\n");
|
|
2101
2122
|
return true;
|
|
2102
2123
|
} catch (err) {
|
|
2103
|
-
console.error(`
|
|
2104
|
-
console.error("
|
|
2124
|
+
console.error(` Failed to write config: ${err.message}`);
|
|
2125
|
+
console.error(" Add this manually under a top-level `extensions:` key in config.yaml:\n");
|
|
2105
2126
|
console.log(`extensions:\n${niroBlock}`);
|
|
2106
2127
|
return false;
|
|
2107
2128
|
}
|
|
@@ -2119,13 +2140,13 @@ async function setupJsonConfig(clientId, apiKey, mcpServerUrl) {
|
|
|
2119
2140
|
const client = CLIENTS[clientId];
|
|
2120
2141
|
const configPathFn = client.configPath;
|
|
2121
2142
|
if (!configPathFn) {
|
|
2122
|
-
console.error(`
|
|
2143
|
+
console.error(` No config path known for ${client.name}.`);
|
|
2123
2144
|
return false;
|
|
2124
2145
|
}
|
|
2125
2146
|
|
|
2126
2147
|
const configPath = configPathFn();
|
|
2127
|
-
console.log(`\n
|
|
2128
|
-
console.log(`
|
|
2148
|
+
console.log(`\n Configuring ${client.name}...`);
|
|
2149
|
+
console.log(` Config: ${configPath}\n`);
|
|
2129
2150
|
|
|
2130
2151
|
// Read existing config or start fresh
|
|
2131
2152
|
let config = {};
|
|
@@ -2160,37 +2181,37 @@ async function setupJsonConfig(clientId, apiKey, mcpServerUrl) {
|
|
|
2160
2181
|
try {
|
|
2161
2182
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
2162
2183
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
2163
|
-
console.log(`
|
|
2184
|
+
console.log(` MCP server added to ${client.name} config.\n`);
|
|
2164
2185
|
if (clientId === "cursor") {
|
|
2165
2186
|
ensureCursorPermissionsForNiro();
|
|
2166
2187
|
ensureCursorRule();
|
|
2167
|
-
console.log("
|
|
2188
|
+
console.log(" Quit and reopen Cursor (full restart) so MCP + permissions reload.\n");
|
|
2168
2189
|
}
|
|
2169
2190
|
if (clientId === "windsurf") {
|
|
2170
2191
|
addAgentsSkill();
|
|
2171
2192
|
addAgentsCliSkill();
|
|
2172
2193
|
addWindsurfGlobalRule();
|
|
2173
|
-
console.log("
|
|
2194
|
+
console.log(" Restart Windsurf so MCP + rules reload.\n");
|
|
2174
2195
|
}
|
|
2175
2196
|
if (clientId === "opencode") {
|
|
2176
2197
|
addOpenCodeMemory();
|
|
2177
|
-
console.log("
|
|
2198
|
+
console.log(" Restart OpenCode so the MCP server reloads.\n");
|
|
2178
2199
|
}
|
|
2179
2200
|
if (clientId === "cline") {
|
|
2180
2201
|
addClineRule();
|
|
2181
|
-
console.log("
|
|
2202
|
+
console.log(" Reopen Cline's MCP Servers panel (or restart VS Code) to reload.\n");
|
|
2182
2203
|
}
|
|
2183
2204
|
if (clientId === "kiro") {
|
|
2184
2205
|
addKiroSteering();
|
|
2185
|
-
console.log("
|
|
2206
|
+
console.log(" Kiro reconnects MCP servers automatically on config save.\n");
|
|
2186
2207
|
}
|
|
2187
2208
|
if (clientId === "antigravity") {
|
|
2188
|
-
console.log("
|
|
2209
|
+
console.log(" Reload via Agent Settings → Customizations → Installed MCP Servers → Refresh.\n");
|
|
2189
2210
|
}
|
|
2190
2211
|
return true;
|
|
2191
2212
|
} catch (err) {
|
|
2192
|
-
console.error(`
|
|
2193
|
-
console.error(`
|
|
2213
|
+
console.error(` Failed to write config: ${err.message}`);
|
|
2214
|
+
console.error(` Manually add to ${configPath}:\n`);
|
|
2194
2215
|
console.log(JSON.stringify({ [topLevelKey]: { niro: config[topLevelKey].niro } }, null, 2));
|
|
2195
2216
|
return false;
|
|
2196
2217
|
}
|
|
@@ -2219,7 +2240,7 @@ async function main() {
|
|
|
2219
2240
|
else if (args[i] === "--url" && args[i + 1]) urlArg = args[++i];
|
|
2220
2241
|
}
|
|
2221
2242
|
if (urlArg && !/^https?:\/\//i.test(urlArg)) {
|
|
2222
|
-
console.error(`\n
|
|
2243
|
+
console.error(`\n Invalid --url: "${urlArg}". It must start with http:// or https://\n`);
|
|
2223
2244
|
process.exit(1);
|
|
2224
2245
|
}
|
|
2225
2246
|
|
|
@@ -2234,13 +2255,13 @@ async function main() {
|
|
|
2234
2255
|
// ~/.niro/credentials.json). Only prompt for an API key when there is no cli-token.
|
|
2235
2256
|
let apiKey = null;
|
|
2236
2257
|
if (hasCliToken()) {
|
|
2237
|
-
console.log("\n
|
|
2238
|
-
console.log("
|
|
2258
|
+
console.log("\n Using your `niro login` session, so no API key needed.");
|
|
2259
|
+
console.log(" The MCP server will authenticate with your CLI token from ~/.niro/credentials.json.\n");
|
|
2239
2260
|
} else {
|
|
2240
|
-
apiKey = await ask(rl, "
|
|
2261
|
+
apiKey = await ask(rl, " Niro API key (from console.niroai.dev > Settings)", resolveApiKey());
|
|
2241
2262
|
if (!apiKey) {
|
|
2242
|
-
console.error("\n
|
|
2243
|
-
console.error("
|
|
2263
|
+
console.error("\n No credential found. Run `niro login` first (recommended), or paste an API key");
|
|
2264
|
+
console.error(" from console.niroai.dev > Settings > Account.\n");
|
|
2244
2265
|
rl.close();
|
|
2245
2266
|
process.exit(1);
|
|
2246
2267
|
}
|
|
@@ -2249,9 +2270,9 @@ async function main() {
|
|
|
2249
2270
|
// The MCP server URL is resolved, not prompted — see resolveMcpServerUrl.
|
|
2250
2271
|
// Printing the URL + its source keeps a wrong pick visible and correctable.
|
|
2251
2272
|
const { url: mcpServerUrl, source: urlSource, explicit } = resolveMcpServerUrl(urlArg);
|
|
2252
|
-
console.log(`
|
|
2273
|
+
console.log(` MCP server URL: ${mcpServerUrl} (${urlSource})`);
|
|
2253
2274
|
if (!urlArg) {
|
|
2254
|
-
console.log("
|
|
2275
|
+
console.log(" Different backend? Re-run with: niro mcp install --url <url>\n");
|
|
2255
2276
|
} else {
|
|
2256
2277
|
console.log("");
|
|
2257
2278
|
}
|
|
@@ -2263,20 +2284,29 @@ async function main() {
|
|
|
2263
2284
|
try {
|
|
2264
2285
|
config.save({ mcpServerUrl });
|
|
2265
2286
|
} catch (err) {
|
|
2266
|
-
console.warn(`\n
|
|
2287
|
+
console.warn(`\n Could not write ${config.FILE}: ${err.message}\n`);
|
|
2267
2288
|
}
|
|
2268
2289
|
}
|
|
2269
2290
|
|
|
2291
|
+
// `--list` is a lookup, not an install: print every client and stop. rl is closed first,
|
|
2292
|
+
// or the process hangs on an open readline that nothing will ever answer.
|
|
2293
|
+
if (process.argv.includes("--list")) {
|
|
2294
|
+
console.log("");
|
|
2295
|
+
printClients({ all: true });
|
|
2296
|
+
rl.close();
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2270
2300
|
// Select client
|
|
2271
2301
|
let selectedClient = clientArg;
|
|
2272
2302
|
if (!selectedClient) {
|
|
2273
2303
|
console.log("");
|
|
2274
2304
|
printClients();
|
|
2275
|
-
selectedClient = await ask(rl, "
|
|
2305
|
+
selectedClient = await ask(rl, " Which client to configure?", "claude-code");
|
|
2276
2306
|
}
|
|
2277
2307
|
|
|
2278
2308
|
if (!CLIENTS[selectedClient]) {
|
|
2279
|
-
console.error(`\n
|
|
2309
|
+
console.error(`\n Unknown client: ${selectedClient}`);
|
|
2280
2310
|
printClients();
|
|
2281
2311
|
rl.close();
|
|
2282
2312
|
process.exit(1);
|
|
@@ -2292,13 +2322,13 @@ async function main() {
|
|
|
2292
2322
|
// only applies to some (e.g. a query-string URL with "&" is fine for Cursor/VS Code/etc.).
|
|
2293
2323
|
if (CLI_SETUP[selectedClient]) {
|
|
2294
2324
|
if (hasShellMetacharacters(mcpServerUrl)) {
|
|
2295
|
-
console.error(`\n
|
|
2296
|
-
console.error("
|
|
2325
|
+
console.error(`\n Refusing to use MCP server URL "${mcpServerUrl}": it contains spaces or`);
|
|
2326
|
+
console.error(" characters not allowed in a URL. Check --url / NIRO_MCP_SERVER_URL.\n");
|
|
2297
2327
|
process.exit(1);
|
|
2298
2328
|
}
|
|
2299
2329
|
if (apiKey && hasShellMetacharacters(apiKey)) {
|
|
2300
|
-
console.error("\n
|
|
2301
|
-
console.error("
|
|
2330
|
+
console.error("\n Refusing to use that API key: it contains spaces or characters that aren't");
|
|
2331
|
+
console.error(" valid in an API key. Copy it again from console.niroai.dev > Settings > Account.\n");
|
|
2302
2332
|
process.exit(1);
|
|
2303
2333
|
}
|
|
2304
2334
|
}
|
|
@@ -2314,12 +2344,15 @@ async function main() {
|
|
|
2314
2344
|
}
|
|
2315
2345
|
|
|
2316
2346
|
if (success) {
|
|
2317
|
-
|
|
2347
|
+
// One line about what all of that was for, instead of a paragraph under each item.
|
|
2348
|
+
console.log(" Those hooks and skills are what remind your assistant to use Niro, and let");
|
|
2349
|
+
console.log(" you drive the CLI from chat. `niro mcp uninstall` removes all of them.");
|
|
2350
|
+
console.log(" Setup complete! Restart your AI assistant to start using Niro.\n");
|
|
2318
2351
|
if (selectedClient !== "claude-code") {
|
|
2319
|
-
console.log("
|
|
2320
|
-
console.log("
|
|
2321
|
-
console.log("
|
|
2322
|
-
console.log("
|
|
2352
|
+
console.log(" Tip: drop a `.niro-project` file at the repo root (single line");
|
|
2353
|
+
console.log(" with the project_id) to pin that tree to a Niro project.");
|
|
2354
|
+
console.log(" Without it, Niro runs in multi-project mode and discovers");
|
|
2355
|
+
console.log(" projects via the list_projects tool.\n");
|
|
2323
2356
|
}
|
|
2324
2357
|
}
|
|
2325
2358
|
}
|
|
@@ -2335,6 +2368,11 @@ if (require.main === module) {
|
|
|
2335
2368
|
}
|
|
2336
2369
|
|
|
2337
2370
|
module.exports = {
|
|
2371
|
+
// Exported so `niro ab` resolves the ai-assistant base URL through the SAME precedence the
|
|
2372
|
+
// MCP installer uses (--url > NIRO_MCP_SERVER_URL > saved config > cloud default). A second
|
|
2373
|
+
// resolver would drift the first time that order changed, and the two would then disagree
|
|
2374
|
+
// about which backend a machine is pointed at.
|
|
2375
|
+
resolveMcpServerUrl,
|
|
2338
2376
|
writeManagedSkill,
|
|
2339
2377
|
addClaudeCodeSkill,
|
|
2340
2378
|
addClaudeCodeCliSkill,
|