@rvry/mcp 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,12 +18,44 @@ The wizard will:
18
18
  2. Auto-detect supported clients on your machine
19
19
  3. Configure them automatically
20
20
 
21
- Supported clients: **Claude Code**, **Claude Desktop**
21
+ Supported clients: **Claude Code**, **Claude Desktop / Co-Work**, **Codex**
22
22
 
23
23
  Restart any running clients after setup, and RVRY is ready.
24
24
 
25
+ ### Codex
26
+
27
+ To configure only Codex, run:
28
+
29
+ ```bash
30
+ npx --yes --package @rvry/mcp@latest rvry-mcp setup --client codex
31
+ ```
32
+
33
+ The installer registers the `rvry` MCP server through the Codex CLI and installs seven namespaced skills under `~/.agents/skills/`:
34
+
35
+ | Skill | Tool | What it does |
36
+ |-----------------------|-----------------|-------------------------------------------------------------------------------|
37
+ | `$rvry` | `rvry` | Reflective answering: observation first, then an answer under its commitments |
38
+ | `$rvry-think` | `think` | Quick structured analysis of a question |
39
+ | `$rvry-think-model` | `think_model` | Apply one of the 18 named mental models |
40
+ | `$rvry-deepthink` | `deepthink` | Deep multi-round analysis of a hard question |
41
+ | `$rvry-problem-solve` | `problem_solve` | Structured decision-making before commitment |
42
+ | `$rvry-challenge` | `challenge` | Adversarial stress-testing with a GO / NO GO verdict |
43
+ | `$rvry-meta` | `meta` | Metacognitive observation on a topic |
44
+
45
+ For example: `$rvry-deepthink Should we split the billing service?`
46
+
47
+ The installer does not add a Codex lifecycle hook, and none is required. The MCP registration and skill files contain the integration. After installing or updating RVRY, restart existing Codex sessions or reload skills so they discover the new files.
48
+
25
49
  ### Manual Configuration
26
50
 
51
+ For Codex, register the MCP server with its CLI:
52
+
53
+ ```bash
54
+ codex mcp add rvry --env RVRY_TOKEN=rvry_your_token -- npx --yes --package @rvry/mcp@latest rvry-mcp
55
+ ```
56
+
57
+ This manual command creates or overwrites the `rvry` registration, but it registers the tool server only. Use the setup command above to install the seven Codex skills as well. Do not print the registration as a verification step: current Codex CLI output includes the literal `RVRY_TOKEN`.
58
+
27
59
  For any MCP client that supports **stdio transport**, add this to your MCP config:
28
60
 
29
61
  ```json
@@ -42,28 +74,28 @@ Get your token at [rvry.ai/dashboard](https://rvry.ai/dashboard) after signing u
42
74
 
43
75
  ## Tools
44
76
 
45
- | Tool | What it does | Plan |
46
- |------|-------------|------|
47
- | **`think`** | Structured analysis for questions that need structure, not a saga. 1–3 analytical rounds; simple questions complete in one. | Free+ |
48
- | **`deepthink`** | Extended multi-round analysis for high-stakes questions. Catches assumptions, tests them, and doesn't let your AI wrap up until it's dealt with what it found. | Free+ |
49
- | **`rvry`** | Reflective answering. A protected observation round surfaces the commitments the answer must honor, then the answer is produced under them — with a ledger showing the fate of every commitment. | Free+ |
50
- | **`think_model`** | Applies one named mental model — 18 available, from Five Whys to Red Team — as a structured session with model-specific stages and synthesis. | Free+ |
51
- | **`problem_solve`** | Structured decision-making: orient, anticipate, generate, evaluate, commit. Includes a six-bias audit and written reversal conditions before the recommendation is final. | Pro+ |
52
- | **`challenge`** | Adversarial stress-testing. Finds weaknesses, tests assumptions, surfaces edge cases, and closes with a GO / CONDITIONAL GO / NO GO verdict. | Pro+ |
53
- | **`meta`** | Sustained metacognitive observation. Examines how your AI is thinking, not just what it's thinking. | Max |
77
+ | Tool | What it does | Plan |
78
+ |---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|
79
+ | **`think`** | Structured analysis for questions that need structure, not a saga. 1–3 analytical rounds; simple questions complete in one. | Free+ |
80
+ | **`deepthink`** | Extended multi-round analysis for high-stakes questions. Catches assumptions, tests them, and doesn't let your AI wrap up until it's dealt with what it found. | Free+ |
81
+ | **`rvry`** | Reflective answering. A protected observation round surfaces the commitments the answer must honor, then the answer is produced under them — with a ledger showing the fate of every commitment. | Free+ |
82
+ | **`think_model`** | Applies one named mental model — 18 available, from Five Whys to Red Team — as a structured session with model-specific stages and synthesis. | Free+ |
83
+ | **`problem_solve`** | Structured decision-making: orient, anticipate, generate, evaluate, commit. Includes a six-bias audit and written reversal conditions before the recommendation is final. | Pro+ |
84
+ | **`challenge`** | Adversarial stress-testing. Finds weaknesses, tests assumptions, surfaces edge cases, and closes with a GO / CONDITIONAL GO / NO GO verdict. | Pro+ |
85
+ | **`meta`** | Sustained metacognitive observation. Examines how your AI is thinking, not just what it's thinking. | Max |
54
86
 
55
87
  ## The 18 Mental Models
56
88
 
57
- `think_model` (and `/think-model` in Claude Code) runs exactly one model as an engine-enforced session — fixed stages, model-specific output structure, no early exits.
89
+ `think_model` (`/think-model` in Claude Code, `$rvry-think-model` in Codex) runs exactly one model as an engine-enforced session — fixed stages, model-specific output structure, no early exits.
58
90
 
59
- | | | |
60
- |---|---|---|
61
- | `five-whys` | `rubber-duck` | `assumption-surfacing` |
62
- | `fermi-estimation` | `abstraction-laddering` | `decomposition` |
63
- | `constraint-relaxation` | `first-principles` | `steelmanning` |
64
- | `opportunity-cost` | `trade-off-matrix` | `time-horizon-shifting` |
65
- | `reversibility` | `pre-mortem` | `inversion` |
66
- | `second-order-effects` | `red-team` | `impact-effort-grid` |
91
+ | | | |
92
+ |-------------------------|-------------------------|-------------------------|
93
+ | `five-whys` | `rubber-duck` | `assumption-surfacing` |
94
+ | `fermi-estimation` | `abstraction-laddering` | `decomposition` |
95
+ | `constraint-relaxation` | `first-principles` | `steelmanning` |
96
+ | `opportunity-cost` | `trade-off-matrix` | `time-horizon-shifting` |
97
+ | `reversibility` | `pre-mortem` | `inversion` |
98
+ | `second-order-effects` | `red-team` | `impact-effort-grid` |
67
99
 
68
100
  ```
69
101
  /think-model pre-mortem we're migrating the billing system to event sourcing next quarter
@@ -77,15 +109,15 @@ A Five Whys session ends with a verified causal chain and a root-cause fix. A Pr
77
109
 
78
110
  When you run setup and select Claude Code, the wizard installs these commands:
79
111
 
80
- | Command | Tool | What it does |
81
- |---------|------|-------------|
82
- | `/think` | think | Quick structured analysis of a question |
83
- | `/deepthink` | deepthink | Deep multi-round analysis of a hard question |
84
- | `/rvry` | rvry | Reflective answering — self-observation first, then the answer |
85
- | `/think-model` | think_model | Apply one named mental model to a problem |
86
- | `/problem-solve` | problem_solve | Structured decision-making, stress-tested before commitment |
87
- | `/challenge` | challenge | Adversarial stress-test with a GO / NO GO verdict |
88
- | `/meta` | meta | Metacognitive observation on a topic |
112
+ | Command | Tool | What it does |
113
+ |------------------|---------------|----------------------------------------------------------------|
114
+ | `/think` | think | Quick structured analysis of a question |
115
+ | `/deepthink` | deepthink | Deep multi-round analysis of a hard question |
116
+ | `/rvry` | rvry | Reflective answering — self-observation first, then the answer |
117
+ | `/think-model` | think_model | Apply one named mental model to a problem |
118
+ | `/problem-solve` | problem_solve | Structured decision-making, stress-tested before commitment |
119
+ | `/challenge` | challenge | Adversarial stress-test with a GO / NO GO verdict |
120
+ | `/meta` | meta | Metacognitive observation on a topic |
89
121
 
