@intellectronica/ruler 0.2.14 → 0.2.16

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 CHANGED
@@ -40,7 +40,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
40
40
  | ---------------- | ------------------------------------------------ | --------------------------------------------------- |
41
41
  | GitHub Copilot | `.github/copilot-instructions.md` | `.vscode/mcp.json` |
42
42
  | Claude Code | `CLAUDE.md` | `claude_desktop_config.json` |
43
- | OpenAI Codex CLI | `AGENTS.md` | `~/.codex/config.json` |
43
+ | OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml`, `~/.codex/config.json` |
44
44
  | Jules | `AGENTS.md` | - |
45
45
  | Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json`, `~/.cursor/mcp.json` |
46
46
  | Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` | `~/.codeium/windsurf/mcp_config.json` |
@@ -53,6 +53,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
53
53
  | AugmentCode | `.augment/rules/ruler_augment_instructions.md` | `.vscode/settings.json` |
54
54
  | Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
55
55
  | OpenCode | `AGENTS.md` | `opencode.json`, `~/.config/opencode/opencode.json` |
56
+ | Goose | `.goosehints` | - |
56
57
 
57
58
  ## Getting Started
58
59
 
@@ -299,6 +300,17 @@ enabled = true
299
300
  output_path_instructions = "ruler_aider_instructions.md"
300
301
  output_path_config = ".aider.conf.yml"
301
302
 
303
+ # OpenAI Codex CLI agent and MCP config
304
+ [agents.codex]
305
+ enabled = true
306
+ output_path = "AGENTS.md"
307
+ output_path_config = ".codex/config.toml"
308
+
309
+ # Agent-specific MCP configuration for Codex CLI
310
+ [agents.codex.mcp]
311
+ enabled = true
312
+ merge_strategy = "merge"
313
+
302
314
  [agents.firebase]
303
315
  enabled = true
304
316
  output_path = ".idx/airules.md"
@@ -360,8 +372,14 @@ Define your project's MCP servers:
360
372
  }
361
373
  ```
362
374
 
375
+
363
376
  Ruler uses this file with the `merge` (default) or `overwrite` strategy, controlled by `ruler.toml` or CLI flags.
364
377
 
378
+ **Note for OpenAI Codex CLI:** To apply the local Codex CLI MCP configuration, set the `CODEX_HOME` environment variable to your project’s `.codex` directory:
379
+ ```bash
380
+ export CODEX_HOME="$(pwd)/.codex"
381
+ ```
382
+
365
383
  ## `.gitignore` Integration
366
384
 
367
385
  Ruler automatically manages your `.gitignore` file to keep generated agent configuration files out of version control.
@@ -35,9 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.CodexCliAgent = void 0;
37
37
  const path = __importStar(require("path"));
38
+ /* eslint-disable @typescript-eslint/no-explicit-any */
39
+ const fs_1 = require("fs");
40
+ const toml = __importStar(require("toml"));
41
+ const toml_1 = require("@iarna/toml");
38
42
  const FileSystemUtils_1 = require("../core/FileSystemUtils");
39
43
  /**
40
- * OpenAI Codex CLI agent adapter (stub implementation).
44
+ * OpenAI Codex CLI agent adapter.
41
45
  */
