@intellectronica/ruler 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/dist/agents/CopilotAgent.js +4 -64
- package/dist/agents/FirebaseAgent.js +8 -0
- package/dist/agents/FirebenderAgent.js +205 -0
- package/dist/agents/index.js +2 -0
- package/dist/core/apply-engine.js +24 -2
- package/dist/paths/mcp.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
57
57
|
| Agent | Rules File(s) | MCP Configuration / Notes |
|
|
58
58
|
| ---------------- | ------------------------------------------------ | ------------------------------------------------ |
|
|
59
59
|
| AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
|
|
60
|
-
| GitHub Copilot | `AGENTS.md
|
|
60
|
+
| GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` |
|
|
61
61
|
| Claude Code | `CLAUDE.md` | `.mcp.json` |
|
|
62
62
|
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
|
|
63
63
|
| Jules | `AGENTS.md` | - |
|
|
@@ -68,11 +68,11 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
68
68
|
| Amp | `AGENTS.md` | - |
|
|
69
69
|
| Amazon Q CLI | `.amazonq/rules/ruler_q_rules.md` | `.amazonq/mcp.json` |
|
|
70
70
|
| Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` |
|
|
71
|
-
| Firebase Studio | `.idx/airules.md` |
|
|
71
|
+
| Firebase Studio | `.idx/airules.md` | `.idx/mcp.json` |
|
|
72
72
|
| Open Hands | `.openhands/microagents/repo.md` | `config.toml` |
|
|
73
73
|
| Gemini CLI | `AGENTS.md` | `.gemini/settings.json` |
|
|
74
74
|
| Junie | `.junie/guidelines.md` | - |
|
|
75
|
-
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` |
|
|
75
|
+
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | - |
|
|
76
76
|
| Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
|
|
77
77
|
| opencode | `AGENTS.md` | `opencode.json` |
|
|
78
78
|
| Goose | `.goosehints` | - |
|
|
@@ -82,6 +82,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
82
82
|
| Trae AI | `.trae/rules/project_rules.md` | - |
|
|
83
83
|
| Warp | `WARP.md` | - |
|
|
84
84
|
| Kiro | `.kiro/steering/ruler_kiro_instructions.md` | - |
|
|
85
|
+
| Firebender | `firebender.json` | - |
|
|
85
86
|
|
|
86
87
|
## Getting Started
|
|
87
88
|
|
|
@@ -214,7 +215,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
214
215
|
| Option | Description |
|
|
215
216
|
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
216
217
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
217
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, amazonqcli, amp,
|
|
218
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, aider, amazonqcli, amp, augmentcode, claude, cline, codex, copilot, crush, cursor, firebase, firebender, gemini-cli, goose, jules, junie, kilocode, kiro, opencode, openhands, qwen, roo, trae, warp, windsurf, zed) |
|
|
218
219
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
219
220
|
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
|
|
220
221
|
| `--no-mcp` | Disable applying MCP server configurations |
|
|
@@ -307,7 +308,7 @@ ruler revert [options]
|
|
|
307
308
|
| Option | Description |
|
|
308
309
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
309
310
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
310
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, amazonqcli, amp,
|
|
311
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, aider, amazonqcli, amp, augmentcode, claude, cline, codex, copilot, crush, cursor, firebase, firebender, gemini-cli, goose, jules, junie, kilocode, kiro, opencode, openhands, qwen, roo, trae, warp, windsurf, zed) |
|
|
311
312
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
312
313
|
| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
|
|
313
314
|
| `--dry-run` | Preview changes without actually reverting files |
|
|
@@ -389,7 +390,6 @@ enabled = true
|
|
|
389
390
|
# --- Agent-Specific Configurations ---
|
|
390
391
|
[agents.copilot]
|
|
391
392
|
enabled = true
|
|
392
|
-
output_path = ".github/copilot-instructions.md"
|
|
393
393
|
|
|
394
394
|
[agents.claude]
|
|
395
395
|
enabled = true
|
|
@@ -565,7 +565,6 @@ node_modules/
|
|
|
565
565
|
.aider.conf.yml
|
|
566
566
|
.clinerules
|
|
567
567
|
.cursor/rules/ruler_cursor_instructions.mdc
|
|
568
|
-
.github/copilot-instructions.md
|
|
569
568
|
.windsurf/rules/ruler_windsurf_instructions.md
|
|
570
569
|
AGENTS.md
|
|
571
570
|
CLAUDE.md
|
|
@@ -1,47 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.CopilotAgent = void 0;
|
|
37
|
-
const path = __importStar(require("path"));
|
|
38
4
|
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
39
|
-
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
40
|
-
const fs_1 = require("fs");
|
|
41
5
|
/**
|
|
42
6
|
* GitHub Copilot agent adapter.
|
|
43
|
-
* Writes to
|
|
44
|
-
* .github/copilot-instructions.md (for VS Code extension compatibility).
|
|
7
|
+
* Writes to AGENTS.md for both web-based GitHub Copilot and VS Code extension.
|
|
45
8
|
*/
|
|
46
9
|
class CopilotAgent {
|
|
47
10
|
constructor() {
|
|
@@ -54,41 +17,18 @@ class CopilotAgent {
|
|
|
54
17
|
return 'GitHub Copilot';
|
|
55
18
|
}
|
|
56
19
|
/**
|
|
57
|
-
* Returns
|
|
20
|
+
* Returns the default output path for AGENTS.md.
|
|
58
21
|
*/
|
|
59
22
|
getDefaultOutputPath(projectRoot) {
|
|
60
|
-
return
|
|
61
|
-
instructions: path.join(projectRoot, 'AGENTS.md'),
|
|
62
|
-
legacy: path.join(projectRoot, '.github', 'copilot-instructions.md'),
|
|
63
|
-
};
|
|
23
|
+
return this.agentsMdAgent.getDefaultOutputPath(projectRoot);
|
|
64
24
|
}
|
|
65
25
|
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
|
|
66
|
-
//
|
|
26
|
+
// Write to AGENTS.md using the existing AgentsMdAgent infrastructure
|
|
67
27
|
await this.agentsMdAgent.applyRulerConfig(concatenatedRules, projectRoot, null, // No MCP config needed for the instructions file
|
|
68
28
|
{
|
|
69
29
|
// Preserve explicit outputPath precedence semantics if provided
|
|
70
30
|
outputPath: agentConfig?.outputPath || agentConfig?.outputPathInstructions,
|
|
71
31
|
}, backup);
|
|
72
|
-
// Additionally write to .github/copilot-instructions.md for VS Code extension compatibility
|
|
73
|
-
const outputPaths = this.getDefaultOutputPath(projectRoot);
|
|
74
|
-
const legacyPath = path.resolve(projectRoot, outputPaths.legacy);
|
|
75
|
-
// Add marker comment to the content to identify it as generated
|
|
76
|
-
const contentWithMarker = `<!-- Generated by Ruler -->\n${concatenatedRules}`;
|
|
77
|
-
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(legacyPath));
|
|
78
|
-
// Check if content has changed (idempotency)
|
|
79
|
-
let existingLegacy = null;
|
|
80
|
-
try {
|
|
81
|
-
existingLegacy = await fs_1.promises.readFile(legacyPath, 'utf8');
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
existingLegacy = null;
|
|
85
|
-
}
|
|
86
|
-
if (existingLegacy === null || existingLegacy !== contentWithMarker) {
|
|
87
|
-
if (backup) {
|
|
88
|
-
await (0, FileSystemUtils_1.backupFile)(legacyPath);
|
|
89
|
-
}
|
|
90
|
-
await (0, FileSystemUtils_1.writeGeneratedFile)(legacyPath, contentWithMarker);
|
|
91
|
-
}
|
|
92
32
|
}
|
|
93
33
|
getMcpServerKey() {
|
|
94
34
|
return 'servers';
|
|
@@ -49,5 +49,13 @@ class FirebaseAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
49
49
|
getDefaultOutputPath(projectRoot) {
|
|
50
50
|
return path.join(projectRoot, '.idx', 'airules.md');
|
|
51
51
|
}
|
|
52
|
+
// Firebase Studio (IDX) supports stdio MCP servers via .idx/mcp.json
|
|
53
|
+
supportsMcpStdio() {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// Remote MCP over HTTP/SSE is not documented for Firebase Studio yet
|
|
57
|
+
supportsMcpRemote() {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
61
|
exports.FirebaseAgent = FirebaseAgent;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FirebenderAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
40
|
+
/**
|
|
41
|
+
* Firebender agent adapter.
|
|
42
|
+
*/
|
|
43
|
+
class FirebenderAgent {
|
|
44
|
+
/**
|
|
45
|
+
* Type guard function to safely check if an object is a FirebenderRule.
|
|
46
|
+
*/
|
|
47
|
+
isFirebenderRule(rule) {
|
|
48
|
+
return (typeof rule === 'object' &&
|
|
49
|
+
rule !== null &&
|
|
50
|
+
'filePathMatches' in rule &&
|
|
51
|
+
'rulesPaths' in rule &&
|
|
52
|
+
typeof rule.filePathMatches === 'string' &&
|
|
53
|
+
typeof rule.rulesPaths === 'string');
|
|
54
|
+
}
|
|
55
|
+
getIdentifier() {
|
|
56
|
+
return 'firebender';
|
|
57
|
+
}
|
|
58
|
+
getName() {
|
|
59
|
+
return 'Firebender';
|
|
60
|
+
}
|
|
61
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
|
|
62
|
+
const rulesPath = this.resolveOutputPath(projectRoot, agentConfig);
|
|
63
|
+
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(rulesPath));
|
|
64
|
+
const firebenderConfig = await this.loadExistingConfig(rulesPath);
|
|
65
|
+
const newRules = this.createRulesFromConcatenatedRules(concatenatedRules, projectRoot);
|
|
66
|
+
firebenderConfig.rules.push(...newRules);
|
|
67
|
+
this.removeDuplicateRules(firebenderConfig);
|
|
68
|
+
const mcpEnabled = agentConfig?.mcp?.enabled ?? true;
|
|
69
|
+
if (mcpEnabled && rulerMcpJson) {
|
|
70
|
+
await this.handleMcpConfiguration(firebenderConfig, rulerMcpJson, agentConfig);
|
|
71
|
+
}
|
|
72
|
+
await this.saveConfig(rulesPath, firebenderConfig, backup);
|
|
73
|
+
}
|
|
74
|
+
resolveOutputPath(projectRoot, agentConfig) {
|
|
75
|
+
const outputPaths = this.getDefaultOutputPath(projectRoot);
|
|
76
|
+
const output = agentConfig?.outputPath ??
|
|
77
|
+
agentConfig?.outputPathInstructions ??
|
|
78
|
+
outputPaths['instructions'];
|
|
79
|
+
return path.resolve(projectRoot, output);
|
|
80
|
+
}
|
|
81
|
+
async loadExistingConfig(rulesPath) {
|
|
82
|
+
try {
|
|
83
|
+
const existingContent = await fs.promises.readFile(rulesPath, 'utf8');
|
|
84
|
+
const config = JSON.parse(existingContent);
|
|
85
|
+
if (!config.rules) {
|
|
86
|
+
config.rules = [];
|
|
87
|
+
}
|
|
88
|
+
return config;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error &&
|
|
92
|
+
typeof error === 'object' &&
|
|
93
|
+
'code' in error &&
|
|
94
|
+
error.code === 'ENOENT') {
|
|
95
|
+
return { rules: [] };
|
|
96
|
+
}
|
|
97
|
+
console.warn(`Failed to read/parse existing firebender.json: ${error}`);
|
|
98
|
+
return { rules: [] };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
createRulesFromConcatenatedRules(concatenatedRules, projectRoot) {
|
|
102
|
+
const filePaths = this.extractFilePathsFromRules(concatenatedRules, projectRoot);
|
|
103
|
+
if (filePaths.length > 0) {
|
|
104
|
+
return this.createRuleObjectsFromFilePaths(filePaths);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
return this.createRulesFromPlainText(concatenatedRules);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
createRuleObjectsFromFilePaths(filePaths) {
|
|
111
|
+
return filePaths.map((filePath) => ({
|
|
112
|
+
filePathMatches: '**/*',
|
|
113
|
+
rulesPaths: filePath,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
createRulesFromPlainText(concatenatedRules) {
|
|
117
|
+
return concatenatedRules.split('\n').filter((rule) => rule.trim());
|
|
118
|
+
}
|
|
119
|
+
removeDuplicateRules(firebenderConfig) {
|
|
120
|
+
const seen = new Set();
|
|
121
|
+
firebenderConfig.rules = firebenderConfig.rules.filter((rule) => {
|
|
122
|
+
let key;
|
|
123
|
+
if (this.isFirebenderRule(rule)) {
|
|
124
|
+
const filePathMatchesPart = rule.filePathMatches;
|
|
125
|
+
const rulesPathsPart = rule.rulesPaths;
|
|
126
|
+
key = `${filePathMatchesPart}::${rulesPathsPart}`;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
key = String(rule);
|
|
130
|
+
}
|
|
131
|
+
if (seen.has(key)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
seen.add(key);
|
|
135
|
+
return true;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async saveConfig(rulesPath, config, backup) {
|
|
139
|
+
const updatedContent = JSON.stringify(config, null, 2);
|
|
140
|
+
if (backup) {
|
|
141
|
+
await (0, FileSystemUtils_1.backupFile)(rulesPath);
|
|
142
|
+
}
|
|
143
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(rulesPath, updatedContent);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Handle MCP server configuration for Firebender.
|
|
147
|
+
* Merges or overwrites MCP servers in the firebender.json configuration based on strategy.
|
|
148
|
+
*/
|
|
149
|
+
async handleMcpConfiguration(firebenderConfig, rulerMcpJson, agentConfig) {
|
|
150
|
+
const strategy = agentConfig?.mcp?.strategy ?? 'merge';
|
|
151
|
+
const incomingServers = rulerMcpJson.mcpServers || {};
|
|
152
|
+
if (!firebenderConfig.mcpServers) {
|
|
153
|
+
firebenderConfig.mcpServers = {};
|
|
154
|
+
}
|
|
155
|
+
if (strategy === 'overwrite') {
|
|
156
|
+
firebenderConfig.mcpServers = { ...incomingServers };
|
|
157
|
+
}
|
|
158
|
+
else if (strategy === 'merge') {
|
|
159
|
+
const existingServers = firebenderConfig.mcpServers || {};
|
|
160
|
+
firebenderConfig.mcpServers = { ...existingServers, ...incomingServers };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
getDefaultOutputPath(projectRoot) {
|
|
164
|
+
return {
|
|
165
|
+
instructions: path.join(projectRoot, 'firebender.json'),
|
|
166
|
+
mcp: path.join(projectRoot, 'firebender.json'),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
getMcpServerKey() {
|
|
170
|
+
return 'mcpServers';
|
|
171
|
+
}
|
|
172
|
+
supportsMcpStdio() {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
supportsMcpRemote() {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Extracts file paths from concatenated rules by parsing HTML source comments.
|
|
180
|
+
* @param concatenatedRules The concatenated rules string with HTML comments
|
|
181
|
+
* @param projectRoot The project root directory
|
|
182
|
+
* @returns Array of file paths relative to project root
|
|
183
|
+
*/
|
|
184
|
+
extractFilePathsFromRules(concatenatedRules, projectRoot) {
|
|
185
|
+
const sourceCommentRegex = /<!-- Source: (.+?) -->/g;
|
|
186
|
+
const filePaths = [];
|
|
187
|
+
let match;
|
|
188
|
+
while ((match = sourceCommentRegex.exec(concatenatedRules)) !== null) {
|
|
189
|
+
const relativePath = match[1];
|
|
190
|
+
const absolutePath = path.resolve(projectRoot, relativePath);
|
|
191
|
+
const normalizedProjectRoot = path.resolve(projectRoot);
|
|
192
|
+
// Ensure the absolutePath is within the project root (cross-platform compatible)
|
|
193
|
+
// This prevents path traversal attacks while handling Windows/Unix path differences
|
|
194
|
+
const isWithinProject = absolutePath.startsWith(normalizedProjectRoot) &&
|
|
195
|
+
(absolutePath.length === normalizedProjectRoot.length ||
|
|
196
|
+
absolutePath[normalizedProjectRoot.length] === path.sep);
|
|
197
|
+
if (isWithinProject) {
|
|
198
|
+
const projectRelativePath = path.relative(projectRoot, absolutePath);
|
|
199
|
+
filePaths.push(projectRelativePath);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return filePaths;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.FirebenderAgent = FirebenderAgent;
|
package/dist/agents/index.js
CHANGED
|
@@ -30,6 +30,7 @@ const WarpAgent_1 = require("./WarpAgent");
|
|
|
30
30
|
const RooCodeAgent_1 = require("./RooCodeAgent");
|
|
31
31
|
const TraeAgent_1 = require("./TraeAgent");
|
|
32
32
|
const AmazonQCliAgent_1 = require("./AmazonQCliAgent");
|
|
33
|
+
const FirebenderAgent_1 = require("./FirebenderAgent");
|
|
33
34
|
exports.allAgents = [
|
|
34
35
|
new CopilotAgent_1.CopilotAgent(),
|
|
35
36
|
new ClaudeAgent_1.ClaudeAgent(),
|
|
@@ -57,6 +58,7 @@ exports.allAgents = [
|
|
|
57
58
|
new RooCodeAgent_1.RooCodeAgent(),
|
|
58
59
|
new TraeAgent_1.TraeAgent(),
|
|
59
60
|
new AmazonQCliAgent_1.AmazonQCliAgent(),
|
|
61
|
+
new FirebenderAgent_1.FirebenderAgent(),
|
|
60
62
|
];
|
|
61
63
|
/**
|
|
62
64
|
* Generates a comma-separated list of agent identifiers for CLI help text.
|
|
@@ -392,15 +392,37 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
|
|
|
392
392
|
}
|
|
393
393
|
const existing = await (0, mcp_1.readNativeMcp)(dest);
|
|
394
394
|
const merged = (0, merge_1.mergeMcp)(existing, mcpToMerge, strategy, serverKey);
|
|
395
|
+
// Firebase Studio (IDX) expects no "type" fields in .idx/mcp.json server entries.
|
|
396
|
+
// Sanitize merged config by stripping 'type' from each server when targeting Firebase.
|
|
397
|
+
const sanitizeForFirebase = (obj) => {
|
|
398
|
+
if (agent.getIdentifier() !== 'firebase')
|
|
399
|
+
return obj;
|
|
400
|
+
const out = { ...obj };
|
|
401
|
+
const servers = out[serverKey] || {};
|
|
402
|
+
const cleanedServers = {};
|
|
403
|
+
for (const [name, def] of Object.entries(servers)) {
|
|
404
|
+
if (def && typeof def === 'object') {
|
|
405
|
+
const copy = { ...def };
|
|
406
|
+
delete copy.type;
|
|
407
|
+
cleanedServers[name] = copy;
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
cleanedServers[name] = def;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
out[serverKey] = cleanedServers;
|
|
414
|
+
return out;
|
|
415
|
+
};
|
|
416
|
+
const toWrite = sanitizeForFirebase(merged);
|
|
395
417
|
// Only backup and write if content would actually change (idempotent)
|
|
396
418
|
const currentContent = JSON.stringify(existing, null, 2);
|
|
397
|
-
const newContent = JSON.stringify(
|
|
419
|
+
const newContent = JSON.stringify(toWrite, null, 2);
|
|
398
420
|
if (currentContent !== newContent) {
|
|
399
421
|
if (backup) {
|
|
400
422
|
const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
|
|
401
423
|
await backupFile(dest);
|
|
402
424
|
}
|
|
403
|
-
await (0, mcp_1.writeNativeMcp)(dest,
|
|
425
|
+
await (0, mcp_1.writeNativeMcp)(dest, toWrite);
|
|
404
426
|
}
|
|
405
427
|
else {
|
|
406
428
|
(0, constants_1.logVerbose)(`MCP config for ${agent.getName()} is already up to date - skipping backup and write`, verbose);
|
package/dist/paths/mcp.js
CHANGED
|
@@ -80,6 +80,9 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
80
80
|
case 'OpenCode':
|
|
81
81
|
candidates.push(path.join(projectRoot, 'opencode.json'));
|
|
82
82
|
break;
|
|
83
|
+
case 'Firebase Studio':
|
|
84
|
+
candidates.push(path.join(projectRoot, '.idx', 'mcp.json'));
|
|
85
|
+
break;
|
|
83
86
|
case 'Zed':
|
|
84
87
|
// Only consider project-local Zed settings (avoid writing to user home directory)
|
|
85
88
|
candidates.push(path.join(projectRoot, '.zed', 'settings.json'));
|