90
122
  Usage: type `/deepthink [your question]` in Claude Code. Each command routes to the corresponding RVRY tool and includes rendering instructions so analysis appears as visible markdown rather than hidden in tool calls.
91
123
 
@@ -95,23 +127,39 @@ Usage: type `/deepthink [your question]` in Claude Code. Each command routes to
95
127
 
96
128
  Commands are installed to `~/.claude/commands/`.
97
129
 
130
+ ## Codex Skills
131
+
132
+ When you run setup with `--client codex`, the installer writes the seven skills listed above to `~/.agents/skills/`. Each skill includes Codex metadata declaring the `rvry` MCP dependency and disabling implicit invocation, so RVRY runs only when you explicitly select a namespaced skill. The skills preserve the same multi-round continuation, visible observation/reflection, harvest, and optional recording behavior as the Claude Code workflows.
133
+
134
+ The metadata identifies the already configured stdio dependency; it cannot carry the package arguments or `RVRY_TOKEN` needed to install RVRY by itself. Use the setup command above as the complete install and repair path.
135
+
136
+ Invoke a skill by name followed by the question, for example:
137
+
138
+ ```text
139
+ $rvry-think-model pre-mortem we're migrating billing to event sourcing next quarter
140
+ ```
141
+
142
+ Re-run setup to refresh RVRY-owned skill files and metadata after a package update. Changed RVRY-owned files are backed up beside the original with a `.bak` suffix. New Codex sessions discover them automatically; restart an existing session or reload skills after installation. No Codex lifecycle hook is installed or required.
143
+
98
144
  ## Parameters
99
145
 
100
146
  All tools accept:
101
147
 
102
- | Parameter | Type | Required | Description |
103
- |-----------|------|----------|-------------|
104
- | `input` | string | Yes | Your question (new session) or analysis findings (continuation). |
105
- | `sessionId` | string | No | Session ID for continuing an existing session. |
106
- | `skipScoping` | boolean | No | Skip the scoping questions phase and begin analysis immediately. |
107
- | `userConstraints` | string[] | No | User-stated directives or requirements to track across rounds. |
108
- | `requiresCurrentData` | boolean | No | Set to `true` when the question depends on facts, policies, prices, or events that may have changed. Seeds a verification constraint that persists across all rounds. |
109
- | `record` | boolean | No | When `true` on every call, the local client archives each round's body to `.rvry/<op>/<timestamp>-<slug>/round-N-<label>.md`. Passed from slash commands via the `--record` flag. |
110
- | `harvest` | object | No | Final-call-only. The client includes its synthesis (`title`, `summary`, `keyFindings`, `shimmer`, `openQuestions`, `followUps`, plus operation-specific fields) so it can be persisted to the local session file. |
111
- | `rounds` | array | No | Final-call-only, present only when `--record` was set. Array of `{round, mode, body}` for each analytical round. |
148
+ | Parameter | Type | Required | Description |
149
+ |-----------------------|----------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
150
+ | `input` | string | Yes | Your question (new session) or analysis findings (continuation). |
151
+ | `sessionId` | string | No | Session ID for continuing an existing session. |
152
+ | `skipScoping` | boolean | No | Skip the scoping questions phase and begin analysis immediately. |
153
+ | `userConstraints` | string[] | No | User-stated directives or requirements to track across rounds. |
154
+ | `requiresCurrentData` | boolean | No | Set to `true` when the question depends on facts, policies, prices, or events that may have changed. Seeds a verification constraint that persists across all rounds. |
155
+ | `record` | boolean | No | When `true` on every call, the local client archives each round's body to `.rvry/<op>/<timestamp>-<slug>/round-N-<label>.md`. Passed from slash commands via the `--record` flag. |
156
+ | `harvest` | object | No | Final-call-only. The client includes its synthesis (`title`, `summary`, `keyFindings`, `shimmer`, `openQuestions`, `followUps`, plus operation-specific fields) so it can be persisted to the local session file. |
157
+ | `rounds` | array | No | Final-call-only, present only when `--record` was set. Array of `{round, mode, body}` for each analytical round. |
112
158
 
113
159
  `think_model` additionally requires `modelName` (one of the 18 model ids) on the first call.
114
160
 
161
+ **Required user-input pauses:** A `scoping` response includes `scopingQuestions`; ask the user and resume with the same `sessionId`. During analysis, the client exposes `mustAskUser: true` when the engine requires an answer before it can continue. `blockingUnknowns` is included only under that exact gate and is withheld otherwise. Ask the user, stop the current turn, then continue the same session with an evidence-bearing `RESOLVE Cn:` constraint update for the user's answer; a raw answer alone does not resolve the engine constraint.
162
+
115
163
  **When to use `requiresCurrentData`:** Any question about regulations, pricing, legal rulings, market conditions, API specifications, or current events. The engine will require the model to explicitly classify every cited fact as either verified-current or training-data-dependent before concluding.
116
164
 
117
165
  **SHIMMER display:** Most sessions open with a self-observation round. The model displays `RVRY SHIMMER` with the revery definition before its observation — this is the priming phase, where the model observes its own processing before structured analysis begins. The observation is preserved verbatim in the final harvest.
@@ -128,18 +176,18 @@ The model keeps working not because it was told to think harder, but because it
128
176
 
129
177
  ## Pricing
130
178
 
131
- | Plan | Sessions | Tools | Price |
132
- |------|----------|-------|-------|
133
- | Free | 5/month | `think`, `deepthink`, `rvry`, `think_model` | $0 |
134
- | Pro | 21/month | Free tools + `problem_solve`, `challenge` | $20/mo |
135
- | Max | Unlimited | All tools, including `meta` | $99/mo |
179
+ | Plan | Sessions | Tools | Price |
180
+ |------|-----------|---------------------------------------------|--------|
181
+ | Free | 5/month | `think`, `deepthink`, `rvry`, `think_model` | $0 |
182
+ | Pro | 21/month | Free tools + `problem_solve`, `challenge` | $20/mo |
183
+ | Max | Unlimited | All tools, including `meta` | $99/mo |
136
184
 