42
46
  class CodexCliAgent {
43
47
  getIdentifier() {
@@ -46,14 +50,107 @@ class CodexCliAgent {
46
50
  getName() {
47
51
  return 'OpenAI Codex CLI';
48
52
  }
49
- async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
50
- agentConfig) {
51
- const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
52
- await (0, FileSystemUtils_1.backupFile)(output);
53
- await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
53
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
54
+ // Get default paths
55
+ const defaults = this.getDefaultOutputPath(projectRoot);
56
+ // Determine the instructions file path
57
+ const instructionsPath = agentConfig?.outputPath ??
58
+ agentConfig?.outputPathInstructions ??
59
+ defaults.instructions;
60
+ // Write the instructions file
61
+ await (0, FileSystemUtils_1.backupFile)(instructionsPath);
62
+ await (0, FileSystemUtils_1.writeGeneratedFile)(instructionsPath, concatenatedRules);
63
+ // Handle MCP configuration if enabled
64
+ const mcpEnabled = agentConfig?.mcp?.enabled ?? true;
65
+ if (mcpEnabled && rulerMcpJson) {
66
+ // Determine the config file path
67
+ const configPath = agentConfig?.outputPathConfig ?? defaults.config;
68
+ // Ensure the parent directory exists
69
+ await fs_1.promises.mkdir(path.dirname(configPath), { recursive: true });
70
+ // Get the merge strategy
71
+ const strategy = agentConfig?.mcp?.strategy ?? 'merge';
72
+ // Extract MCP servers from ruler config
73
+ const rulerServers = rulerMcpJson.mcpServers || {};
74
+ // Read existing TOML config if it exists
75
+ let existingConfig = {};
76
+ try {
77
+ const existingContent = await fs_1.promises.readFile(configPath, 'utf8');
78
+ existingConfig = toml.parse(existingContent);
79
+ }
80
+ catch {
81
+ // File doesn't exist or can't be parsed, use empty config
82
+ }
83
+ // Create the updated config
84
+ const updatedConfig = { ...existingConfig };
85
+ // Initialize mcp_servers if it doesn't exist
86
+ if (!updatedConfig.mcp_servers) {
87
+ updatedConfig.mcp_servers = {};
88
+ }
89
+ if (strategy === 'overwrite') {
90
+ // For overwrite strategy, replace the entire mcp_servers section
91
+ updatedConfig.mcp_servers = {};
92
+ }
93
+ // Add the ruler servers
94
+ for (const [serverName, serverConfig] of Object.entries(rulerServers)) {
95
+ // Create a properly formatted MCP server entry
96
+ const mcpServer = {
97
+ command: serverConfig.command,
98
+ args: serverConfig.args,
99
+ };
100
+ // Format env as an inline table
101
+ if (serverConfig.env) {
102
+ mcpServer.env = serverConfig.env;
103
+ }
104
+ updatedConfig.mcp_servers[serverName] = mcpServer;
105
+ }
106
+ // Convert to TOML with special handling for env to ensure it's an inline table
107
+ let tomlContent = '';
108
+ // Handle non-mcp_servers sections first
109
+ const configWithoutMcpServers = { ...updatedConfig };
110
+ delete configWithoutMcpServers.mcp_servers;
111
+ if (Object.keys(configWithoutMcpServers).length > 0) {
112
+ tomlContent += (0, toml_1.stringify)(configWithoutMcpServers);
113
+ }
114
+ // Now handle mcp_servers with special formatting for env
115
+ if (updatedConfig.mcp_servers &&
116
+ Object.keys(updatedConfig.mcp_servers).length > 0) {
117
+ for (const [serverName, serverConfigRaw] of Object.entries(updatedConfig.mcp_servers)) {
118
+ const serverConfig = serverConfigRaw;
119
+ tomlContent += `\n[mcp_servers.${serverName}]\n`;
120
+ // Add command
121
+ if (serverConfig.command) {
122
+ tomlContent += `command = "${serverConfig.command}"\n`;
123
+ }
124
+ // Add args if present
125
+ if (serverConfig.args && Array.isArray(serverConfig.args)) {
126
+ const argsStr = JSON.stringify(serverConfig.args)
127
+ .replace(/"/g, '"')
128
+ .replace(/,/g, ', ');
129
+ tomlContent += `args = ${argsStr}\n`;
130
+ }
131
+ // Add env as inline table if present
132
+ if (serverConfig.env && Object.keys(serverConfig.env).length > 0) {
133
+ tomlContent += `env = { `;
134
+ const entries = Object.entries(serverConfig.env);
135
+ for (let i = 0; i < entries.length; i++) {
136
+ const [key, value] = entries[i];
137
+ tomlContent += `${key} = "${value}"`;
138
+ if (i < entries.length - 1) {
139
+ tomlContent += ', ';
140
+ }
141
+ }
142
+ tomlContent += ` }\n`;
143
+ }
144
+ }
145
+ }
146
+ await (0, FileSystemUtils_1.writeGeneratedFile)(configPath, tomlContent);
147
+ }
54
148
  }
55
149
  getDefaultOutputPath(projectRoot) {
56
- return path.join(projectRoot, 'AGENTS.md');
150
+ return {
151
+ instructions: path.join(projectRoot, 'AGENTS.md'),
152
+ config: path.join(projectRoot, '.codex', 'config.toml'),
153
+ };
57
154
  }
58
155
  }
59
156
  exports.CodexCliAgent = CodexCliAgent;
@@ -0,0 +1,66 @@
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.GooseAgent = void 0;
37
+ const path = __importStar(require("path"));
38
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
39
+ /**
40
+ * Goose agent adapter for Block's Goose AI assistant.
41
+ * Propagates rules to .goosehints file.
42
+ */
43
+ class GooseAgent {
44
+ getIdentifier() {
45
+ return 'goose';
46
+ }
47
+ getName() {
48
+ return 'Goose';
49
+ }
50
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
51
+ // Get the output path for .goosehints
52
+ const hintsPath = agentConfig?.outputPathInstructions ??
53
+ this.getDefaultOutputPath(projectRoot);
54
+ // Write rules to .goosehints
55
+ await (0, FileSystemUtils_1.backupFile)(hintsPath);
56
+ await (0, FileSystemUtils_1.writeGeneratedFile)(hintsPath, concatenatedRules);
57
+ }
58
+ getDefaultOutputPath(projectRoot) {
59
+ return path.join(projectRoot, '.goosehints');
60
+ }
61
+ getMcpServerKey() {
62
+ // Goose doesn't support MCP configuration via local config files
63
+ return '';
64
+ }
65
+ }
66
+ exports.GooseAgent = GooseAgent;
@@ -32,15 +32,12 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.loadConfig = loadConfig;
40
37
  const fs_1 = require("fs");
41
38
  const path = __importStar(require("path"));
42
39
  const os = __importStar(require("os"));
43
- const toml_1 = __importDefault(require("@iarna/toml"));
40
+ const TOML = __importStar(require("toml"));
44
41
  const zod_1 = require("zod");
45
42
  const constants_1 = require("../constants");
46
43
  const mcpConfigSchema = zod_1.z
@@ -99,7 +96,7 @@ async function loadConfig(options) {
99
96
  let raw = {};
100
97
  try {
101
98
  const text = await fs_1.promises.readFile(configFile, 'utf8');
102
- raw = text.trim() ? toml_1.default.parse(text) : {};
99
+ raw = text.trim() ? TOML.parse(text) : {};
103
100
  // Validate the configuration with zod
104
101
  const validationResult = rulerConfigSchema.safeParse(raw);
105
102
  if (!validationResult.success) {
package/dist/lib.js CHANGED
@@ -55,6 +55,7 @@ const JunieAgent_1 = require("./agents/JunieAgent");
55
55
  const AugmentCodeAgent_1 = require("./agents/AugmentCodeAgent");
56
56
  const KiloCodeAgent_1 = require("./agents/KiloCodeAgent");
57
57
  const OpenCodeAgent_1 = require("./agents/OpenCodeAgent");
58
+ const GooseAgent_1 = require("./agents/GooseAgent");
58
59
  const merge_1 = require("./mcp/merge");
59
60
  const validate_1 = require("./mcp/validate");
60
61
  const mcp_1 = require("./paths/mcp");
@@ -111,6 +112,7 @@ const agents = [
111
112
  new AugmentCodeAgent_1.AugmentCodeAgent(),
112
113
  new KiloCodeAgent_1.KiloCodeAgent(),
113
114
  new OpenCodeAgent_1.OpenCodeAgent(),
115
+ new GooseAgent_1.GooseAgent(),
114
116
  ];
115
117
  /**
116
118
  * Applies ruler configurations for all supported AI agents.
@@ -32,13 +32,11 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.propagateMcpToOpenHands = propagateMcpToOpenHands;
40
37
  const fs = __importStar(require("fs/promises"));
41
- const toml_1 = __importDefault(require("@iarna/toml"));
38
+ const TOML = __importStar(require("toml"));
39
+ const toml_1 = require("@iarna/toml");
42
40
  const FileSystemUtils_1 = require("../core/FileSystemUtils");
43
41
  const path = __importStar(require("path"));
44
42
  async function propagateMcpToOpenHands(rulerMcpPath, openHandsConfigPath) {
@@ -54,7 +52,7 @@ async function propagateMcpToOpenHands(rulerMcpPath, openHandsConfigPath) {
54
52
  let config = {};
55
53
  try {
56
54
  const tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
57
- config = toml_1.default.parse(tomlContent);
55
+ config = TOML.parse(tomlContent);
58
56
  }
59
57
  catch {
60
58
  // File doesn't exist, we'll create it.
@@ -79,5 +77,5 @@ async function propagateMcpToOpenHands(rulerMcpPath, openHandsConfigPath) {
79
77
  }
80
78
  config.mcp.stdio_servers = Array.from(existingServers.values());
81
79
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openHandsConfigPath));
82
- await fs.writeFile(openHandsConfigPath, toml_1.default.stringify(config));
80
+ await fs.writeFile(openHandsConfigPath, (0, toml_1.stringify)(config));
83
81
  }
package/dist/revert.js CHANGED
@@ -53,6 +53,7 @@ const JunieAgent_1 = require("./agents/JunieAgent");
53
53
  const AugmentCodeAgent_1 = require("./agents/AugmentCodeAgent");
54
54
  const KiloCodeAgent_1 = require("./agents/KiloCodeAgent");
55
55
  const OpenCodeAgent_1 = require("./agents/OpenCodeAgent");
56
+ const GooseAgent_1 = require("./agents/GooseAgent");
56
57
  const mcp_1 = require("./paths/mcp");
57
58
  const constants_1 = require("./constants");
58
59
  const settings_1 = require("./vscode/settings");
@@ -72,6 +73,7 @@ const agents = [
72
73
  new AugmentCodeAgent_1.AugmentCodeAgent(),
73
74
  new KiloCodeAgent_1.KiloCodeAgent(),
74
75
  new OpenCodeAgent_1.OpenCodeAgent(),
76
+ new GooseAgent_1.GooseAgent(),
75
77
  ];
76
78
  /**
77
79
  * Gets all output paths for an agent, taking into account any config overrides.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {
@@ -61,6 +61,7 @@
61
61
  "dependencies": {
62
62
  "@iarna/toml": "^2.2.5",
63
63
  "js-yaml": "^4.1.0",
64
+ "toml": "^3.0.0",
64
65
  "yargs": "^17.7.2",
65
66
  "zod": "^3.25.28"
66
67
  }