odd-studio 3.2.3 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +42 -10
- package/bin/commands/init.js +29 -9
- package/bin/commands/uninstall.js +22 -4
- package/bin/commands/upgrade.js +12 -3
- package/bin/odd-studio.js +1 -1
- package/codex-plugin/.codex-plugin/plugin.json +39 -0
- package/codex-plugin/.mcp.json +15 -0
- package/codex-plugin/README.md +3 -0
- package/codex-plugin/hooks.json +110 -0
- package/package.json +4 -2
- package/scripts/assets.js +8 -0
- package/scripts/detect-agent.js +13 -5
- package/scripts/install-codex-commands.js +56 -0
- package/scripts/scaffold-project.js +5 -3
- package/scripts/setup-codex-plugin.js +117 -0
- package/scripts/setup-mcp.js +27 -4
- package/skill/SKILL.md +4 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odd-studio",
|
|
3
3
|
"description": "Outcome-Driven Development — a planning and build harness for domain experts building serious software with AI. Installs the /odd skill, safety hooks, and project scaffolding into Claude Code.",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.3.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ODD Studio"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
A planning and build harness for domain experts who are building serious software with AI — and want to understand exactly what they're doing and why.
|
|
6
6
|
|
|
7
|
-
Works with **Claude Code
|
|
7
|
+
Works with **Claude Code**, **OpenCode**, and **Codex**.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -38,21 +38,38 @@ npx odd-studio init my-project --agent opencode
|
|
|
38
38
|
cd my-project
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
### For
|
|
41
|
+
### For Codex
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx odd-studio init my-project --agent codex
|
|
45
|
+
cd my-project
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
In Codex, ODD Studio is installed as a project-local plugin with visible plugin commands. After restarting Codex, type `/odd`.
|
|
49
|
+
Once inside `/odd`, the same starred flow works there too, including commands like `*plan`, `*build`, and `*status`. You can also invoke the direct Codex commands such as `/odd-build`.
|
|
50
|
+
|
|
51
|
+
### For Claude Code and OpenCode on the same machine
|
|
42
52
|
|
|
43
53
|
```bash
|
|
44
54
|
npx odd-studio init my-project --agent both
|
|
45
55
|
cd my-project
|
|
46
56
|
```
|
|
47
57
|
|
|
58
|
+
### For every supported agent on the same machine
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx odd-studio init my-project --agent all
|
|
62
|
+
cd my-project
|
|
63
|
+
```
|
|
64
|
+
|
|
48
65
|
Everything is installed into your project folder. Nothing is written to your home directory or installed globally.
|
|
49
66
|
|
|
50
67
|
That single command:
|
|
51
68
|
|
|
52
69
|
- Detects your installed AI coding agent (or uses `--agent` to specify)
|
|
53
70
|
- Scaffolds your project structure (`docs/`, `.odd/`, instruction files)
|
|
54
|
-
- Installs the
|
|
55
|
-
- Configures odd-flow MCP server for cross-session memory
|
|
71
|
+
- Installs the ODD harness into the matching project-local agent surface (`.claude/`, `.opencode/`, or `plugins/odd-studio/`)
|
|
72
|
+
- Configures odd-flow MCP server for cross-session memory in the matching agent config
|
|
56
73
|
- Optionally installs Checkpoint security scanning (you'll be asked)
|
|
57
74
|
- Initialises git with an initial commit
|
|
58
75
|
|
|
@@ -69,8 +86,9 @@ That single command:
|
|
|
69
86
|
|-------|-------------------|---------------|
|
|
70
87
|
| **Claude Code** | Project-local skills (`.claude/skills/`), hooks (`.claude/settings.local.json`), odd-flow MCP (`.mcp.json`) | Claude (Opus, Sonnet, Haiku) |
|
|
71
88
|
| **OpenCode** | Project-local commands (`.opencode/commands/`), JS plugin (`.opencode/plugins/`), odd-flow MCP (`opencode.json`) | Any provider — Ollama (Qwen3-Coder, MiniMax M2.7, DeepSeek), OpenAI, Anthropic, Google, Groq, and 75+ more |
|
|
89
|
+
| **Codex** | Project-local plugin (`plugins/odd-studio/`), local marketplace registration (`.agents/plugins/marketplace.json`), plugin MCP (`plugins/odd-studio/.mcp.json`) | Codex |
|
|
72
90
|
|
|
73
|
-
|
|
91
|
+
All supported agents get the same methodology, the same safety enforcement, and the same odd-flow-powered cross-session memory. The only difference is the delivery mechanism.
|
|
74
92
|
|
|
75
93
|
---
|
|
76
94
|
|
|
@@ -138,6 +156,8 @@ ODD Studio installs safety gates that run automatically throughout your build. T
|
|
|
138
156
|
|
|
139
157
|
**Claude Code:** Implemented as shell hooks registered in `.claude/settings.local.json` (project-local).
|
|
140
158
|
**OpenCode:** Implemented as a JS plugin (`odd-studio-plugin.js`) in `.opencode/plugins/` (project-local).
|
|
159
|
+
**Codex:** Implemented as a project-local plugin in `plugins/odd-studio/` with `hooks.json` and plugin-local skills.
|
|
160
|
+
It also installs Codex plugin commands so `/odd` is directly discoverable in the composer.
|
|
141
161
|
|
|
142
162
|
---
|
|
143
163
|
|
|
@@ -150,7 +170,7 @@ When you're ready to build, ODD Studio initialises a odd-flow swarm — a team o
|
|
|
150
170
|
- **UI agent** — implements the frontend using shadcn/ui, Tailwind CSS v4, and Framer Motion, against WCAG 2.1 AA accessibility standards
|
|
151
171
|
- **QA agent** — runs your verification steps and reports failures in your language, not technical error messages
|
|
152
172
|
|
|
153
|
-
odd-flow MCP is configured automatically in your project
|
|
173
|
+
odd-flow MCP is configured automatically in your project-local agent config — `.mcp.json` for Claude Code, `opencode.json` for OpenCode, and `plugins/odd-studio/.mcp.json` for Codex. Every agent knows the full project state, regardless of which sessions they were spawned in.
|
|
154
174
|
|
|
155
175
|
---
|
|
156
176
|
|
|
@@ -206,8 +226,8 @@ After `npx odd-studio init my-project`, your project looks like this:
|
|
|
206
226
|
```
|
|
207
227
|
my-project/
|
|
208
228
|
├── CLAUDE.md ← ODD build rules (Claude Code)
|
|
209
|
-
├── AGENTS.md ← ODD build rules (OpenCode)
|
|
210
|
-
├── .mcp.json ← odd-flow MCP config (
|
|
229
|
+
├── AGENTS.md ← ODD build rules (OpenCode and Codex)
|
|
230
|
+
├── .mcp.json ← odd-flow MCP config (Claude Code)
|
|
211
231
|
├── .odd/
|
|
212
232
|
│ └── state.json ← Project state (updated automatically)
|
|
213
233
|
├── .claude/ ← Claude Code config (project-local)
|
|
@@ -219,6 +239,14 @@ my-project/
|
|
|
219
239
|
│ ├── commands/ ← /odd command files
|
|
220
240
|
│ ├── plugins/odd-studio-plugin.js ← Safety gate plugin
|
|
221
241
|
│ └── odd/ ← Skill knowledge base
|
|
242
|
+
├── .agents/plugins/marketplace.json ← Codex local marketplace registration
|
|
243
|
+
├── plugins/odd-studio/ ← Codex project-local plugin
|
|
244
|
+
│ ├── .codex-plugin/plugin.json ← Codex plugin manifest
|
|
245
|
+
│ ├── commands/odd.md ← Codex /odd command entrypoint
|
|
246
|
+
│ ├── skills/ ← ODD Studio skills for Codex
|
|
247
|
+
│ ├── hooks/odd-studio.sh ← Safety gate hook script
|
|
248
|
+
│ ├── hooks.json ← Codex hook registration
|
|
249
|
+
│ └── .mcp.json ← odd-flow MCP config for Codex
|
|
222
250
|
└── docs/
|
|
223
251
|
├── plan.md ← Master Implementation Plan
|
|
224
252
|
├── contract-map.md ← Contracts and dependency graph
|
|
@@ -229,13 +257,13 @@ my-project/
|
|
|
229
257
|
└── ui/ ← UI specifications per outcome
|
|
230
258
|
```
|
|
231
259
|
|
|
232
|
-
Only the config directories for your target agent are generated (`.claude/`, `.opencode/`, or
|
|
260
|
+
Only the config directories for your target agent are generated (`.claude/`, `.opencode/`, `plugins/odd-studio/`, or a supported combination).
|
|
233
261
|
|
|
234
262
|
---
|
|
235
263
|
|
|
236
264
|
## Slash commands
|
|
237
265
|
|
|
238
|
-
These are the top-level commands you can invoke in
|
|
266
|
+
These are the top-level commands you can invoke in Claude Code, OpenCode, or Codex:
|
|
239
267
|
|
|
240
268
|
| Command | What it does |
|
|
241
269
|
|---------|-------------|
|
|
@@ -273,10 +301,13 @@ Once inside `/odd`, you can also use these sub-commands:
|
|
|
273
301
|
```bash
|
|
274
302
|
npx odd-studio init [name] # Scaffold a new ODD project
|
|
275
303
|
npx odd-studio init --agent opencode # Scaffold for OpenCode (local models)
|
|
304
|
+
npx odd-studio init --agent codex # Scaffold for Codex
|
|
276
305
|
npx odd-studio init --agent both # Scaffold for both agents
|
|
306
|
+
npx odd-studio init --agent all # Scaffold for Claude Code, OpenCode, and Codex
|
|
277
307
|
npx odd-studio init --skip-git # Scaffold without git init or the initial commit
|
|
278
308
|
npx odd-studio status # Show current planning state (run from project dir)
|
|
279
309
|
npx odd-studio upgrade # Update skills, hooks, and odd-flow config (run from project dir)
|
|
310
|
+
npx odd-studio uninstall # Remove ODD Studio agent assets from the project
|
|
280
311
|
npx odd-studio export # Instructions for exporting the Session Brief
|
|
281
312
|
```
|
|
282
313
|
|
|
@@ -322,6 +353,7 @@ ODD Studio implements the methodology from **The ODD Way to Build Software with
|
|
|
322
353
|
- One of:
|
|
323
354
|
- **Claude Code** installed (`npm install -g @anthropic-ai/claude-code`)
|
|
324
355
|
- **OpenCode** installed (`npm install -g opencode` or `brew install opencode`)
|
|
356
|
+
- **Codex** installed
|
|
325
357
|
- odd-flow MCP is configured automatically by `npx odd-studio init`
|
|
326
358
|
|
|
327
359
|
### For local model builds (OpenCode)
|
package/bin/commands/init.js
CHANGED
|
@@ -10,7 +10,7 @@ export function registerInit(program, deps) {
|
|
|
10
10
|
program
|
|
11
11
|
.command('init [project-name]')
|
|
12
12
|
.description('Scaffold a new ODD project and install /odd into your AI coding agent')
|
|
13
|
-
.option('--agent <type>', 'Target agent: claude-code, opencode, both, or auto (default: auto)', 'auto')
|
|
13
|
+
.option('--agent <type>', 'Target agent: claude-code, opencode, codex, both, all, or auto (default: auto)', 'auto')
|
|
14
14
|
.option('--skip-skill', 'Skip installing the /odd skills/commands globally (advanced)')
|
|
15
15
|
.option('--skip-hooks', 'Skip installing safety hooks/plugin (not recommended)')
|
|
16
16
|
.option('--skip-mcp', 'Skip configuring the odd-flow MCP server')
|
|
@@ -23,11 +23,12 @@ export function registerInit(program, deps) {
|
|
|
23
23
|
const { default: scaffoldProject } = await import('../../scripts/scaffold-project.js');
|
|
24
24
|
|
|
25
25
|
const agent = detectAgent(options.agent);
|
|
26
|
-
const isClaude = agent === 'claude-code' || agent === 'both';
|
|
27
|
-
const isOpenCode = agent === 'opencode' || agent === 'both';
|
|
26
|
+
const isClaude = agent === 'claude-code' || agent === 'both' || agent === 'all';
|
|
27
|
+
const isOpenCode = agent === 'opencode' || agent === 'both' || agent === 'all';
|
|
28
|
+
const isCodex = agent === 'codex' || agent === 'all';
|
|
28
29
|
const targetDir = projectName ? path.resolve(process.cwd(), projectName) : process.cwd();
|
|
29
30
|
const resolvedName = projectName || path.basename(process.cwd());
|
|
30
|
-
const agentLabel = agent
|
|
31
|
+
const agentLabel = getAgentLabel(agent);
|
|
31
32
|
const totalSteps = 5;
|
|
32
33
|
|
|
33
34
|
console.log(chalk.bold(` Setting up: ${chalk.cyan(resolvedName)}`) + chalk.dim(` (${agentLabel})\n`));
|
|
@@ -45,13 +46,13 @@ export function registerInit(program, deps) {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
await installSkills({ PACKAGE_ROOT, isClaude, isOpenCode, options, print, targetDir, totalSteps });
|
|
48
|
-
await installHooks({ PACKAGE_ROOT, isClaude, isOpenCode, options, print, targetDir, totalSteps });
|
|
49
|
+
await installHooks({ PACKAGE_ROOT, isClaude, isOpenCode, isCodex, options, print, targetDir, totalSteps });
|
|
49
50
|
await maybeInstallCheckpoint({ options, print, targetDir, totalSteps });
|
|
50
51
|
await configureMcp({ agent, options, print, targetDir, totalSteps });
|
|
51
52
|
|
|
52
53
|
print.blank();
|
|
53
54
|
console.log(chalk.bold.green(' ✓ ODD Studio is ready.\n'));
|
|
54
|
-
printNextSteps({ isClaude, isOpenCode, projectName });
|
|
55
|
+
printNextSteps({ isClaude, isOpenCode, isCodex, projectName });
|
|
55
56
|
console.log(chalk.dim(' ODD Studio implements Outcome-Driven Development.'));
|
|
56
57
|
console.log(chalk.dim(" At every step, you'll understand why — not just what to do."));
|
|
57
58
|
print.blank();
|
|
@@ -98,7 +99,7 @@ async function installSkills(context) {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
async function installHooks(context) {
|
|
101
|
-
const { PACKAGE_ROOT, isClaude, isOpenCode, options, print, targetDir, totalSteps } = context;
|
|
102
|
+
const { PACKAGE_ROOT, isClaude, isOpenCode, isCodex, options, print, targetDir, totalSteps } = context;
|
|
102
103
|
|
|
103
104
|
if (options.skipHooks) {
|
|
104
105
|
print.step(3, totalSteps, 'Skipping hooks/plugin (--skip-hooks)');
|
|
@@ -109,6 +110,7 @@ async function installHooks(context) {
|
|
|
109
110
|
const targets = [];
|
|
110
111
|
if (isClaude) targets.push('hooks');
|
|
111
112
|
if (isOpenCode) targets.push('plugin');
|
|
113
|
+
if (isCodex) targets.push('Codex plugin');
|
|
112
114
|
print.step(3, totalSteps, `Installing safety ${targets.join(' + ')}...`);
|
|
113
115
|
const spinner = ora({ text: '', indent: 4 }).start();
|
|
114
116
|
|
|
@@ -126,6 +128,12 @@ async function installHooks(context) {
|
|
|
126
128
|
print.ok('OpenCode plugin installed → odd-studio-plugin.js');
|
|
127
129
|
}));
|
|
128
130
|
}
|
|
131
|
+
if (isCodex) {
|
|
132
|
+
const { default: setupCodexPlugin } = await import('../../scripts/setup-codex-plugin.js');
|
|
133
|
+
installs.push(setupCodexPlugin(PACKAGE_ROOT, targetDir).then(() => {
|
|
134
|
+
print.ok('Codex plugin installed → ' + chalk.cyan('plugins/odd-studio/'));
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
129
137
|
await Promise.all(installs);
|
|
130
138
|
spinner.stop();
|
|
131
139
|
} catch (error) {
|
|
@@ -184,7 +192,7 @@ async function configureMcp({ agent, options, print, targetDir, totalSteps }) {
|
|
|
184
192
|
const { default: setupMcp } = await import('../../scripts/setup-mcp.js');
|
|
185
193
|
const result = await setupMcp(agent, targetDir);
|
|
186
194
|
spinner.stop();
|
|
187
|
-
if (result.mcpJsonUpdated || result.openCodeUpdated) {
|
|
195
|
+
if (result.mcpJsonUpdated || result.openCodeUpdated || result.codexUpdated) {
|
|
188
196
|
print.ok('odd-flow MCP server configured → ' + chalk.cyan('.mcp.json'));
|
|
189
197
|
} else {
|
|
190
198
|
print.ok('odd-flow MCP server already configured');
|
|
@@ -195,7 +203,7 @@ async function configureMcp({ agent, options, print, targetDir, totalSteps }) {
|
|
|
195
203
|
}
|
|
196
204
|
}
|
|
197
205
|
|
|
198
|
-
function printNextSteps({ isClaude, isOpenCode, projectName }) {
|
|
206
|
+
function printNextSteps({ isClaude, isOpenCode, isCodex, projectName }) {
|
|
199
207
|
console.log(chalk.bold(' Next steps:'));
|
|
200
208
|
console.log(chalk.dim(' ─────────────────────────────────────────'));
|
|
201
209
|
let stepN = 1;
|
|
@@ -208,6 +216,18 @@ function printNextSteps({ isClaude, isOpenCode, projectName }) {
|
|
|
208
216
|
console.log(` ${stepN++}. Restart OpenCode: ` + chalk.cyan('quit and reopen') + chalk.dim(' (activates odd-flow memory + plugin)'));
|
|
209
217
|
console.log(` ${stepN++}. Open your project: ` + chalk.cyan('opencode'));
|
|
210
218
|
}
|
|
219
|
+
if (isCodex) {
|
|
220
|
+
console.log(` ${stepN++}. Restart Codex: ` + chalk.cyan('quit and reopen') + chalk.dim(' (activates the local ODD Studio plugin)'));
|
|
221
|
+
console.log(` ${stepN++}. Open your project in Codex`);
|
|
222
|
+
}
|
|
211
223
|
console.log(` ${stepN++}. Start your ODD session: ` + chalk.cyan('/odd'));
|
|
212
224
|
console.log();
|
|
213
225
|
}
|
|
226
|
+
|
|
227
|
+
function getAgentLabel(agent) {
|
|
228
|
+
if (agent === 'all') return 'Claude Code + OpenCode + Codex';
|
|
229
|
+
if (agent === 'both') return 'Claude Code + OpenCode';
|
|
230
|
+
if (agent === 'opencode') return 'OpenCode';
|
|
231
|
+
if (agent === 'codex') return 'Codex';
|
|
232
|
+
return 'Claude Code';
|
|
233
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
|
-
import { OPEN_CODE_COMMAND_FILES } from '../../scripts/assets.js';
|
|
5
|
+
import { CODEX_PLUGIN_NAME, OPEN_CODE_COMMAND_FILES } from '../../scripts/assets.js';
|
|
6
6
|
import { removeIfEmpty } from '../shared.js';
|
|
7
7
|
|
|
8
8
|
export function registerUninstall(program, deps) {
|
|
@@ -11,19 +11,21 @@ export function registerUninstall(program, deps) {
|
|
|
11
11
|
program
|
|
12
12
|
.command('uninstall')
|
|
13
13
|
.description('Remove all ODD Studio hooks, skills, commands, plugin, and MCP config from this project')
|
|
14
|
-
.option('--agent <type>', 'Target agent: claude-code, opencode, both, or auto (default: auto)', 'auto')
|
|
14
|
+
.option('--agent <type>', 'Target agent: claude-code, opencode, codex, both, all, or auto (default: auto)', 'auto')
|
|
15
15
|
.action(async (options) => {
|
|
16
16
|
print.logo();
|
|
17
17
|
const { default: detectAgent } = await import('../../scripts/detect-agent.js');
|
|
18
18
|
const agent = detectAgent(options.agent);
|
|
19
|
-
const isClaude = agent === 'claude-code' || agent === 'both';
|
|
20
|
-
const isOpenCode = agent === 'opencode' || agent === 'both';
|
|
19
|
+
const isClaude = agent === 'claude-code' || agent === 'both' || agent === 'all';
|
|
20
|
+
const isOpenCode = agent === 'opencode' || agent === 'both' || agent === 'all';
|
|
21
|
+
const isCodex = agent === 'codex' || agent === 'all';
|
|
21
22
|
const targetDir = process.cwd();
|
|
22
23
|
const removals = [];
|
|
23
24
|
|
|
24
25
|
console.log(' Removing ODD Studio from this project...\n');
|
|
25
26
|
if (isClaude) await removeClaudeAssets(targetDir, removals);
|
|
26
27
|
if (isOpenCode) await removeOpenCodeAssets(targetDir, removals);
|
|
28
|
+
if (isCodex) await removeCodexAssets(targetDir, removals);
|
|
27
29
|
await removeMcpConfig(targetDir, removals);
|
|
28
30
|
|
|
29
31
|
print.blank();
|
|
@@ -84,6 +86,22 @@ async function removeMcpConfig(targetDir, removals) {
|
|
|
84
86
|
await removeMcpEntry(path.join(targetDir, 'opencode.json'), ['mcp', 'odd-flow'], 'odd-flow MCP server from opencode.json', removals);
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
async function removeCodexAssets(targetDir, removals) {
|
|
90
|
+
await removeOwnedPath(path.join(targetDir, 'plugins', CODEX_PLUGIN_NAME), 'Codex ODD plugin', removals);
|
|
91
|
+
const marketplacePath = path.join(targetDir, '.agents', 'plugins', 'marketplace.json');
|
|
92
|
+
if (!fs.existsSync(marketplacePath)) return;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const marketplace = await fs.readJson(marketplacePath);
|
|
96
|
+
if (!Array.isArray(marketplace.plugins)) return;
|
|
97
|
+
const nextPlugins = marketplace.plugins.filter((plugin) => plugin.name !== CODEX_PLUGIN_NAME);
|
|
98
|
+
if (nextPlugins.length === marketplace.plugins.length) return;
|
|
99
|
+
marketplace.plugins = nextPlugins;
|
|
100
|
+
await fs.writeJson(marketplacePath, marketplace, { spaces: 2 });
|
|
101
|
+
removals.push('Codex marketplace registration');
|
|
102
|
+
} catch {}
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
async function removeMcpEntry(filePath, keyPath, label, removals) {
|
|
88
106
|
if (!fs.existsSync(filePath)) return;
|
|
89
107
|
try {
|
package/bin/commands/upgrade.js
CHANGED
|
@@ -11,13 +11,14 @@ export function registerUpgrade(program, deps) {
|
|
|
11
11
|
program
|
|
12
12
|
.command('upgrade')
|
|
13
13
|
.description('Upgrade the /odd skills/commands, hooks/plugin, and odd-flow config to the latest version')
|
|
14
|
-
.option('--agent <type>', 'Target agent: claude-code, opencode, both, or auto (default: auto)', 'auto')
|
|
14
|
+
.option('--agent <type>', 'Target agent: claude-code, opencode, codex, both, all, or auto (default: auto)', 'auto')
|
|
15
15
|
.action(async (options) => {
|
|
16
16
|
print.logo();
|
|
17
17
|
const { default: detectAgent } = await import('../../scripts/detect-agent.js');
|
|
18
18
|
const agent = detectAgent(options.agent);
|
|
19
|
-
const isClaude = agent === 'claude-code' || agent === 'both';
|
|
20
|
-
const isOpenCode = agent === 'opencode' || agent === 'both';
|
|
19
|
+
const isClaude = agent === 'claude-code' || agent === 'both' || agent === 'all';
|
|
20
|
+
const isOpenCode = agent === 'opencode' || agent === 'both' || agent === 'all';
|
|
21
|
+
const isCodex = agent === 'codex' || agent === 'all';
|
|
21
22
|
const targetDir = process.cwd();
|
|
22
23
|
const stateFile = path.resolve(targetDir, '.odd', 'state.json');
|
|
23
24
|
|
|
@@ -30,6 +31,7 @@ export function registerUpgrade(program, deps) {
|
|
|
30
31
|
console.log(chalk.bold(' Upgrading ODD Studio...\n'));
|
|
31
32
|
if (isClaude) await upgradeClaude(PACKAGE_ROOT, targetDir);
|
|
32
33
|
if (isOpenCode) await upgradeOpenCode(PACKAGE_ROOT, targetDir);
|
|
34
|
+
if (isCodex) await upgradeCodex(PACKAGE_ROOT, targetDir);
|
|
33
35
|
await upgradeMcp(agent, targetDir);
|
|
34
36
|
print.blank();
|
|
35
37
|
print.ok('Upgrade complete.');
|
|
@@ -66,6 +68,13 @@ async function upgradeOpenCode(packageRoot, targetDir) {
|
|
|
66
68
|
});
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
async function upgradeCodex(packageRoot, targetDir) {
|
|
72
|
+
await runSpinner('Updating Codex plugin...', 'Codex plugin updated', async () => {
|
|
73
|
+
const { default: setupCodexPlugin } = await import('../../scripts/setup-codex-plugin.js');
|
|
74
|
+
await setupCodexPlugin(packageRoot, targetDir);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
async function upgradeMcp(agent, targetDir) {
|
|
70
79
|
await runSpinner('Checking odd-flow MCP configuration...', null, async (spinner) => {
|
|
71
80
|
const { default: setupMcp } = await import('../../scripts/setup-mcp.js');
|
package/bin/odd-studio.js
CHANGED
|
@@ -15,7 +15,7 @@ import { registerUninstall } from './commands/uninstall.js';
|
|
|
15
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __dirname = path.dirname(__filename);
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
18
|
-
const pkg = require('../package.json'); // v3.
|
|
18
|
+
const pkg = require('../package.json'); // v3.3.1
|
|
19
19
|
|
|
20
20
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
21
21
|
const deps = { PACKAGE_ROOT, print };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "odd-studio",
|
|
3
|
+
"version": "3.3.1",
|
|
4
|
+
"description": "Outcome-Driven Development planning and build harness for domain experts building serious software with AI.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "ODD Studio"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/odd-studio/odd-studio",
|
|
9
|
+
"repository": "https://github.com/odd-studio/odd-studio",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"odd-studio",
|
|
13
|
+
"outcome-driven-development",
|
|
14
|
+
"codex",
|
|
15
|
+
"planning",
|
|
16
|
+
"build"
|
|
17
|
+
],
|
|
18
|
+
"skills": "./skills/",
|
|
19
|
+
"hooks": "./hooks.json",
|
|
20
|
+
"mcpServers": "./.mcp.json",
|
|
21
|
+
"interface": {
|
|
22
|
+
"displayName": "ODD Studio",
|
|
23
|
+
"shortDescription": "Guide planning, build sequencing, and verification with Outcome-Driven Development",
|
|
24
|
+
"longDescription": "Use ODD Studio to plan around personas, outcomes, contracts, and phased delivery, then run a disciplined build and verification flow with odd-flow memory and safety gates.",
|
|
25
|
+
"developerName": "ODD Studio",
|
|
26
|
+
"category": "Coding",
|
|
27
|
+
"capabilities": [
|
|
28
|
+
"Interactive",
|
|
29
|
+
"Read",
|
|
30
|
+
"Write"
|
|
31
|
+
],
|
|
32
|
+
"websiteURL": "https://github.com/odd-studio/odd-studio",
|
|
33
|
+
"privacyPolicyURL": "https://github.com/odd-studio/odd-studio",
|
|
34
|
+
"termsOfServiceURL": "https://github.com/odd-studio/odd-studio",
|
|
35
|
+
"defaultPrompt": "Start or resume an ODD Studio planning and build session for this project",
|
|
36
|
+
"brandColor": "#111111",
|
|
37
|
+
"screenshots": []
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Agent",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "./hooks/odd-studio.sh brief-gate"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "command",
|
|
13
|
+
"command": "./hooks/odd-studio.sh build-gate"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"matcher": "Write|Edit",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "./hooks/odd-studio.sh swarm-write"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "command",
|
|
26
|
+
"command": "./hooks/odd-studio.sh verify-gate"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "./hooks/odd-studio.sh confirm-gate"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"matcher": "Bash",
|
|
36
|
+
"hooks": [
|
|
37
|
+
{
|
|
38
|
+
"type": "command",
|
|
39
|
+
"command": "./hooks/odd-studio.sh commit-gate"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"UserPromptSubmit": [
|
|
45
|
+
{
|
|
46
|
+
"hooks": [
|
|
47
|
+
{
|
|
48
|
+
"type": "command",
|
|
49
|
+
"command": "./hooks/odd-studio.sh swarm-guard"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"PostToolUse": [
|
|
55
|
+
{
|
|
56
|
+
"matcher": "Bash",
|
|
57
|
+
"hooks": [
|
|
58
|
+
{
|
|
59
|
+
"type": "command",
|
|
60
|
+
"command": "./hooks/odd-studio.sh session-save"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"matcher": "mcp__odd-flow__memory_store",
|
|
66
|
+
"hooks": [
|
|
67
|
+
{
|
|
68
|
+
"type": "command",
|
|
69
|
+
"command": "./hooks/odd-studio.sh store-validate"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"matcher": "mcp__odd-flow__coordination_sync",
|
|
75
|
+
"hooks": [
|
|
76
|
+
{
|
|
77
|
+
"type": "command",
|
|
78
|
+
"command": "./hooks/odd-studio.sh sync-validate"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"matcher": "Write",
|
|
84
|
+
"hooks": [
|
|
85
|
+
{
|
|
86
|
+
"type": "command",
|
|
87
|
+
"command": "./hooks/odd-studio.sh code-quality"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"type": "command",
|
|
91
|
+
"command": "./hooks/odd-studio.sh brief-quality"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"type": "command",
|
|
95
|
+
"command": "./hooks/odd-studio.sh outcome-quality"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"matcher": "Edit",
|
|
101
|
+
"hooks": [
|
|
102
|
+
{
|
|
103
|
+
"type": "command",
|
|
104
|
+
"command": "./hooks/odd-studio.sh code-quality"
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odd-studio",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Outcome-Driven Development for AI coding agents — a planning and build harness for domain experts building serious software with AI. Works with Claude Code and
|
|
3
|
+
"version": "3.3.1",
|
|
4
|
+
"description": "Outcome-Driven Development for AI coding agents — a planning and build harness for domain experts building serious software with AI. Works with Claude Code, OpenCode, and Codex.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
7
7
|
"opencode",
|
|
8
|
+
"codex",
|
|
8
9
|
"odd",
|
|
9
10
|
"outcome-driven-development",
|
|
10
11
|
"ai-development",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
"skill/",
|
|
36
37
|
"hooks/",
|
|
37
38
|
"plugins/",
|
|
39
|
+
"codex-plugin/",
|
|
38
40
|
"templates/",
|
|
39
41
|
".claude-plugin/"
|
|
40
42
|
],
|
package/scripts/assets.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
export const ODD_HOOK_FILE = 'odd-studio.sh';
|
|
4
|
+
export const CODEX_PLUGIN_NAME = 'odd-studio';
|
|
5
|
+
export const CODEX_MARKETPLACE_ROOT = {
|
|
6
|
+
name: 'local-plugins',
|
|
7
|
+
interface: {
|
|
8
|
+
displayName: 'Local plugins',
|
|
9
|
+
},
|
|
10
|
+
plugins: [],
|
|
11
|
+
};
|
|
4
12
|
|
|
5
13
|
export const OPEN_CODE_COMMAND_FILES = [
|
|
6
14
|
'odd.md',
|
package/scripts/detect-agent.js
CHANGED
|
@@ -5,20 +5,28 @@ import os from 'os';
|
|
|
5
5
|
|
|
6
6
|
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
7
7
|
const OC_DIR = path.join(os.homedir(), '.config', 'opencode');
|
|
8
|
+
const CODEX_DIR = path.join(os.homedir(), '.codex');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Detects which AI coding agent is installed on this machine.
|
|
11
12
|
*
|
|
12
|
-
* @param {string} override - 'claude-code', 'opencode', 'both', or 'auto'
|
|
13
|
-
* @
|
|
13
|
+
* @param {string} override - 'claude-code', 'opencode', 'codex', 'both', 'all', or 'auto'
|
|
14
|
+
* @param {{ existsSync?: (path: string) => boolean }} options
|
|
15
|
+
* @returns {'claude-code' | 'opencode' | 'codex' | 'both' | 'all'}
|
|
14
16
|
*/
|
|
15
|
-
export default function detectAgent(override = 'auto') {
|
|
17
|
+
export default function detectAgent(override = 'auto', options = {}) {
|
|
16
18
|
if (override !== 'auto') return override;
|
|
19
|
+
const exists = options.existsSync || fs.existsSync;
|
|
17
20
|
|
|
18
|
-
const hasClaude =
|
|
19
|
-
const hasOpenCode =
|
|
21
|
+
const hasClaude = exists(CLAUDE_DIR);
|
|
22
|
+
const hasOpenCode = exists(OC_DIR);
|
|
23
|
+
const hasCodex = exists(CODEX_DIR);
|
|
20
24
|
|
|
25
|
+
if (hasClaude && hasOpenCode && hasCodex) return 'all';
|
|
21
26
|
if (hasClaude && hasOpenCode) return 'both';
|
|
27
|
+
if (hasClaude && hasCodex) return 'all';
|
|
28
|
+
if (hasOpenCode && hasCodex) return 'all';
|
|
29
|
+
if (hasCodex) return 'codex';
|
|
22
30
|
if (hasOpenCode) return 'opencode';
|
|
23
31
|
return 'claude-code';
|
|
24
32
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { COMMANDS } from './command-definitions.js';
|
|
5
|
+
import { CODEX_PLUGIN_NAME } from './assets.js';
|
|
6
|
+
|
|
7
|
+
const CODEX_ODD_ROOT = `plugins/${CODEX_PLUGIN_NAME}/skills/odd/`;
|
|
8
|
+
|
|
9
|
+
export default async function installCodexCommands(pluginDest) {
|
|
10
|
+
const commandsDest = path.join(pluginDest, 'commands');
|
|
11
|
+
await fs.ensureDir(commandsDest);
|
|
12
|
+
|
|
13
|
+
for (const cmd of COMMANDS) {
|
|
14
|
+
const content = cmd.source
|
|
15
|
+
? buildMainCommand(cmd)
|
|
16
|
+
: buildSubcommand(cmd);
|
|
17
|
+
await fs.writeFile(path.join(commandsDest, `${cmd.name}.md`), content);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { destination: commandsDest, commandCount: COMMANDS.length };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function buildMainCommand(cmd) {
|
|
24
|
+
return [
|
|
25
|
+
'---',
|
|
26
|
+
`description: "${cmd.description}"`,
|
|
27
|
+
'---',
|
|
28
|
+
'',
|
|
29
|
+
'# /odd',
|
|
30
|
+
'',
|
|
31
|
+
'You are now operating as the ODD Studio coach.',
|
|
32
|
+
'',
|
|
33
|
+
'Read this file now:',
|
|
34
|
+
`- \`${CODEX_ODD_ROOT}SKILL.md\` — the full ODD Studio coach`,
|
|
35
|
+
'',
|
|
36
|
+
'Then execute the startup state check exactly as documented.',
|
|
37
|
+
'',
|
|
38
|
+
].join('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildSubcommand(cmd) {
|
|
42
|
+
return [
|
|
43
|
+
'---',
|
|
44
|
+
`description: "${cmd.description}"`,
|
|
45
|
+
'---',
|
|
46
|
+
'',
|
|
47
|
+
`# /${cmd.name}`,
|
|
48
|
+
'',
|
|
49
|
+
rewriteBody(cmd.body),
|
|
50
|
+
'',
|
|
51
|
+
].join('\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function rewriteBody(body) {
|
|
55
|
+
return body.replace(/`\.opencode\/odd\//g, `\`${CODEX_ODD_ROOT}`);
|
|
56
|
+
}
|
|
@@ -16,6 +16,9 @@ export default async function scaffoldProject(targetDir, projectName, agent = 'c
|
|
|
16
16
|
}
|
|
17
17
|
const isClaude = agent === 'claude-code' || agent === 'both';
|
|
18
18
|
const isOpenCode = agent === 'opencode' || agent === 'both';
|
|
19
|
+
const isCodex = agent === 'codex' || agent === 'all';
|
|
20
|
+
const wantsAgentsMd = isOpenCode || isCodex;
|
|
21
|
+
const wantsClaudeMd = isClaude || agent === 'all';
|
|
19
22
|
|
|
20
23
|
await fs.ensureDir(targetDir);
|
|
21
24
|
|
|
@@ -31,9 +34,8 @@ export default async function scaffoldProject(targetDir, projectName, agent = 'c
|
|
|
31
34
|
return !fs.existsSync(destDir) || fs.readdirSync(destDir).length === 0;
|
|
32
35
|
}
|
|
33
36
|
// Skip CLAUDE.md if OpenCode-only
|
|
34
|
-
if (basename === 'CLAUDE.md' && !
|
|
35
|
-
|
|
36
|
-
if (basename === 'AGENTS.md' && !isOpenCode) return false;
|
|
37
|
+
if (basename === 'CLAUDE.md' && !wantsClaudeMd) return false;
|
|
38
|
+
if (basename === 'AGENTS.md' && !wantsAgentsMd) return false;
|
|
37
39
|
return true;
|
|
38
40
|
},
|
|
39
41
|
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import {
|
|
5
|
+
CODEX_MARKETPLACE_ROOT,
|
|
6
|
+
CODEX_PLUGIN_NAME,
|
|
7
|
+
ODD_HOOK_FILE,
|
|
8
|
+
} from './assets.js';
|
|
9
|
+
import installCodexCommands from './install-codex-commands.js';
|
|
10
|
+
|
|
11
|
+
const PLUGIN_RELATIVE_PATH = `./plugins/${CODEX_PLUGIN_NAME}`;
|
|
12
|
+
const MARKETPLACE_ENTRY = {
|
|
13
|
+
name: CODEX_PLUGIN_NAME,
|
|
14
|
+
source: {
|
|
15
|
+
source: 'local',
|
|
16
|
+
path: PLUGIN_RELATIVE_PATH,
|
|
17
|
+
},
|
|
18
|
+
policy: {
|
|
19
|
+
installation: 'AVAILABLE',
|
|
20
|
+
authentication: 'ON_INSTALL',
|
|
21
|
+
},
|
|
22
|
+
category: 'Coding',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default async function setupCodexPlugin(packageRoot, targetDir) {
|
|
26
|
+
const pluginSource = path.join(packageRoot, 'codex-plugin');
|
|
27
|
+
const pluginDest = path.join(targetDir, 'plugins', CODEX_PLUGIN_NAME);
|
|
28
|
+
const skillSource = path.join(packageRoot, 'skill');
|
|
29
|
+
const hookSource = path.join(packageRoot, 'hooks', ODD_HOOK_FILE);
|
|
30
|
+
|
|
31
|
+
if (!fs.existsSync(pluginSource)) {
|
|
32
|
+
throw new Error(`Codex plugin source not found: ${pluginSource}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await fs.copy(pluginSource, pluginDest, { overwrite: true });
|
|
36
|
+
await installCodexSkills(skillSource, pluginDest);
|
|
37
|
+
await installCodexCommands(pluginDest);
|
|
38
|
+
await installCodexHook(hookSource, pluginDest);
|
|
39
|
+
const marketplaceUpdated = await updateMarketplace(targetDir);
|
|
40
|
+
|
|
41
|
+
return { destination: pluginDest, marketplaceUpdated };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function installCodexSkills(skillSource, pluginDest) {
|
|
45
|
+
const skillsDest = path.join(pluginDest, 'skills');
|
|
46
|
+
await fs.ensureDir(skillsDest);
|
|
47
|
+
|
|
48
|
+
const oddDest = path.join(skillsDest, 'odd');
|
|
49
|
+
await fs.ensureDir(oddDest);
|
|
50
|
+
|
|
51
|
+
const entries = await fs.readdir(skillSource, { withFileTypes: true });
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const src = path.join(skillSource, entry.name);
|
|
54
|
+
if (entry.isDirectory() && fs.existsSync(path.join(src, 'SKILL.md'))) {
|
|
55
|
+
await fs.copy(src, path.join(skillsDest, entry.name), { overwrite: true });
|
|
56
|
+
await rewriteSkillPaths(path.join(skillsDest, entry.name, 'SKILL.md'));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await fs.copy(src, path.join(oddDest, entry.name), { overwrite: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await rewriteSkillPaths(path.join(oddDest, 'SKILL.md'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function rewriteSkillPaths(filePath) {
|
|
67
|
+
if (!fs.existsSync(filePath)) return;
|
|
68
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
69
|
+
content = content.replace(/Works with Claude Code and OpenCode\./g, 'Works with Claude Code, OpenCode, and Codex.');
|
|
70
|
+
content = content.replace(/`\.claude\/skills\/odd\//g, '`plugins/odd-studio/skills/odd/');
|
|
71
|
+
content = content.replace(/\.claude\/skills\/odd\//g, 'plugins/odd-studio/skills/odd/');
|
|
72
|
+
await fs.writeFile(filePath, content);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function installCodexHook(hookSource, pluginDest) {
|
|
76
|
+
const hooksDest = path.join(pluginDest, 'hooks');
|
|
77
|
+
await fs.ensureDir(hooksDest);
|
|
78
|
+
const hookDest = path.join(hooksDest, ODD_HOOK_FILE);
|
|
79
|
+
await fs.copy(hookSource, hookDest, { overwrite: true });
|
|
80
|
+
await fs.chmod(hookDest, 0o755);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function updateMarketplace(targetDir) {
|
|
84
|
+
const marketplacePath = path.join(targetDir, '.agents', 'plugins', 'marketplace.json');
|
|
85
|
+
await fs.ensureDir(path.dirname(marketplacePath));
|
|
86
|
+
|
|
87
|
+
let marketplace = CODEX_MARKETPLACE_ROOT;
|
|
88
|
+
if (fs.existsSync(marketplacePath)) {
|
|
89
|
+
try {
|
|
90
|
+
marketplace = await fs.readJson(marketplacePath);
|
|
91
|
+
} catch {
|
|
92
|
+
marketplace = CODEX_MARKETPLACE_ROOT;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!marketplace.name) marketplace.name = CODEX_MARKETPLACE_ROOT.name;
|
|
97
|
+
if (!marketplace.interface) marketplace.interface = CODEX_MARKETPLACE_ROOT.interface;
|
|
98
|
+
if (!marketplace.interface.displayName) {
|
|
99
|
+
marketplace.interface.displayName = CODEX_MARKETPLACE_ROOT.interface.displayName;
|
|
100
|
+
}
|
|
101
|
+
if (!Array.isArray(marketplace.plugins)) marketplace.plugins = [];
|
|
102
|
+
|
|
103
|
+
const existingIndex = marketplace.plugins.findIndex((plugin) => plugin.name === CODEX_PLUGIN_NAME);
|
|
104
|
+
if (existingIndex === -1) {
|
|
105
|
+
marketplace.plugins.push(MARKETPLACE_ENTRY);
|
|
106
|
+
await fs.writeJson(marketplacePath, marketplace, { spaces: 2 });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const existing = marketplace.plugins[existingIndex];
|
|
111
|
+
const changed = JSON.stringify(existing) !== JSON.stringify(MARKETPLACE_ENTRY);
|
|
112
|
+
if (changed) {
|
|
113
|
+
marketplace.plugins[existingIndex] = MARKETPLACE_ENTRY;
|
|
114
|
+
await fs.writeJson(marketplacePath, marketplace, { spaces: 2 });
|
|
115
|
+
}
|
|
116
|
+
return changed;
|
|
117
|
+
}
|
package/scripts/setup-mcp.js
CHANGED
|
@@ -24,14 +24,14 @@ const ODD_FLOW_SERVER_ENTRY = {
|
|
|
24
24
|
*
|
|
25
25
|
* Nothing is written to ~/. Both paths are idempotent.
|
|
26
26
|
*
|
|
27
|
-
* @param {'claude-code' | 'opencode' | 'both'} agent - Target agent
|
|
27
|
+
* @param {'claude-code' | 'opencode' | 'codex' | 'both' | 'all'} agent - Target agent
|
|
28
28
|
* @param {string} targetDir - Project directory
|
|
29
29
|
*/
|
|
30
30
|
export default async function setupMcp(agent = 'claude-code', targetDir) {
|
|
31
|
-
const result = { mcpJsonUpdated: false, openCodeUpdated: false };
|
|
31
|
+
const result = { mcpJsonUpdated: false, openCodeUpdated: false, codexUpdated: false };
|
|
32
32
|
|
|
33
33
|
// ── Claude Code: <projectDir>/.mcp.json ───────────────────────────────────
|
|
34
|
-
if (agent === 'claude-code' || agent === 'both') {
|
|
34
|
+
if (agent === 'claude-code' || agent === 'both' || agent === 'all') {
|
|
35
35
|
const mcpJsonPath = path.join(targetDir, '.mcp.json');
|
|
36
36
|
let mcpConfig = { mcpServers: {} };
|
|
37
37
|
if (fs.existsSync(mcpJsonPath)) {
|
|
@@ -55,7 +55,7 @@ export default async function setupMcp(agent = 'claude-code', targetDir) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// ── OpenCode: <projectDir>/opencode.json ──────────────────────────────────
|
|
58
|
-
if (agent === 'opencode' || agent === 'both') {
|
|
58
|
+
if (agent === 'opencode' || agent === 'both' || agent === 'all') {
|
|
59
59
|
const ocConfigPath = path.join(targetDir, 'opencode.json');
|
|
60
60
|
|
|
61
61
|
let ocConfig = {};
|
|
@@ -84,5 +84,28 @@ export default async function setupMcp(agent = 'claude-code', targetDir) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
if (agent === 'codex' || agent === 'all') {
|
|
88
|
+
const codexMcpPath = path.join(targetDir, 'plugins', 'odd-studio', '.mcp.json');
|
|
89
|
+
await fs.ensureDir(path.dirname(codexMcpPath));
|
|
90
|
+
let codexConfig = { mcpServers: {} };
|
|
91
|
+
if (fs.existsSync(codexMcpPath)) {
|
|
92
|
+
try {
|
|
93
|
+
codexConfig = await fs.readJson(codexMcpPath);
|
|
94
|
+
} catch {
|
|
95
|
+
codexConfig = { mcpServers: {} };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!codexConfig.mcpServers) codexConfig.mcpServers = {};
|
|
99
|
+
|
|
100
|
+
if (!codexConfig.mcpServers['odd-flow']) {
|
|
101
|
+
if (fs.existsSync(codexMcpPath)) {
|
|
102
|
+
await fs.copy(codexMcpPath, codexMcpPath + '.bak', { overwrite: true });
|
|
103
|
+
}
|
|
104
|
+
codexConfig.mcpServers['odd-flow'] = ODD_FLOW_SERVER_ENTRY;
|
|
105
|
+
await fs.writeJson(codexMcpPath, codexConfig, { spaces: 2 });
|
|
106
|
+
result.codexUpdated = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
87
110
|
return result;
|
|
88
111
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "odd"
|
|
3
|
-
version: "3.
|
|
4
|
-
description: "Outcome-Driven Development planning and build coach. Use /odd to start or resume an ODD project — building personas, writing outcomes, mapping contracts, creating a Master Implementation Plan, and directing a odd-flow-powered build. Designed for domain experts who are not developers. Works with Claude Code and
|
|
3
|
+
version: "3.3.1"
|
|
4
|
+
description: "Outcome-Driven Development planning and build coach. Use /odd to start or resume an ODD project — building personas, writing outcomes, mapping contracts, creating a Master Implementation Plan, and directing a odd-flow-powered build. Designed for domain experts who are not developers. Works with Claude Code, OpenCode, and Codex."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# ODD Studio — Outcome-Driven Development Coach
|
|
@@ -35,7 +35,7 @@ Display this when no existing state is found:
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
Welcome to ODD Studio v3.
|
|
38
|
+
Welcome to ODD Studio v3.3.1.
|
|
39
39
|
|
|
40
40
|
You are about to plan and build something real — using a methodology called Outcome-Driven Development. Before we write a single line of code, we are going to get precise about three things:
|
|
41
41
|
|
|
@@ -59,7 +59,7 @@ Display this when existing state is found. Replace the bracketed values with act
|
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
62
|
-
Welcome back to ODD Studio v3.
|
|
62
|
+
Welcome back to ODD Studio v3.3.1.
|
|
63
63
|
|
|
64
64
|
**Project:** [project.name]
|
|
65
65
|
**Current Phase:** [state.currentPhase]
|