137
185
  Sign up at [rvry.ai](https://rvry.ai).
138
186
 
139
187
  ## Requirements
140
188
 
141
189
  - Node.js ≥ 18
142
- - An MCP client with stdio transport support (Claude Code, Claude Desktop, and most others)
190
+ - An MCP client with stdio transport support (Claude Code, Claude Desktop, Codex, and most others)
143
191
 
144
192
  ## License
145
193
 
package/dist/client.d.ts CHANGED
@@ -18,6 +18,10 @@ export interface ThinkResponse {
18
18
  prompt: string;
19
19
  instruction: string;
20
20
  scopingQuestions?: ScopingQuestion[];
21
+ /** True when active blocking unknowns require user input before continuing. */
22
+ mustAskUser?: boolean;
23
+ /** User-facing descriptions of the blocking unknowns that must be resolved. */
24
+ blockingUnknowns?: string[];
21
25
  usage?: {
22
26
  used: number;
23
27
  limit: number;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Codex-native installation helpers for @rvry/mcp.
3
+ *
4
+ * This module deliberately uses argv arrays for every Codex invocation. Tokens
5
+ * are never interpolated into a shell command or written to captured output.
6
+ */
7
+ declare const SKILL_NAME_BY_SOURCE_FILE: {
8
+ readonly 'rvry.md': "rvry";
9
+ readonly 'think.md': "rvry-think";
10
+ readonly 'think-model.md': "rvry-think-model";
11
+ readonly 'deepthink.md': "rvry-deepthink";
12
+ readonly 'problem-solve.md': "rvry-problem-solve";
13
+ readonly 'challenge.md': "rvry-challenge";
14
+ readonly 'meta.md': "rvry-meta";
15
+ };
16
+ type CodexSkillName = (typeof SKILL_NAME_BY_SOURCE_FILE)[keyof typeof SKILL_NAME_BY_SOURCE_FILE];
17
+ export type CodexCommandRunner = (command: string, args: readonly string[]) => string;
18
+ export interface CodexSkillSource {
19
+ file: string;
20
+ content: string;
21
+ description: string;
22
+ label: string;
23
+ argumentHint: string;
24
+ }
25
+ export interface CodexSkillInstallResult {
26
+ name: CodexSkillName;
27
+ status: 'ok' | 'updated' | 'unchanged' | 'error';
28
+ path: string;
29
+ metadataPath?: string;
30
+ backupPath?: string;
31
+ metadataBackupPath?: string;
32
+ error?: string;
33
+ }
34
+ /** Detect whether a usable Codex CLI executable is available. */
35
+ export declare function isCodexCliAvailable(runner?: CodexCommandRunner): boolean;
36
+ /**
37
+ * Register RVRY as a global Codex MCP server and verify the resulting config.
38
+ *
39
+ * The named entry is always re-applied through `codex mcp add`. Current Codex
40
+ * omits placement fields such as `environment_id` from `mcp get --json`, so a
41
+ * projected exact match cannot prove the server will execute locally. The add
42
+ * operation clears hidden overrides; unrelated inspection failures fail closed.
43
+ */
44
+ export declare function configureCodexMcp(token: string, runner?: CodexCommandRunner): 'ok' | 'updated' | 'unchanged' | 'error';
45
+ /** Convert one Claude slash-command definition into a Codex-native SKILL.md. */
46
+ export declare function buildCodexSkill(source: CodexSkillSource): string;
47
+ /** Build Codex product metadata for one explicitly invoked RVRY skill. */
48
+ export declare function buildCodexSkillMetadata(source: Pick<CodexSkillSource, 'file'>): string;
49
+ /** Install the seven namespaced RVRY skills below an injected global skill root. */
50
+ export declare function installCodexSkills(skillsRoot: string, sources: readonly CodexSkillSource[]): CodexSkillInstallResult[];
51
+ export {};
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Codex-native installation helpers for @rvry/mcp.
3
+ *
4
+ * This module deliberately uses argv arrays for every Codex invocation. Tokens
5
+ * are never interpolated into a shell command or written to captured output.
6
+ */
7
+ import { execFileSync } from 'child_process';
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
9
+ import { basename, join } from 'path';
10
+ const CODEX_SERVER_NAME = 'rvry';
11
+ const RVRY_NPX_ARGS = ['--yes', '--package', '@rvry/mcp@latest', 'rvry-mcp'];
12
+ const SKILL_NAME_BY_SOURCE_FILE = {
13
+ 'rvry.md': 'rvry',
14
+ 'think.md': 'rvry-think',
15
+ 'think-model.md': 'rvry-think-model',
16
+ 'deepthink.md': 'rvry-deepthink',
17
+ 'problem-solve.md': 'rvry-problem-solve',
18
+ 'challenge.md': 'rvry-challenge',
19
+ 'meta.md': 'rvry-meta',
20
+ };
21
+ const SKILL_METADATA = {
22
+ rvry: {
23
+ displayName: 'RVRY',
24
+ shortDescription: 'Metacognitive constraint-guided reasoning',
25
+ defaultPrompt: 'Use $rvry to reason through this request with RVRY.',
26
+ },
27
+ 'rvry-think': {
28
+ displayName: 'RVRY Think',
29
+ shortDescription: 'Structured multi-round reasoning',
30
+ defaultPrompt: 'Use $rvry-think to analyze this question systematically.',
31
+ },
32
+ 'rvry-think-model': {
33
+ displayName: 'RVRY Think Model',
34
+ shortDescription: 'Reason with a named mental model',
35
+ defaultPrompt: 'Use $rvry-think-model to apply a named mental model to this problem.',
36
+ },
37
+ 'rvry-deepthink': {
38
+ displayName: 'RVRY Deepthink',
39
+ shortDescription: 'Adaptive pre-mortem failure analysis',
40
+ defaultPrompt: 'Use $rvry-deepthink to deeply analyze this difficult question.',
41
+ },
42
+ 'rvry-problem-solve': {
43
+ displayName: 'RVRY Problem Solve',
44
+ shortDescription: 'Convergent five-step decision pipeline',
45
+ defaultPrompt: 'Use $rvry-problem-solve to reach a stress-tested decision.',
46
+ },
47
+ 'rvry-challenge': {
48
+ displayName: 'RVRY Challenge',
49
+ shortDescription: 'Adversarial proposal stress testing',
50
+ defaultPrompt: 'Use $rvry-challenge to stress-test this proposal before commitment.',
51
+ },
52
+ 'rvry-meta': {
53
+ displayName: 'RVRY Meta',
54
+ shortDescription: 'Sustained metacognitive observation',
55
+ defaultPrompt: 'Use $rvry-meta to observe the reasoning process around this topic.',
56
+ },
57
+ };
58
+ const defaultCommandRunner = (command, args) => execFileSync(command, [...args], {
59
+ encoding: 'utf8',
60
+ stdio: ['ignore', 'pipe', 'pipe'],
61
+ });
62
+ function errorText(error) {
63
+ if (!(error instanceof Error)) {
64
+ return String(error);
65
+ }
66
+ const processError = error;
67
+ const render = (value) => {
68
+ if (typeof value === 'string')
69
+ return value;
70
+ if (value instanceof Uint8Array)
71
+ return Buffer.from(value).toString('utf8');
72
+ return '';
73
+ };
74
+ return [error.message, render(processError.stdout), render(processError.stderr)]
75
+ .filter(Boolean)
76
+ .join('\n');
77
+ }
78
+ function isMissingServerError(error) {
79
+ return /No MCP server named\s+['"]?rvry['"]?\s+found/i.test(errorText(error));
80
+ }
81
+ function parseMcpConfig(output) {
82
+ try {
83
+ const parsed = JSON.parse(output);
84
+ return typeof parsed === 'object' && parsed !== null ? parsed : null;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ function stringRecord(value) {
91
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
92
+ return null;
93
+ const entries = Object.entries(value);
94
+ if (entries.some(([, item]) => typeof item !== 'string'))
95
+ return null;
96
+ return Object.fromEntries(entries);
97
+ }
98
+ function isExpectedMcpConfig(config, token) {
99
+ const transport = config.transport;
100
+ const args = transport?.args;
101
+ const env = stringRecord(transport?.env);
102
+ return (config.name === CODEX_SERVER_NAME &&
103
+ config.enabled === true &&
104
+ transport?.type === 'stdio' &&
105
+ transport.command === 'npx' &&
106
+ Array.isArray(args) &&
107
+ args.length === RVRY_NPX_ARGS.length &&
108
+ args.every((arg, index) => arg === RVRY_NPX_ARGS[index]) &&
109
+ env?.RVRY_TOKEN === token &&
110
+ Object.keys(env).length === 1 &&
111
+ Array.isArray(transport.env_vars) &&
112
+ transport.env_vars.length === 0 &&
113
+ transport.cwd == null &&
114
+ config.enabled_tools == null &&
115
+ config.disabled_tools == null &&
116
+ config.startup_timeout_sec == null &&
117
+ config.tool_timeout_sec == null);
118
+ }
119
+ function readCodexMcpConfig(runner) {
120
+ const output = runner('codex', ['mcp', 'get', CODEX_SERVER_NAME, '--json']);
121
+ const config = parseMcpConfig(output);
122
+ if (!config) {
123
+ throw new Error('Codex returned invalid MCP configuration JSON.');
124
+ }
125
+ return config;
126
+ }
127
+ /** Detect whether a usable Codex CLI executable is available. */
128
+ export function isCodexCliAvailable(runner = defaultCommandRunner) {
129
+ try {
130
+ runner('codex', ['--version']);
131
+ return true;
132
+ }
133
+ catch {
134
+ return false;
135
+ }
136
+ }
137
+ /**
138
+ * Register RVRY as a global Codex MCP server and verify the resulting config.
139
+ *
140
+ * The named entry is always re-applied through `codex mcp add`. Current Codex
141
+ * omits placement fields such as `environment_id` from `mcp get --json`, so a
142
+ * projected exact match cannot prove the server will execute locally. The add
143
+ * operation clears hidden overrides; unrelated inspection failures fail closed.
144
+ */
145
+ export function configureCodexMcp(token, runner = defaultCommandRunner) {
146
+ let existing = null;
147
+ let existed = false;
148
+ try {
149
+ existing = readCodexMcpConfig(runner);
150
+ existed = true;
151
+ }
152
+ catch (error) {
153
+ if (!isMissingServerError(error))
154
+ return 'error';
155
+ }
156
+ const projectedConfigWasExpected = existing
157
+ ? isExpectedMcpConfig(existing, token)
158
+ : false;
159
+ try {
160
+ runner('codex', [
161
+ 'mcp',
162
+ 'add',
163
+ CODEX_SERVER_NAME,
164
+ '--env',
165
+ `RVRY_TOKEN=${token}`,
166
+ '--',
167
+ 'npx',
168
+ ...RVRY_NPX_ARGS,
169
+ ]);
170
+ const verified = readCodexMcpConfig(runner);
171
+ if (!verified || !isExpectedMcpConfig(verified, token)) {
172
+ return 'error';
173
+ }
174
+ if (!existed)
175
+ return 'ok';
176
+ return projectedConfigWasExpected ? 'unchanged' : 'updated';
177
+ }
178
+ catch {
179
+ return 'error';
180
+ }
181
+ }
182
+ function resolveSkillName(sourceFile) {
183
+ const name = SKILL_NAME_BY_SOURCE_FILE[basename(sourceFile)];
184
+ if (!name) {
185
+ throw new Error(`Unsupported RVRY Codex skill source: ${sourceFile}`);
186
+ }
187
+ return name;
188
+ }
189
+ function replaceSlashReferences(content) {
190
+ const replacements = [
191
+ ['/problem-solve', '$rvry-problem-solve'],
192
+ ['/think-model', '$rvry-think-model'],
193
+ ['/deepthink', '$rvry-deepthink'],
194
+ ['/challenge', '$rvry-challenge'],
195
+ ['/think', '$rvry-think'],
196
+ ['/meta', '$rvry-meta'],
197
+ ['/rvry', '$rvry'],
198
+ ];
199
+ let result = content;
200
+ for (const [slashName, skillName] of replacements) {
201
+ const escaped = slashName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
202
+ const pattern = new RegExp(`(^|[^A-Za-z0-9:/])${escaped}(?=$|[^A-Za-z0-9_-])`, 'gm');
203
+ result = result.replace(pattern, (_match, prefix) => `${prefix}${skillName}`);
204
+ }
205
+ return result;
206
+ }
207
+ function replaceClaudeQuestionSchema(content) {
208
+ return content.replace(/(^3\. For each BLOCKING uncertainty not answered in the proposal, )ask via AskUserQuestion:\r?\n(?: {3}-[^\r\n]*(?:\r?\n|$)){4}/m, '$1ask the user one concise blocking question in plain text, end the turn, and wait for the answer before continuing.\n');
209
+ }
210
+ function adaptSkillBody(content) {
211
+ return replaceSlashReferences(replaceClaudeQuestionSchema(content))
212
+ .replaceAll('$ARGUMENTS', 'SKILL_INPUT')
213
+ .replaceAll('ask via AskUserQuestion:', 'ask the user directly in one concise plain-text question, then end the turn and wait:')
214
+ .replaceAll('AskUserQuestion', 'a concise plain-text question');
215
+ }
216
+ function codexPreamble(skillName, argumentHint) {
217
+ const challengeScopingNote = skillName === 'rvry-challenge'
218
+ ? '- For the first `challenge` call, pass `skipScoping: true` after completing Phase 0.5 or when `--auto` skips that scan. Phase 0.5 already handles scoping, so do not ask the engine to repeat it.\n'
219
+ : '';
220
+ return `# Codex RVRY adapter contract
221
+
222
+ Invoke this workflow as \`$${skillName} ${argumentHint}\`. SKILL_INPUT is the user's request associated with \`$${skillName}\`. Remove the skill mention itself before treating the remainder as the operation input.
223
+
224
+ - This workflow requires the MCP server named \`rvry\`. Call the requested tool from that server. If the server or tool is unavailable, stop and tell the user to run RVRY setup; never imitate or replace the RVRY workflow yourself.
225
+ - When the request depends on current facts, policies, prices, regulations, software behavior, or events, pass \`requiresCurrentData: true\` on the first call and every continuation call.
226
+ - Preserve the returned \`sessionId\` and use that same session for every continuation. Never start a replacement session to avoid an unresolved round.
227
+ - If a response has status \`scoping\` and includes \`scopingQuestions\`, ask the user those questions, end the turn, and wait. Resume the same \`sessionId\` after the user answers.
228
+ - If a response has \`mustAskUser: true\` or non-empty \`blockingUnknowns\`, record each matching active \`BLOCKING_UNKNOWN\` constraint ID from the returned prompt, ask the blocking question in plain text, end the turn, and wait.
229
+ - After the user answers a blocking unknown, do not send the raw answer as a continuation. Resume the same \`sessionId\` with an analytical input containing \`Constraint Updates\`, then \`RESOLVE Cn: The user confirmed that <blocking unknown text>: <user answer>.\`, then \`End Constraint Updates\`. Use the actual matching constraint ID, unknown text, and answer; resolve only answered unknowns and never invent evidence.
230
+ ${challengeScopingNote}- Continue the tool loop for \`shimmer\`, \`orient\`, and \`active\` responses. A turn may exit the workflow only after the tool returns status \`complete\`, except for a required user-input pause or a surfaced setup/error condition.
231
+ `;
232
+ }
233
+ /** Convert one Claude slash-command definition into a Codex-native SKILL.md. */
234
+ export function buildCodexSkill(source) {
235
+ const skillName = resolveSkillName(source.file);
236
+ const taskTrigger = source.description.replace(/\s+/g, ' ').trim().replace(/[.!?]+$/, '');
237
+ const normalizedTrigger = `${taskTrigger.charAt(0).toLowerCase()}${taskTrigger.slice(1)}`;
238
+ const description = `Run RVRY's ${normalizedTrigger} workflow. Invoke explicitly as $${skillName}.`;
239
+ const body = adaptSkillBody(source.content).trim();
240
+ return [
241
+ '---',
242
+ `name: ${skillName}`,
243
+ `description: ${JSON.stringify(description)}`,
244
+ '---',
245
+ '',
246
+ codexPreamble(skillName, source.argumentHint).trim(),
247
+ '',
248
+ '## RVRY operation workflow',
249
+ '',
250
+ body,
251
+ '',
252
+ ].join('\n');
253
+ }
254
+ /** Build Codex product metadata for one explicitly invoked RVRY skill. */
255
+ export function buildCodexSkillMetadata(source) {
256
+ const skillName = resolveSkillName(source.file);
257
+ const metadata = SKILL_METADATA[skillName];
258
+ return [
259
+ 'interface:',
260
+ ` display_name: ${JSON.stringify(metadata.displayName)}`,
261
+ ` short_description: ${JSON.stringify(metadata.shortDescription)}`,
262
+ ` default_prompt: ${JSON.stringify(metadata.defaultPrompt)}`,
263
+ 'dependencies:',
264
+ ' tools:',
265
+ ' - type: "mcp"',
266
+ ' value: "rvry"',
267
+ ' description: "RVRY reasoning session server"',
268
+ ' transport: "stdio"',
269
+ ' command: "npx"',
270
+ 'policy:',
271
+ ' allow_implicit_invocation: false',
272
+ '',
273
+ ].join('\n');
274
+ }
275
+ /** Install the seven namespaced RVRY skills below an injected global skill root. */
276
+ export function installCodexSkills(skillsRoot, sources) {
277
+ return sources.map((source) => {
278
+ let name;
279
+ try {
280
+ name = resolveSkillName(source.file);
281
+ }
282
+ catch (error) {
283
+ return {
284
+ name: 'rvry',
285
+ status: 'error',
286
+ path: skillsRoot,
287
+ error: error instanceof Error ? error.message : String(error),
288
+ };
289
+ }
290
+ const directory = join(skillsRoot, name);
291
+ const path = join(directory, 'SKILL.md');
292
+ const backupPath = `${path}.bak`;
293
+ const metadataDirectory = join(directory, 'agents');
294
+ const metadataPath = join(metadataDirectory, 'openai.yaml');
295
+ const metadataBackupPath = `${metadataPath}.bak`;
296
+ try {
297
+ const content = buildCodexSkill(source);
298
+ const metadata = buildCodexSkillMetadata(source);
299
+ mkdirSync(directory, { recursive: true });
300
+ mkdirSync(metadataDirectory, { recursive: true });
301
+ const skillExists = existsSync(path);
302
+ const metadataExists = existsSync(metadataPath);
303
+ const previous = skillExists ? readFileSync(path, 'utf8') : undefined;
304
+ const previousMetadata = metadataExists ? readFileSync(metadataPath, 'utf8') : undefined;
305
+ const skillChanged = previous !== content;
306
+ const metadataChanged = previousMetadata !== metadata;
307
+ if (!skillChanged && !metadataChanged) {
308
+ return { name, status: 'unchanged', path, metadataPath };
309
+ }
310
+ let writtenBackupPath;
311
+ let writtenMetadataBackupPath;
312
+ if (skillChanged) {
313
+ if (previous !== undefined) {
314
+ writeFileSync(backupPath, previous, 'utf8');
315
+ writtenBackupPath = backupPath;
316
+ }
317
+ writeFileSync(path, content, 'utf8');
318
+ }
319
+ if (metadataChanged) {
320
+ if (previousMetadata !== undefined) {
321
+ writeFileSync(metadataBackupPath, previousMetadata, 'utf8');
322
+ writtenMetadataBackupPath = metadataBackupPath;
323
+ }
324
+ writeFileSync(metadataPath, metadata, 'utf8');
325
+ }
326
+ const status = !skillExists && !metadataExists ? 'ok' : 'updated';
327
+ return {
328
+ name,
329
+ status,
330
+ path,
331
+ metadataPath,
332
+ backupPath: writtenBackupPath,
333
+ metadataBackupPath: writtenMetadataBackupPath,
334
+ };
335
+ }
336
+ catch (error) {
337
+ return {
338
+ name,
339
+ status: 'error',
340
+ path,
341
+ metadataPath,
342
+ error: error instanceof Error ? error.message : String(error),
343
+ };
344
+ }
345
+ });
346
+ }
package/dist/index.d.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  * Exposes 7 tools (think, think_model, rvry, deepthink, problem_solve,
9
9
  * challenge, meta) + RVRY_* backward-compat aliases + MCP Prompts.
10
10
  *
11
- * Also supports `npx --yes --package @rvry/mcp@latest rvry-mcp setup` to install Claude Code commands.
11
+ * Also supports `npx --yes --package @rvry/mcp@latest rvry-mcp setup` to
12
+ * configure supported clients and install Claude Code commands or Codex skills.
12
13
  */
13
14
  import { type Tool } from '@modelcontextprotocol/sdk/types.js';
14
15
  import { type RvryTool, type ThinkResponse } from './client.js';
package/dist/index.js CHANGED
@@ -8,7 +8,8 @@
8
8
  * Exposes 7 tools (think, think_model, rvry, deepthink, problem_solve,
9
9
  * challenge, meta) + RVRY_* backward-compat aliases + MCP Prompts.
10
10
  *
11
- * Also supports `npx --yes --package @rvry/mcp@latest rvry-mcp setup` to install Claude Code commands.
11
+ * Also supports `npx --yes --package @rvry/mcp@latest rvry-mcp setup` to
12
+ * configure supported clients and install Claude Code commands or Codex skills.
12
13
  */
13
14
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
14
15
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -673,12 +674,18 @@ const TOOL_DEFS = [
673
674
  * Strip response fields based on status for context-efficient MCP responses.
674
675
  *
675
676
  * `toolName` is the public MCP tool name of the call in scope. The 'shimmer'
676
- * status (an engine wire value) passthrough applies to the tools whose
677
- * observation round is a visible product surface rvry, deepthink, and
678
- * problem_solve. The remaining ops keep dropping prompt/instruction on their
679
- * priming round.
677
+ * status (an engine wire value) passthrough applies to every public workflow
678
+ * that executes the engine's observation task. Meta starts directly in its own
679
+ * active observation pass and therefore never reaches this status.
680
680
  */
681
- const SHIMMER_VISIBLE_TOOLS = new Set(['rvry', 'deepthink', 'problem_solve']);
681
+ const SHIMMER_WORKFLOW_TOOLS = new Set([
682
+ 'rvry',
683
+ 'deepthink',
684
+ 'problem_solve',
685
+ 'think',
686
+ 'challenge',
687
+ 'think_model',
688
+ ]);
682
689
  function stripResponse(result, question, toolName) {
683
690
  const stripped = {
684
691
  sessionId: result.sessionId,
@@ -693,13 +700,21 @@ function stripResponse(result, question, toolName) {
693
700
  stripped.prompt = result.prompt;
694
701
  stripped.instruction = result.instruction;
695
702
  }
696
- if (result.status === 'shimmer' && toolName !== undefined && SHIMMER_VISIBLE_TOOLS.has(toolName)) {
703
+ if (result.status === 'shimmer' && toolName !== undefined && SHIMMER_WORKFLOW_TOOLS.has(toolName)) {
697
704
  stripped.prompt = result.prompt;
698
705
  stripped.instruction = result.instruction;
699
706
  }
700
707
  if (result.status === 'orient') {
701
708
  stripped.instruction = result.instruction;
702
709
  }
710
+ // Blocking-unknown signals are safe, user-facing protocol fields. Only
711
+ // expose the unknowns when the engine explicitly requires user input.
712
+ if (result.mustAskUser === true) {
713
+ stripped.mustAskUser = true;
714
+ if (result.blockingUnknowns !== undefined) {
715
+ stripped.blockingUnknowns = result.blockingUnknowns;
716
+ }
717
+ }
703
718
  if (result.status === 'complete' && result.harvest) {
704
719
  const { constraintTrace, ...harvestSafe } = result.harvest;
705
720
  stripped.harvest = harvestSafe;
package/dist/setup.d.ts CHANGED
@@ -2,26 +2,42 @@
2
2
  * @rvry/mcp setup wizard
3
3
  *
4
4
  * Interactive setup: device auth flow (browser-based) or manual token paste.
5
- * Auto-detects Claude Code CLI and Claude Desktop, configures both.
5
+ * Auto-detects Claude Code, Claude Desktop, and Codex, then configures the
6
+ * selected clients without touching unrelated client state.
6
7
  *
7
8
  * Usage: npx --yes --package @rvry/mcp@latest rvry-mcp setup
8
9
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --token <value>
9
10
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client code (Claude Code only)
10
11
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client desktop (Claude Desktop only)
12
+ * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client codex (Codex only)
11
13
  */
14
+ import { type CodexSkillSource } from './codex-adapter.js';
15
+ interface ClientDef {
16
+ /** Display name shown in picker */
17
+ name: string;
18
+ /** Short id for --client flag */
19
+ id: string;
20
+ /** Pre-select when detected? Defaults to true. */
21
+ defaultSelected?: boolean;
22
+ /** Detect if the client is installed */
23
+ detect: () => boolean;
24
+ /** Configure the client. Returns a status string for the summary. */
25
+ configure: (token: string) => 'ok' | 'updated' | 'unchanged' | 'error';
26
+ /** Shown when the client is not installed */
27
+ notInstalledHint: string;
28
+ }
29
+ /** All supported clients. Add new clients here. */
30
+ declare const CLIENT_REGISTRY: ClientDef[];
31
+ declare function isSupportedClientId(value: unknown): value is string;
32
+ /** Return every detected client index for an explicit --client selection. */
33
+ declare function selectDetectedClientIndices(availability: readonly boolean[]): number[];
12
34
  /** The /think-model shim: one named mental model per session. */
13
35
  declare const THINK_MODEL_SHIM_CONTENT: string;
14
36
  /** The /think shim. Carries the Difficulty Gate + mode routing. */
15
37
  declare const THINK_SHIM_CONTENT: string;
16
38
  /** The /rvry shim: 3-round reflective answering with client-owned harvest. */
17
39
  declare const RVRY_SHIM_CONTENT = "Call the rvry MCP tool with your question to start a reflective answering session.\n\nQuestion: $ARGUMENTS\n\nARGUMENT PARSING:\n1. If $ARGUMENTS contains the literal flag `--record`, remove it from the question text and REMEMBER that record mode is ON for this session.\n\nROUND 1 \u2014 OBSERVATION (status \"shimmer\"):\n1. The response's prompt field is your observation task. Work through it.\n2. Show the user your FULL observation prose, beginning with the header \"## RVRY | Observation\" and the revery definition, exactly as the instruction field describes. Do NOT show a brief status line.\n3. The commitments that surfaced (FORWARD:/FORBIDDEN:/QUESTION: lines, or the single line NOTHING) are machine-readable bookkeeping: append them to the end of the observation text you send back to the tool, but do NOT show them in the visible display.\n4. REMEMBER your observation text verbatim \u2014 you will pass it as harvest.shimmer on the final call, and you will re-display its prose in the final output.\n5. Call the rvry tool again with your observation text (prose + commitment lines) as input and the sessionId.\n\nROUNDS 2-3 \u2014 ANSWER AND RESOLUTION (status \"active\"):\n1. Read the prompt field \u2014 this is your task. DO NOT display it to the user.\n2. Round 2 is the answer itself, working under the commitments you surfaced. Round 3 (if it occurs) resolves what the answer left unaddressed \u2014 engage each remaining commitment specifically or defer it with a reason.\n3. Show the user your answer as concise formatted markdown.\n4. Call the tool again with your full text as the input parameter, including the sessionId.\n - If record mode is ON, set `record: true` on every call and REMEMBER your round body as {round, mode, body} for the final call.\n\nFINAL CALL (the call whose response will have status \"complete\"):\nWhen you make the call that completes the session, also construct a `harvest` object on that SAME call:\n- title: a 1-4 word title capturing the topic\n- summary: 3-6 sentences of synthesis, leading with substance\n- keyFindings: 3-7 substantive bullet findings\n- shimmer: the full round-1 observation text you remembered, verbatim\n- constraintLedger: one line per surfaced commitment with its fate \u2014 addressed (and how), deferred (and why), or still open\n- openQuestions: [optional] remaining uncertainties\n- followUps: genuine next-question leads; omit the section if there are none\n\nIf record mode was ON, also include `rounds: [{round, mode, body}, ...]` on that final call with the bodies you remembered per round.\n\nFINAL VISIBLE OUTPUT:\nWhen you present the completed answer to the user, begin your visible output with the header \"## RVRY | Reflection\" followed by the round-1 observation prose you remembered (without the machine-readable commitment lines), then present the answer. The reflection is part of the product \u2014 the user sees the observation that shaped the answer first, then the answer itself.\n\nDO NOT display: the prompt text you received (the round-1 observation prose IS shown in full \u2014 at observation time and again in the final output), the FORWARD/FORBIDDEN/QUESTION commitment lines, constraint tables, quality scores, internal tracking data, or round numbers to the user.";
18
- declare const SLASH_COMMANDS: {
19
- file: string;
20
- content: string;
21
- label: string;
22
- description: string;
23
- argumentHint: string;
24
- }[];
40
+ declare const SLASH_COMMANDS: CodexSkillSource[];
25
41
  /**
26
42
  * Install RVRY slash commands into the given directory.
27
43
  * Only called when Claude Code is the selected client.
@@ -35,5 +51,6 @@ declare const SLASH_COMMANDS: {
35
51
  * touch the real directory.
36
52
  */
37
53
  declare function installSlashCommands(commandsDir: string): string[];
38
- export { SLASH_COMMANDS, installSlashCommands, THINK_SHIM_CONTENT, RVRY_SHIM_CONTENT, THINK_MODEL_SHIM_CONTENT };
54
+ export { CLIENT_REGISTRY, SLASH_COMMANDS, installSlashCommands, THINK_SHIM_CONTENT, RVRY_SHIM_CONTENT, THINK_MODEL_SHIM_CONTENT, };
39
55
  export declare function runSetup(): Promise<void>;
56
+ export { isSupportedClientId, selectDetectedClientIndices };
package/dist/setup.js CHANGED
@@ -2,12 +2,14 @@
2
2
  * @rvry/mcp setup wizard
3
3
  *
4
4
  * Interactive setup: device auth flow (browser-based) or manual token paste.
5
- * Auto-detects Claude Code CLI and Claude Desktop, configures both.
5
+ * Auto-detects Claude Code, Claude Desktop, and Codex, then configures the
6
+ * selected clients without touching unrelated client state.
6
7
  *
7
8
  * Usage: npx --yes --package @rvry/mcp@latest rvry-mcp setup
8
9
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --token <value>
9
10
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client code (Claude Code only)
10
11
  * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client desktop (Claude Desktop only)
12
+ * npx --yes --package @rvry/mcp@latest rvry-mcp setup --client codex (Codex only)
11
13
  */
12
14
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
13
15
  import { join, dirname } from 'path';
@@ -17,6 +19,7 @@ import { execSync } from 'child_process';
17
19
  import { platform } from 'os';
18
20
  import { createRequire } from 'module';
19
21
  import { fetch } from './fetch.js';
22
+ import { configureCodexMcp, installCodexSkills, isCodexCliAvailable, } from './codex-adapter.js';
20
23
  const require = createRequire(import.meta.url);
21
24
  const PKG_VERSION = require('../package.json').version;
22
25
  const ENGINE_URL = 'https://engine.rvry.ai';
@@ -234,7 +237,21 @@ const CLIENT_REGISTRY = [
234
237
  configure: (token) => configureDesktop(token),
235
238
  notInstalledHint: 'Not installed',
236
239
  },
240
+ {
241
+ name: 'Codex',
242
+ id: 'codex',
243
+ detect: isCodexCliAvailable,
244
+ configure: configureCodexMcp,
245
+ notInstalledHint: 'CLI not found (https://developers.openai.com/codex/cli)',
246
+ },
237
247
  ];
248
+ function isSupportedClientId(value) {
249
+ return typeof value === 'string' && CLIENT_REGISTRY.some((client) => client.id === value);
250
+ }
251
+ /** Return every detected client index for an explicit --client selection. */
252
+ function selectDetectedClientIndices(availability) {
253
+ return availability.flatMap((available, index) => available ? [index] : []);
254
+ }
238
255
  /**
239
256
  * Interactive multi-select picker with arrow keys + space + enter.
240
257
  * Returns indices of selected items.
@@ -627,11 +644,12 @@ const RVRY_MODEL_IDS_LINE = 'five-whys, trade-off-matrix, rubber-duck, pre-morte
627
644
  'opportunity-cost, time-horizon-shifting, reversibility, inversion, second-order-effects, ' +
628
645
  'red-team, impact-effort-grid';
629
646
  const RVRY_MODEL_SELECTION = `MODEL SELECTION (BEFORE the first tool call):
630
- 1. The FIRST whitespace-separated token of $ARGUMENTS (after removing --record) is the mental model name. Everything after it is the problem.
631
- 2. Valid models: ${RVRY_MODEL_IDS_LINE}.
632
- 3. Map close variants to the canonical id: "5-whys"/"five whys" -> five-whys; "premortem" -> pre-mortem; "steelman"/"steel-man" -> steelmanning; "tradeoff-matrix"/"trade-offs" -> trade-off-matrix; "first principles" -> first-principles; "redteam"/"red team" -> red-team; "impact-effort"/"impact effort" -> impact-effort-grid; "second order"/"second-order" -> second-order-effects; "fermi" -> fermi-estimation; "rubber duck"/"rubberduck" -> rubber-duck; "abstraction ladder" -> abstraction-laddering; "opportunity cost" -> opportunity-cost; "time horizon"/"time-horizons" -> time-horizon-shifting; "invert" -> inversion; "decompose" -> decomposition; "assumptions" -> assumption-surfacing; "relax constraints" -> constraint-relaxation; "reversible"/"one-way door" -> reversibility.
633
- 4. If the first token does not match any model even loosely, ASK THE USER which model to use (list the valid ids) before making any tool call.
634
- 5. On the FIRST tool call only, pass modelName: <canonical id>. Do NOT pass modelName on continuation calls.`;
647
+ 1. Remove the literal --record flag, trim the remaining input, then match the LONGEST leading canonical id or known alias prefix (case-insensitive). Prefer a multiword or hyphenated match over a shorter first-token match. The matched prefix is the mental model name; everything after that matched prefix is the problem.
648
+ 2. Examples: "five whys why do deploys fail?" selects five-whys with problem "why do deploys fail?"; "first principles redesign auth" selects first-principles; "red team the rollout plan" selects red-team; "rubber duck this race condition" selects rubber-duck.
649
+ 3. Valid models: ${RVRY_MODEL_IDS_LINE}.
650
+ 4. Map close variants to the canonical id: "5-whys"/"five whys" -> five-whys; "premortem" -> pre-mortem; "steelman"/"steel-man" -> steelmanning; "tradeoff-matrix"/"trade-offs" -> trade-off-matrix; "first principles" -> first-principles; "redteam"/"red team" -> red-team; "impact-effort"/"impact effort" -> impact-effort-grid; "second order"/"second-order" -> second-order-effects; "fermi" -> fermi-estimation; "rubber duck"/"rubberduck" -> rubber-duck; "abstraction ladder" -> abstraction-laddering; "opportunity cost" -> opportunity-cost; "time horizon"/"time-horizons" -> time-horizon-shifting; "invert" -> inversion; "decompose" -> decomposition; "assumptions" -> assumption-surfacing; "relax constraints" -> constraint-relaxation; "reversible"/"one-way door" -> reversibility.
651
+ 5. If no leading canonical id or alias prefix matches, ASK THE USER which model to use (list the valid ids) before making any tool call.
652
+ 6. On the FIRST tool call only, pass modelName: <canonical id>. Do NOT pass modelName on continuation calls.`;
635
653
  /** The /think-model shim: one named mental model per session. */
636
654
  const THINK_MODEL_SHIM_CONTENT = buildShim({
637
655
  toolName: 'think_model',
@@ -824,7 +842,7 @@ function installSlashCommands(commandsDir) {
824
842
  return installed;
825
843
  }
826
844
  // Exported for tests (content + install behavior).
827
- export { SLASH_COMMANDS, installSlashCommands, THINK_SHIM_CONTENT, RVRY_SHIM_CONTENT, THINK_MODEL_SHIM_CONTENT };
845
+ export { CLIENT_REGISTRY, SLASH_COMMANDS, installSlashCommands, THINK_SHIM_CONTENT, RVRY_SHIM_CONTENT, THINK_MODEL_SHIM_CONTENT, };
828
846
  // ── Main setup flow ────────────────────────────────────────────────
829
847
  const BANNER = `
830
848
  8888888b. 888 888 8888888b. Y88b d88P
@@ -843,8 +861,14 @@ export async function runSetup() {
843
861
  // Parse flags
844
862
  const tokenFlagIndex = process.argv.indexOf('--token');
845
863
  const clientFlagIndex = process.argv.indexOf('--client');
846
- const clientFilter = clientFlagIndex !== -1 ? process.argv[clientFlagIndex + 1] : null;
864
+ const clientFilterValue = clientFlagIndex !== -1 ? process.argv[clientFlagIndex + 1] : null;
847
865
  const acceptTerms = process.argv.includes('--accept-terms');
866
+ if (clientFlagIndex !== -1 && !isSupportedClientId(clientFilterValue)) {
867
+ console.error(`Invalid --client value. Expected one of: ${CLIENT_REGISTRY.map((client) => client.id).join(', ')}.`);
868
+ process.exitCode = 1;
869
+ return;
870
+ }
871
+ const clientFilter = clientFilterValue;
848
872
  // ── Step 1: Terms of Service ────────────────────────────────────
849
873
  console.log('[1/5] Terms of Service');
850
874
  console.log('');
@@ -938,7 +962,12 @@ export async function runSetup() {
938
962
  console.log('');
939
963
  // ── Step 3: Configure clients ───────────────────────────────────
940
964
  console.log('[4/5] Select apps to add RVRY MCP configuration');
941
- console.log(' Use \x1b[1m↑↓\x1b[0m to navigate, \x1b[1mspace\x1b[0m to toggle, \x1b[1ma\x1b[0m to toggle all, \x1b[1menter\x1b[0m to confirm');
965
+ if (clientFilter) {
966
+ console.log(` Using explicit --client ${clientFilter} selection`);
967
+ }
968
+ else {
969
+ console.log(' Use \x1b[1m↑↓\x1b[0m to navigate, \x1b[1mspace\x1b[0m to toggle, \x1b[1ma\x1b[0m to toggle all, \x1b[1menter\x1b[0m to confirm');
970
+ }
942
971
  console.log('');
943
972
  const pickerItems = detected.map((d) => ({
944
973
  label: d.client.name,
@@ -946,16 +975,21 @@ export async function runSetup() {
946
975
  available: d.available,
947
976
  hint: d.available ? undefined : d.client.notInstalledHint,
948
977
  }));
949
- const selectedIndices = await multiSelect(pickerItems);
978
+ // An explicit --client already expresses the user's selection. Skip the
979
+ // interactive picker so --token + --accept-terms can run unattended.
980
+ const selectedIndices = clientFilter
981
+ ? selectDetectedClientIndices(detected.map((item) => item.available))
982
+ : await multiSelect(pickerItems);
950
983
  const configuredClients = [];
951
984
  let anyConfigured = false;
985
+ let clientConfigurationFailed = false;
952
986
  console.log('');
953
987
  for (const idx of selectedIndices) {
954
988
  const { client } = detected[idx];
955
989
  const result = client.configure(token);
956
990
  const statusMap = {
957
991
  ok: 'Configured',
958
- updated: 'Updated token',
992
+ updated: 'Updated configuration',
959
993
  unchanged: 'Already configured',
960
994
  error: 'Failed',
961
995
  };
@@ -963,8 +997,15 @@ export async function runSetup() {
963
997
  const icon = result === 'error' ? '✗' : '✓';
964
998
  console.log(` ${icon} ${client.name}: ${status}`);
965
999
  configuredClients.push({ name: client.name, status });
966
- if (result !== 'error')
1000
+ if (result === 'error') {
1001
+ clientConfigurationFailed = true;
1002
+ }
1003
+ else {
967
1004
  anyConfigured = true;
1005
+ }
1006
+ }
1007
+ if ((clientFilter && selectedIndices.length === 0) || clientConfigurationFailed) {
1008
+ process.exitCode = 1;
968
1009
  }
969
1010
  // Install slash commands if Claude Code was configured
970
1011
  const claudeCodeConfigured = selectedIndices.some((idx) => detected[idx].client.id === "code") && configuredClients.some((c) => c.name === "Claude Code" && c.status !== "Failed");
@@ -980,6 +1021,36 @@ export async function runSetup() {
980
1021
  }
981
1022
  }
982
1023
  }
1024
+ // Install namespaced Codex skills if Codex was configured. Namespacing keeps
1025
+ // RVRY from clobbering unrelated global skills such as `$deepthink`.
1026
+ const codexConfigured = selectedIndices.some((idx) => detected[idx].client.id === 'codex') && configuredClients.some((c) => c.name === 'Codex' && c.status !== 'Failed');
1027
+ let installedCodexSkills = [];
1028
+ if (codexConfigured) {
1029
+ console.log('');
1030
+ console.log(' Installing Codex skills...');
1031
+ installedCodexSkills = installCodexSkills(join(homedir(), '.agents', 'skills'), SLASH_COMMANDS);
1032
+ if (installedCodexSkills.length > 0) {
1033
+ console.log(` Processed ${installedCodexSkills.length} skill(s):`);
1034
+ for (const skill of installedCodexSkills) {
1035
+ const verb = skill.status === 'unchanged'
1036
+ ? 'Ready'
1037
+ : skill.status === 'updated'
1038
+ ? 'Updated'
1039
+ : skill.status === 'error'
1040
+ ? 'Failed'
1041
+ : 'Installed';
1042
+ const detail = skill.status === 'error' && skill.error ? ` — ${skill.error}` : '';
1043
+ console.log(` ${verb}: $${skill.name}${detail}`);
1044
+ }
1045
+ }
1046
+ }
1047
+ const codexSkillErrors = installedCodexSkills.filter((skill) => skill.status === 'error');
1048
+ const codexSkillsReady = codexConfigured
1049
+ && installedCodexSkills.length === SLASH_COMMANDS.length
1050
+ && codexSkillErrors.length === 0;
1051
+ if (codexConfigured && !codexSkillsReady) {
1052
+ process.exitCode = 1;
1053
+ }
983
1054
  if (selectedIndices.length === 0) {
984
1055
  console.log(' No clients selected.');
985
1056
  }
@@ -989,10 +1060,13 @@ export async function runSetup() {
989
1060
  console.log(' Manual configuration:');
990
1061
  console.log('');
991
1062
  console.log(' Option A — Claude Code (if you install it later):');
992
- console.log(` claude mcp add -e RVRY_TOKEN="${token}" -s user rvry -- npx --yes --package @rvry/mcp@latest rvry-mcp`);
1063
+ console.log(' claude mcp add -e RVRY_TOKEN="rvry_your_token" -s user rvry -- npx --yes --package @rvry/mcp@latest rvry-mcp');
1064
+ console.log('');
1065
+ console.log(' Option B — Codex (if you install it later):');
1066
+ console.log(' codex mcp add rvry --env RVRY_TOKEN="rvry_your_token" -- npx --yes --package @rvry/mcp@latest rvry-mcp');
993
1067
  console.log('');
994
- console.log(' Option B — Claude Desktop JSON config:');
995
- const manualConfig = { mcpServers: { RVRY: RVRY_SERVER_ENTRY(token) } };
1068
+ console.log(' Option C — Claude Desktop JSON config:');
1069
+ const manualConfig = { mcpServers: { RVRY: RVRY_SERVER_ENTRY('rvry_your_token') } };
996
1070
  console.log('');
997
1071
  for (const line of JSON.stringify(manualConfig, null, 2).split('\n')) {
998
1072
  console.log(` ${line}`);
@@ -1006,7 +1080,8 @@ export async function runSetup() {
1006
1080
  // ── Summary ─────────────────────────────────────────────────────
1007
1081
  console.log('─'.repeat(50));
1008
1082
  console.log('');
1009
- console.log('RVRY Setup Complete');
1083
+ const setupFailed = process.exitCode !== undefined && process.exitCode !== 0;
1084
+ console.log(setupFailed ? 'RVRY Setup Incomplete' : 'RVRY Setup Complete');
1010
1085
  console.log('');
1011
1086
  console.log(` Token: ${maskToken(token)}${usage ? ` (${usage.tier})` : ''}`);
1012
1087
  for (const c of configuredClients) {
@@ -1018,12 +1093,23 @@ export async function runSetup() {
1018
1093
  const configuredNames = new Set(configuredClients.map((c) => c.name));
1019
1094
  const hasDesktopStyle = configuredNames.has('Claude Desktop / Claude Co-Work');
1020
1095
  const hasCodeStyle = configuredNames.has('Claude Code');
1096
+ const hasCodexStyle = codexSkillsReady;
1021
1097
  console.log('Next steps:');
1022
1098
  let step = 1;
1023
1099
  if (hasDesktopStyle) {
1024
1100
  console.log(` ${step}. Restart Claude Desktop for the new MCP server to load`);
1025
1101
  step++;
1026
1102
  }
1103
+ if (hasCodexStyle) {
1104
+ console.log(` ${step}. Restart Codex or reload skills so the new adapter is discovered`);
1105
+ step++;
1106
+ console.log(` ${step}. Try: $rvry-deepthink [your question]`);
1107
+ step++;
1108
+ }
1109
+ if (codexConfigured && !codexSkillsReady) {
1110
+ console.log(` ${step}. Fix the Codex skill installation errors above, then rerun setup`);
1111
+ step++;
1112
+ }
1027
1113
  if (hasCodeStyle || hasDesktopStyle) {
1028
1114
  if (installedCommands.length > 0) {
1029
1115
  console.log(` ${step}. Try a slash command: /deepthink [your question]`);
@@ -1038,3 +1124,4 @@ export async function runSetup() {
1038
1124
  console.log(' 2. Then try: "Use deepthink to analyze..."');
1039
1125
  }
1040
1126
  }
1127
+ export { isSupportedClientId, selectDetectedClientIndices };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rvry/mcp",
3
- "version": "1.3.1",
4
- "description": "Reasoning depth enforcement for LLMs. Seven structured analysis tools and 18 mental-model templates for Claude Code, Claude Desktop, and any MCP client.",
3
+ "version": "1.4.0",
4
+ "description": "Reasoning depth enforcement for LLMs. Seven structured analysis tools and 18 mental-model templates for Claude Code, Claude Desktop, Codex, and any MCP client.",
5
5
  "homepage": "https://rvry.ai",
6
6
  "type": "module",
7
7
  "bin": {
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "scripts": {
19
19
  "build": "tsc -p tsconfig.build.json && chmod 755 dist/index.js",
20
- "prepack": "npm run build",
20
+ "prepack": "bun run build",
21
21
  "typecheck": "tsc --noEmit"
22
22
  },
23
23
  "dependencies": {
@@ -38,6 +38,8 @@
38
38
  "critical-thinking",
39
39
  "claude",
40
40
  "claude-code",
41
+ "codex",
42
+ "openai-codex",
41
43
  "llm",
42
44
  "ai"
43
45
  ],