projecta-rrr 1.23.1 → 1.23.4
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 +17 -0
- package/bin/hosted-setup.js +20 -3
- package/bin/install.js +3 -1
- package/docs/hosted-search-setup.md +6 -0
- package/package.json +2 -1
- package/rrr/lib/codex-agent-gen.js +52 -5
- package/scripts/codex-rrr.sh +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to RRR will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.23.4] - 2026-04-20
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Codex hosted MCP launcher** — installer now writes a Codex-specific bearer env file and registers `bearer_token_env_var` in `~/.codex/config.toml` so the hosted RRR setup works with the new `scripts/codex-rrr.sh` launcher.
|
|
11
|
+
- **Codex launch helper** — added `npm run codex:rrr` and docs for starting Codex with the hosted bearer loaded from `~/.config/projecta-rrr/rrr-hosted.env`.
|
|
12
|
+
|
|
13
|
+
## [1.23.3] - 2026-04-20
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Codex agent config self-healing** — installer now rewrites `~/.codex/agents/*.toml` from the repo source and prunes stale generated files so legacy Codex-only fields cannot survive across installs.
|
|
17
|
+
|
|
18
|
+
## [1.23.2] - 2026-04-20
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Codex agent TOML `developer_instructions`** — Codex 0.122.0 requires this field; generator now extracts the first 3000 chars of each agent's markdown body as the system prompt using TOML literal multi-line strings (no escaping needed).
|
|
22
|
+
- **MCP 406 for Codex rmcp clients** — Codex's Rust rmcp client does not send `Accept: application/json, text/event-stream`; server now normalizes the header before the SDK validates it so the connection succeeds.
|
|
23
|
+
|
|
7
24
|
## [1.23.1] - 2026-04-20
|
|
8
25
|
|
|
9
26
|
### Fixed
|
package/bin/hosted-setup.js
CHANGED
|
@@ -31,6 +31,8 @@ const HOSTED_URL = 'https://rrr-search-hosted.fly.dev/mcp';
|
|
|
31
31
|
const GH_APP_INSTALL_URL = 'https://github.com/apps/rrr-search/installations/new';
|
|
32
32
|
const MCP_NAME = 'rrr-search-hosted';
|
|
33
33
|
const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
|
|
34
|
+
const CODEX_BEARER_ENV_VAR = 'RRR_HOSTED_BEARER';
|
|
35
|
+
const CODEX_ENV_FILE = path.join(os.homedir(), '.config', 'projecta-rrr', 'rrr-hosted.env');
|
|
34
36
|
|
|
35
37
|
// ──────────────────────────────── UI ──────────────────────────────────────
|
|
36
38
|
|
|
@@ -136,7 +138,7 @@ function registerCodexMcp (bearer) {
|
|
|
136
138
|
content = content.replace(sectionRegex, '').trimEnd();
|
|
137
139
|
|
|
138
140
|
// Append new registration
|
|
139
|
-
const section = `\n\n[mcp_servers.${MCP_NAME}]\nurl = "${HOSTED_URL}"\
|
|
141
|
+
const section = `\n\n[mcp_servers.${MCP_NAME}]\nurl = "${HOSTED_URL}"\nbearer_token_env_var = "${CODEX_BEARER_ENV_VAR}"\n`;
|
|
140
142
|
content = content + section;
|
|
141
143
|
|
|
142
144
|
// Ensure ~/.codex directory exists
|
|
@@ -144,6 +146,15 @@ function registerCodexMcp (bearer) {
|
|
|
144
146
|
fs.writeFileSync(CODEX_CONFIG_PATH, content, 'utf8');
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
function writeCodexBearerEnv (bearer) {
|
|
150
|
+
fs.mkdirSync(path.dirname(CODEX_ENV_FILE), { recursive: true });
|
|
151
|
+
fs.writeFileSync(
|
|
152
|
+
CODEX_ENV_FILE,
|
|
153
|
+
`export ${CODEX_BEARER_ENV_VAR}=${JSON.stringify(bearer)}\n`,
|
|
154
|
+
'utf8'
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
147
158
|
// ──────────────────────────────── flow ────────────────────────────────────
|
|
148
159
|
|
|
149
160
|
async function main () {
|
|
@@ -177,7 +188,7 @@ async function main () {
|
|
|
177
188
|
step(2, TOTAL_STEPS, 'Team bearer token');
|
|
178
189
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
179
190
|
|
|
180
|
-
let bearer = args.bearer || process.env.RRR_HOSTED_MCP_TOKEN || null;
|
|
191
|
+
let bearer = args.bearer || process.env.RRR_HOSTED_BEARER || process.env.RRR_HOSTED_MCP_TOKEN || null;
|
|
181
192
|
if (!bearer && !args.yes) {
|
|
182
193
|
info('You need a bearer token of the form: rrr_<prefix>_<32chars>');
|
|
183
194
|
info('New team? Ask your rrr-search admin to run:');
|
|
@@ -192,6 +203,9 @@ async function main () {
|
|
|
192
203
|
err('Bearer format invalid. Expected rrr_<prefix>_<32chars>.');
|
|
193
204
|
process.exit(1);
|
|
194
205
|
}
|
|
206
|
+
if (!args.bearer && process.env.RRR_HOSTED_MCP_TOKEN && !process.env.RRR_HOSTED_BEARER) {
|
|
207
|
+
warn('RRR_HOSTED_MCP_TOKEN is deprecated for this launcher; prefer RRR_HOSTED_BEARER.');
|
|
208
|
+
}
|
|
195
209
|
ok(`Bearer captured (team prefix: ${bearer.split('_')[1]})`);
|
|
196
210
|
} else {
|
|
197
211
|
warn('No bearer — skipping MCP registration. You can run this wizard again after getting one.');
|
|
@@ -294,8 +308,11 @@ async function main () {
|
|
|
294
308
|
}
|
|
295
309
|
try {
|
|
296
310
|
registerCodexMcp(bearer);
|
|
311
|
+
writeCodexBearerEnv(bearer);
|
|
297
312
|
ok(`Registered ${MCP_NAME} in ~/.codex/config.toml`);
|
|
298
|
-
info(
|
|
313
|
+
info(`Codex will read the bearer from ${CODEX_BEARER_ENV_VAR}`);
|
|
314
|
+
info(`Codex launcher env file written to ${CODEX_ENV_FILE}`);
|
|
315
|
+
info(`Run scripts/codex-rrr.sh to launch Codex with RRR ready`);
|
|
299
316
|
} catch (e) {
|
|
300
317
|
warn(`Could not write ~/.codex/config.toml: ${e.message}`);
|
|
301
318
|
info('You can manually add the MCP server entry — see docs/hosted-search-setup.md');
|
package/bin/install.js
CHANGED
|
@@ -2219,7 +2219,9 @@ function install(isGlobal) {
|
|
|
2219
2219
|
verbose: false
|
|
2220
2220
|
});
|
|
2221
2221
|
if (agentResult.errors.length === 0) {
|
|
2222
|
-
|
|
2222
|
+
const removedCount = agentResult.removed.length;
|
|
2223
|
+
const removedText = removedCount > 0 ? `, pruned ${removedCount} stale configs` : '';
|
|
2224
|
+
console.log(` ${green}✓${reset} Installed ${agentResult.written.length} Codex agent configs to ~/.codex/agents/${removedText}`);
|
|
2223
2225
|
} else {
|
|
2224
2226
|
console.log(` ${yellow}⚠${reset} Installed ${agentResult.written.length} agent configs (${agentResult.errors.length} errors)`);
|
|
2225
2227
|
agentResult.errors.slice(0, 3).forEach(e => {
|
|
@@ -43,6 +43,12 @@ npx projecta-rrr@1.21.0 --enable-hosted
|
|
|
43
43
|
# The installer registered `rrr-search-hosted` pointed at https://rrr-search-hosted.fly.dev/mcp.
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
For Codex, use `scripts/codex-rrr.sh` to launch the CLI with the hosted bearer loaded from
|
|
47
|
+
`~/.config/projecta-rrr/rrr-hosted.env`. Codex accepts the same `$rrr-*` trigger text, but it
|
|
48
|
+
does not provide Claude-style live autocomplete for those triggers while you type.
|
|
49
|
+
From this repo, `npm run codex:rrr -- <args>` is the shortest way to start Codex with the RRR
|
|
50
|
+
environment loaded.
|
|
51
|
+
|
|
46
52
|
**What happens under the hood:**
|
|
47
53
|
|
|
48
54
|
1. The installer writes `rrr-search-hosted` into `~/.claude/mcp.registry.json` with `disabled:false`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projecta-rrr",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.4",
|
|
4
4
|
"description": "A meta-prompting, context engineering and spec-driven development system for Claude Code by Projecta.ai",
|
|
5
5
|
"bin": {
|
|
6
6
|
"projecta-rrr": "bin/install.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"test": "node --test tests/*.test.js && jest",
|
|
17
17
|
"test:node": "node --test tests/*.test.js",
|
|
18
18
|
"test:jest": "jest",
|
|
19
|
+
"codex:rrr": "bash scripts/codex-rrr.sh",
|
|
19
20
|
"test:browser": "vitest --browser",
|
|
20
21
|
"test:ui": "vitest --ui",
|
|
21
22
|
"mcp:setup": "bash scripts/mcp-setup.sh",
|
|
@@ -20,6 +20,8 @@ const TIER_MAP = {
|
|
|
20
20
|
inherit: { tier: 'ADAPTIVE', model: 'gpt-4o', fallback_model: 'o1' },
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
const LEGACY_TOP_LEVEL_FIELDS = ['tier', 'fallback_model'];
|
|
24
|
+
|
|
23
25
|
/**
|
|
24
26
|
* Parse the YAML frontmatter from a markdown string.
|
|
25
27
|
*
|
|
@@ -75,6 +77,21 @@ function extractField(lines, key) {
|
|
|
75
77
|
return null;
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Guard against old Codex-only metadata leaking into generated TOML.
|
|
82
|
+
*
|
|
83
|
+
* Current Codex CLI agent files accept only name, description, model, and
|
|
84
|
+
* developer_instructions. Legacy tier/fallback_model keys cause Codex to
|
|
85
|
+
* reject the entire agent definition, so we fail fast if they ever reappear.
|
|
86
|
+
*/
|
|
87
|
+
function assertNoLegacyTopLevelFields(toml, filename) {
|
|
88
|
+
for (const field of LEGACY_TOP_LEVEL_FIELDS) {
|
|
89
|
+
if (new RegExp(`^\\s*${field}\\s*=`, 'm').test(toml)) {
|
|
90
|
+
throw new Error(`Generated ${filename} contains unsupported field "${field}"`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
/**
|
|
79
96
|
* Convert a single RRR agent markdown file (Claude Code format) into
|
|
80
97
|
* a Codex-compatible TOML agent config string.
|
|
@@ -84,7 +101,7 @@ function extractField(lines, key) {
|
|
|
84
101
|
* @returns {string} Generated TOML string for ~/.codex/agents/<basename>.toml
|
|
85
102
|
*/
|
|
86
103
|
function generateCodexAgentToml(sourceMarkdown, filename) {
|
|
87
|
-
const { frontmatterLines } = parseFrontmatter(sourceMarkdown);
|
|
104
|
+
const { frontmatterLines, body } = parseFrontmatter(sourceMarkdown);
|
|
88
105
|
|
|
89
106
|
// Extract fields from frontmatter
|
|
90
107
|
const nameField = extractField(frontmatterLines, 'name');
|
|
@@ -97,7 +114,12 @@ function generateCodexAgentToml(sourceMarkdown, filename) {
|
|
|
97
114
|
// Look up tier mapping; default to STANDARD (sonnet) if model not found
|
|
98
115
|
const tierEntry = TIER_MAP[modelField] || TIER_MAP.sonnet;
|
|
99
116
|
|
|
100
|
-
//
|
|
117
|
+
// developer_instructions: body of the markdown file (the system prompt).
|
|
118
|
+
// Truncated to 3000 chars; uses TOML literal multi-line strings (''') which
|
|
119
|
+
// need zero escaping — safe as long as the body contains no ''' sequence.
|
|
120
|
+
const instructions = body.trim().slice(0, 3000);
|
|
121
|
+
|
|
122
|
+
// Build TOML — only fields Codex CLI accepts
|
|
101
123
|
const lines = [
|
|
102
124
|
'# Generated by projecta-rrr — do not edit manually',
|
|
103
125
|
`# Source: agents/${filename}`,
|
|
@@ -105,9 +127,14 @@ function generateCodexAgentToml(sourceMarkdown, filename) {
|
|
|
105
127
|
`name = "${agentName}"`,
|
|
106
128
|
`description = "${descriptionField || agentName}"`,
|
|
107
129
|
`model = "${tierEntry.model}"`,
|
|
130
|
+
`developer_instructions = '''`,
|
|
131
|
+
instructions,
|
|
132
|
+
`'''`,
|
|
108
133
|
];
|
|
109
134
|
|
|
110
|
-
|
|
135
|
+
const toml = lines.join('\n') + '\n';
|
|
136
|
+
assertNoLegacyTopLevelFields(toml, filename);
|
|
137
|
+
return toml;
|
|
111
138
|
}
|
|
112
139
|
|
|
113
140
|
/**
|
|
@@ -122,7 +149,7 @@ function generateCodexAgentToml(sourceMarkdown, filename) {
|
|
|
122
149
|
* @param {string} options.targetDir - Absolute path to target directory (e.g. ~/.codex/agents/)
|
|
123
150
|
* @param {boolean} [options.dryRun=false] - If true, compute paths but skip writing
|
|
124
151
|
* @param {boolean} [options.verbose=false] - If true, log each file written
|
|
125
|
-
* @returns {{ written: string[], skipped: string[], errors: Array<{file: string, error: string}> }}
|
|
152
|
+
* @returns {{ written: string[], skipped: string[], removed: string[], errors: Array<{file: string, error: string}> }}
|
|
126
153
|
*/
|
|
127
154
|
function installCodexAgents(options) {
|
|
128
155
|
const {
|
|
@@ -134,6 +161,7 @@ function installCodexAgents(options) {
|
|
|
134
161
|
|
|
135
162
|
const written = [];
|
|
136
163
|
const skipped = [];
|
|
164
|
+
const removed = [];
|
|
137
165
|
const errors = [];
|
|
138
166
|
|
|
139
167
|
// Ensure target directory exists (no-op if already exists)
|
|
@@ -181,7 +209,26 @@ function installCodexAgents(options) {
|
|
|
181
209
|
}
|
|
182
210
|
}
|
|
183
211
|
|
|
184
|
-
|
|
212
|
+
if (!dryRun) {
|
|
213
|
+
const managedFiles = new Set(written);
|
|
214
|
+
try {
|
|
215
|
+
const existingTargets = fs.readdirSync(targetDir);
|
|
216
|
+
for (const targetEntry of existingTargets) {
|
|
217
|
+
if (!targetEntry.endsWith('.toml')) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (managedFiles.has(targetEntry)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
fs.unlinkSync(path.join(targetDir, targetEntry));
|
|
224
|
+
removed.push(targetEntry);
|
|
225
|
+
}
|
|
226
|
+
} catch (err) {
|
|
227
|
+
errors.push({ file: targetDir, error: `Cannot prune targetDir: ${err.message}` });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { written, skipped, removed, errors };
|
|
185
232
|
}
|
|
186
233
|
|
|
187
234
|
module.exports = {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
ENV_FILE="${RRR_HOSTED_ENV_FILE:-$HOME/.config/projecta-rrr/rrr-hosted.env}"
|
|
6
|
+
|
|
7
|
+
if [[ -f "$ENV_FILE" ]]; then
|
|
8
|
+
# shellcheck disable=SC1090
|
|
9
|
+
source "$ENV_FILE"
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
if [[ -z "${RRR_HOSTED_BEARER:-}" ]]; then
|
|
13
|
+
cat >&2 <<'EOF'
|
|
14
|
+
RRR_HOSTED_BEARER is unset.
|
|
15
|
+
Set it in ~/.config/projecta-rrr/rrr-hosted.env or export it before launching Codex.
|
|
16
|
+
EOF
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
exec codex "$@"
|