ima-claude 2.26.0 → 2.27.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  FP patterns, architecture guidance, and team standards for AI coding agents.
4
4
 
5
- **Supports Claude Code, Junie CLI, Gemini CLI, and GitHub Copilot** — with an extensible adapter architecture ready for more platforms.
5
+ **Supports Claude Code, Junie CLI, Gemini CLI, GitHub Copilot, and OpenAI Codex CLI** — with an extensible adapter architecture ready for more platforms.
6
6
 
7
7
  Built by [Independent Medical Alliance](https://imahealth.org) (formerly FLCCC)
8
8
 
@@ -38,9 +38,9 @@ claude plugin update ima-claude
38
38
 
39
39
  Or use `/plugin` inside Claude Code to manage updates interactively via the **Installed** tab.
40
40
 
41
- ### Junie CLI / Gemini CLI / GitHub Copilot — Multi-Platform Installer
41
+ ### Junie CLI / Gemini CLI / GitHub Copilot / Codex CLI — Multi-Platform Installer
42
42
 
43
- For Junie, Gemini, GitHub Copilot, or any non-plugin platform, use the interactive CLI installer:
43
+ For Junie, Gemini, GitHub Copilot, OpenAI Codex CLI, or any non-plugin platform, use the interactive CLI installer:
44
44
 
45
45
  ```bash
46
46
  npx ima-claude install
@@ -48,7 +48,7 @@ npx ima-claude install
48
48
 
49
49
  The installer auto-detects which platforms are available and walks you through installation:
50
50
 
51
- 1. **Detects platforms** — scans for Claude Code (`~/.claude`), Junie CLI (`~/.junie`), Gemini CLI (`~/.gemini`), and GitHub Copilot (`~/.copilot`)
51
+ 1. **Detects platforms** — scans for Claude Code (`~/.claude`), Junie CLI (`~/.junie`), Gemini CLI (`~/.gemini`), GitHub Copilot (`~/.copilot`), and OpenAI Codex CLI (`~/.codex`, or `$CODEX_HOME`)
52
52
  2. **Shows preview** — lists all skills, agents, and platform-specific items to install
53
53
  3. **Allows exclusions** — skip specific skills or agents you don't need
54
54
  4. **Installs with feedback** — step-by-step progress for each item
@@ -59,6 +59,7 @@ You can also target a specific platform directly:
59
59
  npx ima-claude install --target junie # Junie only
60
60
  npx ima-claude install --target gemini # Gemini CLI only
61
61
  npx ima-claude install --target gh-copilot # GitHub Copilot only
62
+ npx ima-claude install --target codex # OpenAI Codex CLI only
62
63
  npx ima-claude install --target claude # Claude Code only (plugin recommended instead)
63
64
  npx ima-claude detect # Show detected platforms
64
65
  ```
@@ -90,14 +91,14 @@ Caveats:
90
91
 
91
92
  **What's different per platform?**
92
93
 
93
- | | Claude Code | Junie CLI | Gemini CLI | GitHub Copilot |
94
- |---|---|---|---|---|
95
- | **Skills** | Plugin system (auto) | Copied to `~/.junie/skills/` | Copied to `~/.gemini/skills/` | Copied to `~/.copilot/skills/` |
96
- | **Agents** | Plugin system (auto) | Strips `permissionMode` | Strips `model` + `permissionMode`, maps tool names | Strips `model` + `permissionMode`, maps tool names, renames to `.agent.md` |
97
- | **Hooks** | 24 Python hook scripts | No hook system — behavioral guidelines | Translated events + tool names, translator shim | Translated events + tool names, translator shim, flattened config |
98
- | **Guidelines** | Plugin's `CLAUDE.md` injection | Generated `AGENTS.md` | Generated `GEMINI.md` | Generated `copilot-instructions.md` |
94
+ | | Claude Code | Junie CLI | Gemini CLI | GitHub Copilot | Codex CLI |
95
+ |---|---|---|---|---|---|
96
+ | **Skills** | Plugin system (auto) | Copied to `~/.junie/skills/` | Copied to `~/.gemini/skills/` | Copied to `~/.copilot/skills/` | Copied to `~/.codex/skills/` |
97
+ | **Agents** | Plugin system (auto) | Strips `permissionMode` | Strips `model` + `permissionMode`, maps tool names | Strips `model` + `permissionMode`, maps tool names, renames to `.agent.md` | Strips `model` + `permissionMode`, maps tool names; reference copies in `~/.codex/agents/` (Codex has no formal subagent dispatcher yet) |
98
+ | **Hooks** | 24 Python hook scripts | No hook system — behavioral guidelines | Translated events + tool names, translator shim | Translated events + tool names, translator shim, flattened config | Translated tool names only (events match Claude Code), translator shim, matcher-grouped config |
99
+ | **Guidelines** | Plugin's `CLAUDE.md` injection | Generated `AGENTS.md` | Generated `GEMINI.md` | Generated `copilot-instructions.md` | Generated `AGENTS.md` (Codex's native convention) |
99
100
 
100
- Junie doesn't support hooks, so behaviors become guidelines in `AGENTS.md`. Gemini and GitHub Copilot have hooks but use different event/tool names — a translator shim normalizes input so all existing hooks work unmodified. Copilot additionally uses a flat hook config format with `bash` field and `version: 1` wrapper.
101
+ Junie doesn't support hooks, so behaviors become guidelines in `AGENTS.md`. Gemini and GitHub Copilot have hooks but use different event/tool names — a translator shim normalizes input so all existing hooks work unmodified. Copilot additionally uses a flat hook config format with `bash` field and `version: 1` wrapper. Codex CLI shares Claude Code's hook event names natively, so the translator only rewrites tool-name strings; MCP servers register via `[mcp_servers.<name>]` tables in `~/.codex/config.toml` (not auto-configured yet).
101
102
 
102
103
  ### Adding New Platforms
103
104
 
@@ -109,7 +110,8 @@ platforms/
109
110
  ├── claude/adapter.ts # Claude Code adapter
110
111
  ├── junie/adapter.ts # Junie CLI adapter
111
112
  ├── gemini/adapter.ts # Gemini CLI adapter
112
- └── gh-copilot/adapter.ts # GitHub Copilot adapter
113
+ ├── gh-copilot/adapter.ts # GitHub Copilot adapter
114
+ └── codex/adapter.ts # OpenAI Codex CLI adapter
113
115
  ```
114
116
 
115
117
  See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface contract.
@@ -118,10 +120,10 @@ See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface con
118
120
 
119
121
  ## What's Included
120
122
 
121
- - **Multi-Platform Installer**: Interactive CLI with auto-detection, install preview, and per-item exclusion — supports Claude Code, Junie CLI, Gemini CLI, and GitHub Copilot
122
- - **49 Skills**: Foundational + FP implementation + domain expert + integration + meta-skills
123
+ - **Multi-Platform Installer**: Interactive CLI with auto-detection, install preview, and per-item exclusion — supports Claude Code, Junie CLI, Gemini CLI, GitHub Copilot, and OpenAI Codex CLI
124
+ - **63 Skills**: Foundational + FP implementation + domain expert + integration + meta-skills
123
125
  - **6 Named Agents**: Explorer (haiku), Implementer (sonnet), Reviewer (sonnet), Tester (sonnet), WP Developer (sonnet), Memory (sonnet) — enforced constraints
124
- - **23 Hooks**: Automatic behavioral enforcement (security, memory, workflow, Serena, code quality) — translated to guidelines for platforms without hook support
126
+ - **24 Hooks**: Automatic behavioral enforcement (security, memory, workflow, Serena, code quality) — translated to guidelines for platforms without hook support
125
127
  - **Default Persona**: "The Practitioner" - 25-year veteran mindset, collaborative, plan-first
126
128
  - **3-Tier Memory**: Vestige (neural decay) + Qdrant (permanent library) + Serena (project workbench)
127
129
  - **IMA Workflow**: Brainstorm → Plan → Implement → Test → Review → Document (habit-driven, not tool-enforced)
@@ -131,7 +133,7 @@ See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface con
131
133
 
132
134
  ## Prerequisites
133
135
 
134
- - [Claude Code](https://claude.ai/code), [Junie CLI](https://www.jetbrains.com/help/idea/junie.html), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [GitHub Copilot](https://github.com/features/copilot) installed
136
+ - [Claude Code](https://claude.ai/code), [Junie CLI](https://www.jetbrains.com/help/idea/junie.html), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [GitHub Copilot](https://github.com/features/copilot), or [OpenAI Codex CLI](https://github.com/openai/codex) installed
135
137
 
136
138
  ## MCP Servers (Highly Recommended)
137
139
 
@@ -497,7 +499,7 @@ ima-claude follows a **Persona + Skills** architecture:
497
499
  - **Platform adapters** - Shared `PlatformAdapter` interface; each platform implements detect, preview, install, and guideline generation
498
500
 
499
501
  This makes ima-claude:
500
- 1. **Multi-platform** - Same skills and agents across Claude Code, Junie CLI, Gemini CLI, and GitHub Copilot
502
+ 1. **Multi-platform** - Same skills and agents across Claude Code, Junie CLI, Gemini CLI, GitHub Copilot, and OpenAI Codex CLI
501
503
  2. **Fully standalone** - Complete system without dependencies
502
504
  3. **Consistent** - Same mindset across all interactions
503
505
  4. **Efficient** - Skills load on-demand based on context
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ var HOOKS_DIR = join(CLAUDE_DIR, "hooks");
11
11
  var COMMANDS_DIR = join(CLAUDE_DIR, "commands");
12
12
  var RULES_DIR = join(CLAUDE_DIR, "rules");
13
13
  var SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
14
- var VERSION = "2.26.0";
14
+ var VERSION = "2.27.0";
15
15
  var colors = {
16
16
  reset: "\x1B[0m",
17
17
  bright: "\x1B[1m",
@@ -1425,12 +1425,379 @@ function mergeCopilotHooksConfig(configPath, newConfig) {
1425
1425
  writeFileSync4(configPath, JSON.stringify(existing, null, 2) + "\n");
1426
1426
  }
1427
1427
 
1428
+ // platforms/codex/adapter.ts
1429
+ import { join as join6, dirname as dirname4 } from "path";
1430
+ import { homedir as homedir5 } from "os";
1431
+ import { existsSync as existsSync6, readdirSync as readdirSync6, statSync as statSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5, copyFileSync as copyFileSync5 } from "fs";
1432
+ import { fileURLToPath as fileURLToPath3 } from "url";
1433
+ var __filename3 = fileURLToPath3(import.meta.url);
1434
+ var __dirname4 = dirname4(__filename3);
1435
+ var CODEX_DIR = process.env.CODEX_HOME ?? join6(homedir5(), ".codex");
1436
+ var CODEX_SKILLS_DIR = join6(CODEX_DIR, "skills");
1437
+ var CODEX_AGENTS_DIR = join6(CODEX_DIR, "agents");
1438
+ var CODEX_HOOKS_DIR = join6(CODEX_DIR, "hooks");
1439
+ var CODEX_HOOKS_CONFIG = join6(CODEX_DIR, "hooks.json");
1440
+ var CODEX_GUIDELINES_FILE = join6(CODEX_DIR, "AGENTS.md");
1441
+ var CODEX_CONFIG_TOML = join6(CODEX_DIR, "config.toml");
1442
+ var TOOL_MAP3 = {
1443
+ Bash: "shell",
1444
+ Read: "read",
1445
+ Edit: "edit",
1446
+ Write: "write",
1447
+ Glob: "glob",
1448
+ Grep: "grep",
1449
+ LS: "list",
1450
+ WebSearch: "web_search",
1451
+ WebFetch: "fetch",
1452
+ ExitPlanMode: "ExitPlanMode"
1453
+ };
1454
+ var EVENT_MAP3 = {
1455
+ PreToolUse: "PreToolUse",
1456
+ PostToolUse: "PostToolUse",
1457
+ UserPromptSubmit: "UserPromptSubmit",
1458
+ SessionStart: "SessionStart"
1459
+ };
1460
+ function parseFrontmatter4(content) {
1461
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
1462
+ if (!match) return { frontmatter: {}, body: content };
1463
+ const frontmatter = {};
1464
+ for (const line of match[1].split("\n")) {
1465
+ const colonIdx = line.indexOf(":");
1466
+ if (colonIdx === -1) continue;
1467
+ const key = line.slice(0, colonIdx).trim();
1468
+ const value = line.slice(colonIdx + 1).trim();
1469
+ if (key) frontmatter[key] = value;
1470
+ }
1471
+ return { frontmatter, body: match[2] };
1472
+ }
1473
+ function serializeFrontmatter4(frontmatter, body) {
1474
+ const lines = Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`);
1475
+ return `---
1476
+ ${lines.join("\n")}
1477
+ ---
1478
+ ${body}`;
1479
+ }
1480
+ function mapToolName3(claudeName) {
1481
+ return TOOL_MAP3[claudeName] ?? claudeName;
1482
+ }
1483
+ function transformAgentForCodex(content) {
1484
+ const { frontmatter, body } = parseFrontmatter4(content);
1485
+ const { permissionMode: _perm, model: _model, ...kept } = frontmatter;
1486
+ if (kept.tools) {
1487
+ const mapped = kept.tools.split(",").map((t) => t.trim()).map(mapToolName3).join(", ");
1488
+ kept.tools = mapped;
1489
+ }
1490
+ return serializeFrontmatter4(kept, body);
1491
+ }
1492
+ function translateMatcher3(matcher) {
1493
+ return TOOL_MAP3[matcher] ?? matcher;
1494
+ }
1495
+ function translateHookCommand3(command2) {
1496
+ const translatorPath = join6(CODEX_HOOKS_DIR, "hooks-translator.py");
1497
+ const match = command2.match(/python3\s+.*\/([^/\s]+\.py)(\s.*)?$/);
1498
+ if (!match) return command2;
1499
+ const scriptName = match[1];
1500
+ const trailingArgs = match[2] ?? "";
1501
+ return `python3 ${translatorPath} ${join6(CODEX_HOOKS_DIR, scriptName)}${trailingArgs}`;
1502
+ }
1503
+ function generateCodexHooksConfig() {
1504
+ const codexHooks = {};
1505
+ for (const [claudeEvent, hookEntries] of Object.entries(HOOKS_CONFIG.hooks)) {
1506
+ const codexEvent = EVENT_MAP3[claudeEvent] ?? claudeEvent;
1507
+ codexHooks[codexEvent] = hookEntries.map(
1508
+ (entry) => {
1509
+ const translated = {};
1510
+ if (entry.matcher) {
1511
+ translated.matcher = translateMatcher3(entry.matcher);
1512
+ }
1513
+ translated.hooks = entry.hooks.map((h) => ({
1514
+ type: h.type,
1515
+ command: translateHookCommand3(h.command)
1516
+ }));
1517
+ return translated;
1518
+ }
1519
+ );
1520
+ }
1521
+ return { hooks: codexHooks };
1522
+ }
1523
+ function generateCodexAgentsMd() {
1524
+ return `# ima-claude: AI Coding Agent Guidelines
1525
+
1526
+ > Generated by ima-claude v${VERSION} for OpenAI Codex CLI.
1527
+ > Source: https://github.com/Soabirw/ima-claude
1528
+
1529
+ This file is auto-loaded by Codex at session start (\`AGENTS.md\` convention).
1530
+ Per-directory \`AGENTS.md\` files in your project tree merge on top of this one,
1531
+ with closer-to-cwd files taking precedence.
1532
+
1533
+ ## Default Persona: The Practitioner
1534
+
1535
+ A 25-year software development veteran. FP-first, composition-minded, anti-over-engineering.
1536
+ Uses "we" not "I" \u2014 collaborative, humble, light-hearted. "Slow is smooth, smooth is fast."
1537
+
1538
+ **Philosophy**: Simple > Complex | Evidence > Assumptions | Native > Utilities | MVP > Enterprise
1539
+
1540
+ ---
1541
+
1542
+ ## Memory Routing
1543
+
1544
+ | Store what | Where | Why |
1545
+ |---|---|---|
1546
+ | Decisions, preferences, patterns, bugs | Vestige \`smart_ingest\` | Fades naturally if not referenced |
1547
+ | Reference material (docs, standards, PRDs) | Qdrant \`qdrant-store\` | Permanent library |
1548
+ | Session state, task progress | Serena \`write_memory\` | Project-scoped workbench |
1549
+ | Future reminders | Vestige \`intention\` | Surfaces at next session |
1550
+
1551
+ At session start, check memory before asking questions:
1552
+ - Vestige: search for user preferences and project context
1553
+ - Vestige: check for pending reminders/intentions
1554
+ - Serena: list memories if in a Serena-activated project
1555
+
1556
+ Auto-store: "I prefer..." \u2192 Vestige preference. "Let's go with X because..." \u2192 Vestige decision. "The reason this failed..." \u2192 Vestige bug.
1557
+
1558
+ After completing work: store outcome in Vestige, reference material in Qdrant, session state in Serena.
1559
+
1560
+ ---
1561
+
1562
+ ## Orchestrator Protocol
1563
+
1564
+ You are the Orchestrator. Plan and delegate. Do NOT implement directly.
1565
+ - Non-trivial work \u2192 task-planner (decompose) \u2192 task-runner (delegate)
1566
+ - Trivial = single file, < 5 lines, no judgment calls
1567
+
1568
+ ---
1569
+
1570
+ ## Available Agents
1571
+
1572
+ Codex does not yet support a formal named-subagent dispatcher. Treat the agents
1573
+ listed below as documented delegation patterns: when the work matches, scope a
1574
+ session (or a per-directory \`AGENTS.md\` overlay) to that agent's persona.
1575
+ Reference copies of each agent's full system prompt live in \`~/.codex/agents/\`.
1576
+
1577
+ | Agent | Use For |
1578
+ |---|---|
1579
+ | \`explorer\` | File discovery, codebase exploration |
1580
+ | \`implementer\` | Feature dev, bug fixes, refactoring |
1581
+ | \`reviewer\` | Code review, security audit, FP checks |
1582
+ | \`tester\` | Test creation, TDD, debugging test failures |
1583
+ | \`wp-developer\` | WordPress plugins, themes, WP-CLI, forms |
1584
+ | \`memory\` | Memory search, storage, consolidation |
1585
+
1586
+ ---
1587
+
1588
+ ## Code Navigation (Serena)
1589
+
1590
+ When Serena MCP is available, **prefer Serena over read/grep for code investigation.** 40-70% token savings.
1591
+
1592
+ | Instead of | Use |
1593
+ |---|---|
1594
+ | Read file to understand structure | Serena get_symbols_overview |
1595
+ | grep for class/function definition | Serena find_symbol |
1596
+ | grep for callers/references | Serena find_referencing_symbols |
1597
+
1598
+ Use the read tool only when you need the actual implementation body of a known, specific symbol.
1599
+
1600
+ ---
1601
+
1602
+ ## Complex Reasoning
1603
+
1604
+ Use sequential thinking before acting on:
1605
+ - Debugging / root cause analysis / "why is this failing"
1606
+ - Trade-off evaluation / "which approach"
1607
+ - Architectural decisions / design choices
1608
+ - Multi-step investigations where approach may change
1609
+
1610
+ ---
1611
+
1612
+ ## MCP Tool Routing
1613
+
1614
+ | Signal | Preferred Tool |
1615
+ |---|---|
1616
+ | "latest", "2025/2026", "what's new" | Tavily search |
1617
+ | Library/framework API question | Context7 |
1618
+ | URL content extraction | Tavily extract (use advanced for complex pages) |
1619
+
1620
+ Before web tools: check internal knowledge \u2192 Context7 \u2192 then Tavily.
1621
+ Before external lookups: check Vestige memory first.
1622
+
1623
+ To register MCP servers in Codex, add \`[mcp_servers.<name>]\` tables to
1624
+ \`~/.codex/config.toml\`. STDIO transport is the documented default.
1625
+
1626
+ ---
1627
+
1628
+ ## Search Preference
1629
+
1630
+ Always prefer \`rg\` (ripgrep) over grep/find. Faster, respects .gitignore, recursive by default.
1631
+
1632
+ ---
1633
+
1634
+ ## Security
1635
+
1636
+ - Verify nonce usage and input sanitization in WordPress PHP code
1637
+ - Never concatenate user input directly into SQL \u2014 use parameterized queries
1638
+ - Check for XSS, CSRF, and OWASP top 10 vulnerabilities in written code
1639
+
1640
+ ---
1641
+
1642
+ ## Code Style
1643
+
1644
+ - Don't create custom FP utility functions (pipe, compose, curry) \u2014 use language-native patterns or established libraries
1645
+ - In WordPress JavaScript context, use jQuery patterns when jQuery is already loaded
1646
+ - Prefer Bootstrap utility classes over custom CSS overrides
1647
+ - Run \`composer dump-autoload\` after creating new PHP files
1648
+
1649
+ ---
1650
+
1651
+ ## Documentation
1652
+
1653
+ Follow the three-tier documentation system:
1654
+ - **Active** \u2014 Living docs, kept current (README, API docs, architecture)
1655
+ - **Archive** \u2014 Historical reference, rarely updated (decisions, post-mortems)
1656
+ - **Transient** \u2014 Ephemeral, git-ignored (session notes, scratch)
1657
+ `;
1658
+ }
1659
+ var CodexAdapter = class {
1660
+ name = "codex";
1661
+ displayName = "OpenAI Codex CLI";
1662
+ configDir = CODEX_DIR;
1663
+ detect() {
1664
+ return existsSync6(CODEX_DIR);
1665
+ }
1666
+ preview(sourceDir) {
1667
+ const skillItems = SKILLS_TO_INSTALL.map((skill) => ({
1668
+ name: skill,
1669
+ category: "skill",
1670
+ destPath: join6(CODEX_SKILLS_DIR, skill),
1671
+ exists: existsSync6(join6(CODEX_SKILLS_DIR, skill))
1672
+ })).filter((item) => existsSync6(join6(sourceDir, "skills", item.name)));
1673
+ const agentsDir = join6(sourceDir, "agents");
1674
+ const agentItems = existsSync6(agentsDir) ? readdirSync6(agentsDir).filter((f) => f.endsWith(".md")).map((file) => ({
1675
+ name: file.replace(/\.md$/, ""),
1676
+ category: "agent",
1677
+ destPath: join6(CODEX_AGENTS_DIR, file),
1678
+ exists: existsSync6(join6(CODEX_AGENTS_DIR, file))
1679
+ })) : [];
1680
+ const hookItems = HOOKS_TO_INSTALL.map((file) => ({
1681
+ name: file,
1682
+ category: "hook",
1683
+ destPath: join6(CODEX_HOOKS_DIR, file),
1684
+ exists: existsSync6(join6(CODEX_HOOKS_DIR, file))
1685
+ }));
1686
+ const translatorItem = {
1687
+ name: "hooks-translator.py",
1688
+ category: "hook",
1689
+ destPath: join6(CODEX_HOOKS_DIR, "hooks-translator.py"),
1690
+ exists: existsSync6(join6(CODEX_HOOKS_DIR, "hooks-translator.py"))
1691
+ };
1692
+ const guidelineItem = {
1693
+ name: "AGENTS.md",
1694
+ category: "guideline",
1695
+ destPath: CODEX_GUIDELINES_FILE,
1696
+ exists: existsSync6(CODEX_GUIDELINES_FILE)
1697
+ };
1698
+ return {
1699
+ platform: this.name,
1700
+ targetDir: CODEX_DIR,
1701
+ items: [...skillItems, ...agentItems, ...hookItems, translatorItem, guidelineItem]
1702
+ };
1703
+ }
1704
+ installSkills(sourceDir, exclude) {
1705
+ ensureDir(CODEX_SKILLS_DIR);
1706
+ const skills = exclude?.length ? SKILLS_TO_INSTALL.filter((s) => !exclude.includes(s)) : SKILLS_TO_INSTALL;
1707
+ for (const skill of skills) {
1708
+ const src = join6(sourceDir, skill);
1709
+ if (existsSync6(src) && statSync6(src).isDirectory()) {
1710
+ copyDirRecursive(src, join6(CODEX_SKILLS_DIR, skill));
1711
+ log.step(`skill: ${skill}`);
1712
+ }
1713
+ }
1714
+ }
1715
+ installAgents(sourceDir, exclude) {
1716
+ ensureDir(CODEX_AGENTS_DIR);
1717
+ const entries = readdirSync6(sourceDir).filter((f) => f.endsWith(".md")).filter((f) => !exclude?.includes(f.replace(/\.md$/, "")));
1718
+ for (const file of entries) {
1719
+ const content = readFileSync5(join6(sourceDir, file), "utf8");
1720
+ const transformed = transformAgentForCodex(content);
1721
+ writeFileSync5(join6(CODEX_AGENTS_DIR, file), transformed);
1722
+ log.step(`agent: ${file}`);
1723
+ }
1724
+ }
1725
+ installGuidelines(_pluginRoot) {
1726
+ ensureDir(CODEX_DIR);
1727
+ writeFileSync5(CODEX_GUIDELINES_FILE, generateCodexAgentsMd());
1728
+ log.step(`guidelines: ${CODEX_GUIDELINES_FILE}`);
1729
+ }
1730
+ installHooks(sourceDir, exclude) {
1731
+ ensureDir(CODEX_HOOKS_DIR);
1732
+ const hooks = exclude?.length ? HOOKS_TO_INSTALL.filter((f) => !exclude.includes(f)) : HOOKS_TO_INSTALL;
1733
+ for (const file of hooks) {
1734
+ const src = join6(sourceDir, file);
1735
+ if (existsSync6(src)) {
1736
+ copyFileSync5(src, join6(CODEX_HOOKS_DIR, file));
1737
+ log.step(`hook: ${file}`);
1738
+ }
1739
+ }
1740
+ const shimSrc = join6(__dirname4, "hooks-translator.py");
1741
+ if (!existsSync6(shimSrc)) {
1742
+ throw new Error(`hooks-translator.py not found at ${shimSrc} \u2014 packaging error`);
1743
+ }
1744
+ copyFileSync5(shimSrc, join6(CODEX_HOOKS_DIR, "hooks-translator.py"));
1745
+ log.step("hook: hooks-translator.py (shim)");
1746
+ const hooksConfig = generateCodexHooksConfig();
1747
+ mergeCodexHooksConfig(CODEX_HOOKS_CONFIG, hooksConfig);
1748
+ log.step("hook: hooks.json (generated for Codex CLI)");
1749
+ }
1750
+ postInstall() {
1751
+ log.info("OpenAI Codex CLI install complete. Verify:");
1752
+ log.info(` Skills: ${CODEX_SKILLS_DIR}`);
1753
+ log.info(` Agents: ${CODEX_AGENTS_DIR}`);
1754
+ log.info(` Hooks: ${CODEX_HOOKS_DIR}`);
1755
+ log.info(` Hooks cfg: ${CODEX_HOOKS_CONFIG}`);
1756
+ log.info(` Guidelines: ${CODEX_GUIDELINES_FILE}`);
1757
+ log.info("");
1758
+ log.info("MCP servers are not auto-configured. To register them, add");
1759
+ log.info(` [mcp_servers.<name>] command = "..." args = [...]`);
1760
+ log.info(`tables to ${CODEX_CONFIG_TOML} (STDIO transport).`);
1761
+ log.info("");
1762
+ log.info("If a hook silently fails to fire, the Codex tool name may have changed \u2014");
1763
+ log.info("update TOOL_MAP in platforms/codex/adapter.ts and reinstall.");
1764
+ }
1765
+ };
1766
+ function mergeCodexHooksConfig(configPath, newConfig) {
1767
+ let existing = {};
1768
+ if (existsSync6(configPath)) {
1769
+ try {
1770
+ const content = readFileSync5(configPath, "utf8");
1771
+ existing = JSON.parse(content);
1772
+ } catch {
1773
+ existing = {};
1774
+ }
1775
+ }
1776
+ if (!existing.hooks) {
1777
+ existing.hooks = {};
1778
+ }
1779
+ const existingHooks = existing.hooks;
1780
+ const incomingHooks = newConfig.hooks;
1781
+ for (const [event, entries] of Object.entries(incomingHooks)) {
1782
+ if (!existingHooks[event]) {
1783
+ existingHooks[event] = entries;
1784
+ continue;
1785
+ }
1786
+ const userEntries = existingHooks[event].filter(
1787
+ (entry) => !entry.hooks?.some((h) => h.command?.includes("hooks-translator.py"))
1788
+ );
1789
+ existingHooks[event] = [...userEntries, ...entries];
1790
+ }
1791
+ writeFileSync5(configPath, JSON.stringify(existing, null, 2) + "\n");
1792
+ }
1793
+
1428
1794
  // platforms/shared/detector.ts
1429
1795
  var ADAPTERS = [
1430
1796
  new ClaudeAdapter(),
1431
1797
  new JunieAdapter(),
1432
1798
  new GeminiAdapter(),
1433
- new GhCopilotAdapter()
1799
+ new GhCopilotAdapter(),
1800
+ new CodexAdapter()
1434
1801
  ];
1435
1802
  function detectPlatforms() {
1436
1803
  return ADAPTERS.map((adapter) => {
@@ -1444,28 +1811,28 @@ function getAdapter(name) {
1444
1811
  }
1445
1812
 
1446
1813
  // platforms/shared/installer.ts
1447
- import { join as join7 } from "path";
1814
+ import { join as join8 } from "path";
1448
1815
 
1449
1816
  // platforms/shared/types.ts
1450
- import { join as join6, resolve, dirname as dirname4 } from "path";
1451
- import { existsSync as existsSync6 } from "fs";
1452
- import { fileURLToPath as fileURLToPath3 } from "url";
1817
+ import { join as join7, resolve, dirname as dirname5 } from "path";
1818
+ import { existsSync as existsSync7 } from "fs";
1819
+ import { fileURLToPath as fileURLToPath4 } from "url";
1453
1820
  function findPackageRoot() {
1454
1821
  let dir;
1455
1822
  try {
1456
- dir = dirname4(fileURLToPath3(import.meta.url));
1823
+ dir = dirname5(fileURLToPath4(import.meta.url));
1457
1824
  } catch {
1458
1825
  dir = typeof __dirname !== "undefined" ? __dirname : resolve(".");
1459
1826
  }
1460
1827
  for (let i = 0; i < 10; i++) {
1461
- if (existsSync6(join6(dir, "package.json"))) return dir;
1462
- const parent = join6(dir, "..");
1828
+ if (existsSync7(join7(dir, "package.json"))) return dir;
1829
+ const parent = join7(dir, "..");
1463
1830
  if (parent === dir) break;
1464
1831
  dir = parent;
1465
1832
  }
1466
1833
  return process.cwd();
1467
1834
  }
1468
- var PLUGIN_SOURCE = join6(
1835
+ var PLUGIN_SOURCE = join7(
1469
1836
  findPackageRoot(),
1470
1837
  "plugins",
1471
1838
  "ima-claude"
@@ -1581,9 +1948,9 @@ ${colors.bright}Hooks${colors.reset} (${hooks.length} total)`
1581
1948
  return filter;
1582
1949
  }
1583
1950
  async function installForPlatform(adapter, options = {}) {
1584
- const skillsSource = join7(PLUGIN_SOURCE, "skills");
1585
- const agentsSource = join7(PLUGIN_SOURCE, "agents");
1586
- const hooksSource = join7(PLUGIN_SOURCE, "hooks");
1951
+ const skillsSource = join8(PLUGIN_SOURCE, "skills");
1952
+ const agentsSource = join8(PLUGIN_SOURCE, "agents");
1953
+ const hooksSource = join8(PLUGIN_SOURCE, "hooks");
1587
1954
  const preview = adapter.preview(PLUGIN_SOURCE);
1588
1955
  showPreview(adapter, preview.items);
1589
1956
  const filter = await promptExclusions(preview.items);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ima-claude",
3
- "version": "2.26.0",
4
- "description": "IMA's AI coding agent skills - FP patterns, architecture guidance, and team standards. Supports Claude Code, Junie CLI, Gemini CLI, and GitHub Copilot.",
3
+ "version": "2.27.0",
4
+ "description": "IMA's AI coding agent skills - FP patterns, architecture guidance, and team standards. Supports Claude Code, Junie CLI, Gemini CLI, GitHub Copilot, and OpenAI Codex CLI.",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup",
@@ -0,0 +1,452 @@
1
+ import { join, dirname } from "path";
2
+ import { homedir } from "os";
3
+ import { existsSync, readdirSync, statSync, readFileSync, writeFileSync, copyFileSync } from "fs";
4
+ import { fileURLToPath } from "url";
5
+
6
+ import type { PlatformAdapter, InstallItem, InstallPreview } from "../shared/types";
7
+ import { ensureDir, copyDirRecursive, log, SKILLS_TO_INSTALL, HOOKS_TO_INSTALL, HOOKS_CONFIG, VERSION } from "../../scripts/utils";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ // Honour CODEX_HOME if set; otherwise default to ~/.codex
13
+ const CODEX_DIR = process.env.CODEX_HOME ?? join(homedir(), ".codex");
14
+ const CODEX_SKILLS_DIR = join(CODEX_DIR, "skills");
15
+ const CODEX_AGENTS_DIR = join(CODEX_DIR, "agents");
16
+ const CODEX_HOOKS_DIR = join(CODEX_DIR, "hooks");
17
+ const CODEX_HOOKS_CONFIG = join(CODEX_DIR, "hooks.json");
18
+ const CODEX_GUIDELINES_FILE = join(CODEX_DIR, "AGENTS.md");
19
+ const CODEX_CONFIG_TOML = join(CODEX_DIR, "config.toml");
20
+
21
+ // Claude Code → Codex CLI tool name mapping
22
+ // Based on developers.openai.com/codex docs. "shell" is well-attested; the rest
23
+ // are best-known canonical names. Update here if a Codex release renames any.
24
+ const TOOL_MAP: Record<string, string> = {
25
+ Bash: "shell",
26
+ Read: "read",
27
+ Edit: "edit",
28
+ Write: "write",
29
+ Glob: "glob",
30
+ Grep: "grep",
31
+ LS: "list",
32
+ WebSearch: "web_search",
33
+ WebFetch: "fetch",
34
+ ExitPlanMode: "ExitPlanMode",
35
+ };
36
+
37
+ // Claude Code → Codex CLI hook event mapping
38
+ // Codex uses Claude Code's exact event names natively, so this is identity.
39
+ // Kept as a constant so future divergences are a single-point edit.
40
+ const EVENT_MAP: Record<string, string> = {
41
+ PreToolUse: "PreToolUse",
42
+ PostToolUse: "PostToolUse",
43
+ UserPromptSubmit: "UserPromptSubmit",
44
+ SessionStart: "SessionStart",
45
+ };
46
+
47
+ // Simple single-line YAML parser — same shape as Junie/Gemini/Copilot adapters.
48
+ function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
49
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
50
+ if (!match) return { frontmatter: {}, body: content };
51
+
52
+ const frontmatter: Record<string, string> = {};
53
+ for (const line of match[1].split("\n")) {
54
+ const colonIdx = line.indexOf(":");
55
+ if (colonIdx === -1) continue;
56
+ const key = line.slice(0, colonIdx).trim();
57
+ const value = line.slice(colonIdx + 1).trim();
58
+ if (key) frontmatter[key] = value;
59
+ }
60
+
61
+ return { frontmatter, body: match[2] };
62
+ }
63
+
64
+ function serializeFrontmatter(frontmatter: Record<string, string>, body: string): string {
65
+ const lines = Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`);
66
+ return `---\n${lines.join("\n")}\n---\n${body}`;
67
+ }
68
+
69
+ function mapToolName(claudeName: string): string {
70
+ return TOOL_MAP[claudeName] ?? claudeName;
71
+ }
72
+
73
+ function transformAgentForCodex(content: string): string {
74
+ const { frontmatter, body } = parseFrontmatter(content);
75
+
76
+ // Drop permissionMode (Codex uses global approval_policy / sandbox_mode)
77
+ // Drop model (Codex uses its own model selection)
78
+ const { permissionMode: _perm, model: _model, ...kept } = frontmatter;
79
+
80
+ // Map tool names in the tools field if present
81
+ if (kept.tools) {
82
+ const mapped = kept.tools
83
+ .split(",")
84
+ .map((t) => t.trim())
85
+ .map(mapToolName)
86
+ .join(", ");
87
+ kept.tools = mapped;
88
+ }
89
+
90
+ return serializeFrontmatter(kept, body);
91
+ }
92
+
93
+ function translateMatcher(matcher: string): string {
94
+ // MCP tool matchers pass through unchanged; only map built-in Claude tool names
95
+ return TOOL_MAP[matcher] ?? matcher;
96
+ }
97
+
98
+ function translateHookCommand(command: string): string {
99
+ // Rewrite hook commands to route through the translator shim
100
+ // Original: python3 ~/.claude/hooks/some_hook.py
101
+ // Codex: python3 ~/.codex/hooks/hooks-translator.py ~/.codex/hooks/some_hook.py
102
+ const translatorPath = join(CODEX_HOOKS_DIR, "hooks-translator.py");
103
+
104
+ const match = command.match(/python3\s+.*\/([^/\s]+\.py)(\s.*)?$/);
105
+ if (!match) return command;
106
+
107
+ const scriptName = match[1];
108
+ const trailingArgs = match[2] ?? "";
109
+ return `python3 ${translatorPath} ${join(CODEX_HOOKS_DIR, scriptName)}${trailingArgs}`;
110
+ }
111
+
112
+ function generateCodexHooksConfig(): Record<string, unknown> {
113
+ const codexHooks: Record<string, unknown[]> = {};
114
+
115
+ for (const [claudeEvent, hookEntries] of Object.entries(HOOKS_CONFIG.hooks)) {
116
+ const codexEvent = EVENT_MAP[claudeEvent] ?? claudeEvent;
117
+
118
+ codexHooks[codexEvent] = (hookEntries as Array<{ matcher?: string; hooks: Array<{ type: string; command: string }> }>).map(
119
+ (entry) => {
120
+ const translated: Record<string, unknown> = {};
121
+
122
+ if (entry.matcher) {
123
+ translated.matcher = translateMatcher(entry.matcher);
124
+ }
125
+
126
+ translated.hooks = entry.hooks.map((h) => ({
127
+ type: h.type,
128
+ command: translateHookCommand(h.command),
129
+ }));
130
+
131
+ return translated;
132
+ }
133
+ );
134
+ }
135
+
136
+ return { hooks: codexHooks };
137
+ }
138
+
139
+ function generateCodexAgentsMd(): string {
140
+ return `# ima-claude: AI Coding Agent Guidelines
141
+
142
+ > Generated by ima-claude v${VERSION} for OpenAI Codex CLI.
143
+ > Source: https://github.com/Soabirw/ima-claude
144
+
145
+ This file is auto-loaded by Codex at session start (\`AGENTS.md\` convention).
146
+ Per-directory \`AGENTS.md\` files in your project tree merge on top of this one,
147
+ with closer-to-cwd files taking precedence.
148
+
149
+ ## Default Persona: The Practitioner
150
+
151
+ A 25-year software development veteran. FP-first, composition-minded, anti-over-engineering.
152
+ Uses "we" not "I" — collaborative, humble, light-hearted. "Slow is smooth, smooth is fast."
153
+
154
+ **Philosophy**: Simple > Complex | Evidence > Assumptions | Native > Utilities | MVP > Enterprise
155
+
156
+ ---
157
+
158
+ ## Memory Routing
159
+
160
+ | Store what | Where | Why |
161
+ |---|---|---|
162
+ | Decisions, preferences, patterns, bugs | Vestige \`smart_ingest\` | Fades naturally if not referenced |
163
+ | Reference material (docs, standards, PRDs) | Qdrant \`qdrant-store\` | Permanent library |
164
+ | Session state, task progress | Serena \`write_memory\` | Project-scoped workbench |
165
+ | Future reminders | Vestige \`intention\` | Surfaces at next session |
166
+
167
+ At session start, check memory before asking questions:
168
+ - Vestige: search for user preferences and project context
169
+ - Vestige: check for pending reminders/intentions
170
+ - Serena: list memories if in a Serena-activated project
171
+
172
+ Auto-store: "I prefer..." → Vestige preference. "Let's go with X because..." → Vestige decision. "The reason this failed..." → Vestige bug.
173
+
174
+ After completing work: store outcome in Vestige, reference material in Qdrant, session state in Serena.
175
+
176
+ ---
177
+
178
+ ## Orchestrator Protocol
179
+
180
+ You are the Orchestrator. Plan and delegate. Do NOT implement directly.
181
+ - Non-trivial work → task-planner (decompose) → task-runner (delegate)
182
+ - Trivial = single file, < 5 lines, no judgment calls
183
+
184
+ ---
185
+
186
+ ## Available Agents
187
+
188
+ Codex does not yet support a formal named-subagent dispatcher. Treat the agents
189
+ listed below as documented delegation patterns: when the work matches, scope a
190
+ session (or a per-directory \`AGENTS.md\` overlay) to that agent's persona.
191
+ Reference copies of each agent's full system prompt live in \`~/.codex/agents/\`.
192
+
193
+ | Agent | Use For |
194
+ |---|---|
195
+ | \`explorer\` | File discovery, codebase exploration |
196
+ | \`implementer\` | Feature dev, bug fixes, refactoring |
197
+ | \`reviewer\` | Code review, security audit, FP checks |
198
+ | \`tester\` | Test creation, TDD, debugging test failures |
199
+ | \`wp-developer\` | WordPress plugins, themes, WP-CLI, forms |
200
+ | \`memory\` | Memory search, storage, consolidation |
201
+
202
+ ---
203
+
204
+ ## Code Navigation (Serena)
205
+
206
+ When Serena MCP is available, **prefer Serena over read/grep for code investigation.** 40-70% token savings.
207
+
208
+ | Instead of | Use |
209
+ |---|---|
210
+ | Read file to understand structure | Serena get_symbols_overview |
211
+ | grep for class/function definition | Serena find_symbol |
212
+ | grep for callers/references | Serena find_referencing_symbols |
213
+
214
+ Use the read tool only when you need the actual implementation body of a known, specific symbol.
215
+
216
+ ---
217
+
218
+ ## Complex Reasoning
219
+
220
+ Use sequential thinking before acting on:
221
+ - Debugging / root cause analysis / "why is this failing"
222
+ - Trade-off evaluation / "which approach"
223
+ - Architectural decisions / design choices
224
+ - Multi-step investigations where approach may change
225
+
226
+ ---
227
+
228
+ ## MCP Tool Routing
229
+
230
+ | Signal | Preferred Tool |
231
+ |---|---|
232
+ | "latest", "2025/2026", "what's new" | Tavily search |
233
+ | Library/framework API question | Context7 |
234
+ | URL content extraction | Tavily extract (use advanced for complex pages) |
235
+
236
+ Before web tools: check internal knowledge → Context7 → then Tavily.
237
+ Before external lookups: check Vestige memory first.
238
+
239
+ To register MCP servers in Codex, add \`[mcp_servers.<name>]\` tables to
240
+ \`~/.codex/config.toml\`. STDIO transport is the documented default.
241
+
242
+ ---
243
+
244
+ ## Search Preference
245
+
246
+ Always prefer \`rg\` (ripgrep) over grep/find. Faster, respects .gitignore, recursive by default.
247
+
248
+ ---
249
+
250
+ ## Security
251
+
252
+ - Verify nonce usage and input sanitization in WordPress PHP code
253
+ - Never concatenate user input directly into SQL — use parameterized queries
254
+ - Check for XSS, CSRF, and OWASP top 10 vulnerabilities in written code
255
+
256
+ ---
257
+
258
+ ## Code Style
259
+
260
+ - Don't create custom FP utility functions (pipe, compose, curry) — use language-native patterns or established libraries
261
+ - In WordPress JavaScript context, use jQuery patterns when jQuery is already loaded
262
+ - Prefer Bootstrap utility classes over custom CSS overrides
263
+ - Run \`composer dump-autoload\` after creating new PHP files
264
+
265
+ ---
266
+
267
+ ## Documentation
268
+
269
+ Follow the three-tier documentation system:
270
+ - **Active** — Living docs, kept current (README, API docs, architecture)
271
+ - **Archive** — Historical reference, rarely updated (decisions, post-mortems)
272
+ - **Transient** — Ephemeral, git-ignored (session notes, scratch)
273
+ `;
274
+ }
275
+
276
+ export class CodexAdapter implements PlatformAdapter {
277
+ readonly name = "codex";
278
+ readonly displayName = "OpenAI Codex CLI";
279
+ readonly configDir = CODEX_DIR;
280
+
281
+ detect(): boolean {
282
+ return existsSync(CODEX_DIR);
283
+ }
284
+
285
+ preview(sourceDir: string): InstallPreview {
286
+ const skillItems: InstallItem[] = SKILLS_TO_INSTALL.map((skill) => ({
287
+ name: skill,
288
+ category: "skill" as const,
289
+ destPath: join(CODEX_SKILLS_DIR, skill),
290
+ exists: existsSync(join(CODEX_SKILLS_DIR, skill)),
291
+ })).filter((item) => existsSync(join(sourceDir, "skills", item.name)));
292
+
293
+ const agentsDir = join(sourceDir, "agents");
294
+ const agentItems: InstallItem[] = existsSync(agentsDir)
295
+ ? readdirSync(agentsDir)
296
+ .filter((f) => f.endsWith(".md"))
297
+ .map((file) => ({
298
+ name: file.replace(/\.md$/, ""),
299
+ category: "agent" as const,
300
+ destPath: join(CODEX_AGENTS_DIR, file),
301
+ exists: existsSync(join(CODEX_AGENTS_DIR, file)),
302
+ }))
303
+ : [];
304
+
305
+ const hookItems: InstallItem[] = HOOKS_TO_INSTALL.map((file) => ({
306
+ name: file,
307
+ category: "hook" as const,
308
+ destPath: join(CODEX_HOOKS_DIR, file),
309
+ exists: existsSync(join(CODEX_HOOKS_DIR, file)),
310
+ }));
311
+
312
+ const translatorItem: InstallItem = {
313
+ name: "hooks-translator.py",
314
+ category: "hook",
315
+ destPath: join(CODEX_HOOKS_DIR, "hooks-translator.py"),
316
+ exists: existsSync(join(CODEX_HOOKS_DIR, "hooks-translator.py")),
317
+ };
318
+
319
+ const guidelineItem: InstallItem = {
320
+ name: "AGENTS.md",
321
+ category: "guideline",
322
+ destPath: CODEX_GUIDELINES_FILE,
323
+ exists: existsSync(CODEX_GUIDELINES_FILE),
324
+ };
325
+
326
+ return {
327
+ platform: this.name,
328
+ targetDir: CODEX_DIR,
329
+ items: [...skillItems, ...agentItems, ...hookItems, translatorItem, guidelineItem],
330
+ };
331
+ }
332
+
333
+ installSkills(sourceDir: string, exclude?: string[]): void {
334
+ ensureDir(CODEX_SKILLS_DIR);
335
+ const skills = exclude?.length
336
+ ? SKILLS_TO_INSTALL.filter((s) => !exclude.includes(s))
337
+ : SKILLS_TO_INSTALL;
338
+ for (const skill of skills) {
339
+ const src = join(sourceDir, skill);
340
+ if (existsSync(src) && statSync(src).isDirectory()) {
341
+ copyDirRecursive(src, join(CODEX_SKILLS_DIR, skill));
342
+ log.step(`skill: ${skill}`);
343
+ }
344
+ }
345
+ }
346
+
347
+ installAgents(sourceDir: string, exclude?: string[]): void {
348
+ ensureDir(CODEX_AGENTS_DIR);
349
+ const entries = readdirSync(sourceDir)
350
+ .filter((f) => f.endsWith(".md"))
351
+ .filter((f) => !exclude?.includes(f.replace(/\.md$/, "")));
352
+ for (const file of entries) {
353
+ const content = readFileSync(join(sourceDir, file), "utf8");
354
+ const transformed = transformAgentForCodex(content);
355
+ writeFileSync(join(CODEX_AGENTS_DIR, file), transformed);
356
+ log.step(`agent: ${file}`);
357
+ }
358
+ }
359
+
360
+ installGuidelines(_pluginRoot: string): void {
361
+ ensureDir(CODEX_DIR);
362
+ writeFileSync(CODEX_GUIDELINES_FILE, generateCodexAgentsMd());
363
+ log.step(`guidelines: ${CODEX_GUIDELINES_FILE}`);
364
+ }
365
+
366
+ installHooks(sourceDir: string, exclude?: string[]): void {
367
+ ensureDir(CODEX_HOOKS_DIR);
368
+
369
+ const hooks = exclude?.length
370
+ ? HOOKS_TO_INSTALL.filter((f) => !exclude.includes(f))
371
+ : HOOKS_TO_INSTALL;
372
+ for (const file of hooks) {
373
+ const src = join(sourceDir, file);
374
+ if (existsSync(src)) {
375
+ copyFileSync(src, join(CODEX_HOOKS_DIR, file));
376
+ log.step(`hook: ${file}`);
377
+ }
378
+ }
379
+
380
+ const shimSrc = join(__dirname, "hooks-translator.py");
381
+ if (!existsSync(shimSrc)) {
382
+ throw new Error(`hooks-translator.py not found at ${shimSrc} — packaging error`);
383
+ }
384
+ copyFileSync(shimSrc, join(CODEX_HOOKS_DIR, "hooks-translator.py"));
385
+ log.step("hook: hooks-translator.py (shim)");
386
+
387
+ const hooksConfig = generateCodexHooksConfig();
388
+ mergeCodexHooksConfig(CODEX_HOOKS_CONFIG, hooksConfig);
389
+ log.step("hook: hooks.json (generated for Codex CLI)");
390
+ }
391
+
392
+ postInstall(): void {
393
+ log.info("OpenAI Codex CLI install complete. Verify:");
394
+ log.info(` Skills: ${CODEX_SKILLS_DIR}`);
395
+ log.info(` Agents: ${CODEX_AGENTS_DIR}`);
396
+ log.info(` Hooks: ${CODEX_HOOKS_DIR}`);
397
+ log.info(` Hooks cfg: ${CODEX_HOOKS_CONFIG}`);
398
+ log.info(` Guidelines: ${CODEX_GUIDELINES_FILE}`);
399
+ log.info("");
400
+ log.info("MCP servers are not auto-configured. To register them, add");
401
+ log.info(` [mcp_servers.<name>] command = "..." args = [...]`);
402
+ log.info(`tables to ${CODEX_CONFIG_TOML} (STDIO transport).`);
403
+ log.info("");
404
+ log.info("If a hook silently fails to fire, the Codex tool name may have changed —");
405
+ log.info("update TOOL_MAP in platforms/codex/adapter.ts and reinstall.");
406
+ }
407
+ }
408
+
409
+ function mergeCodexHooksConfig(
410
+ configPath: string,
411
+ newConfig: Record<string, unknown>
412
+ ): void {
413
+ let existing: Record<string, unknown> = {};
414
+
415
+ if (existsSync(configPath)) {
416
+ try {
417
+ const content = readFileSync(configPath, "utf8");
418
+ existing = JSON.parse(content);
419
+ } catch {
420
+ existing = {};
421
+ }
422
+ }
423
+
424
+ if (!existing.hooks) {
425
+ existing.hooks = {};
426
+ }
427
+ const existingHooks = existing.hooks as Record<string, unknown[]>;
428
+ const incomingHooks = (newConfig as Record<string, unknown>).hooks as Record<
429
+ string,
430
+ Array<{ matcher?: string; hooks: Array<{ command: string }> }>
431
+ >;
432
+
433
+ // Merge each event: strip prior ima-claude entries (identified by hooks-translator.py
434
+ // substring in any nested command), then append the new entries. User hooks survive.
435
+ for (const [event, entries] of Object.entries(incomingHooks)) {
436
+ if (!existingHooks[event]) {
437
+ existingHooks[event] = entries;
438
+ continue;
439
+ }
440
+
441
+ const userEntries = (
442
+ existingHooks[event] as Array<{ matcher?: string; hooks?: Array<{ command?: string }> }>
443
+ ).filter(
444
+ (entry) =>
445
+ !entry.hooks?.some((h) => h.command?.includes("hooks-translator.py"))
446
+ );
447
+
448
+ existingHooks[event] = [...userEntries, ...entries];
449
+ }
450
+
451
+ writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
452
+ }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env python3
2
+ """OpenAI Codex CLI → Claude Code tool name translator shim.
3
+
4
+ Sits between Codex CLI and ima-claude hook scripts, translating Codex
5
+ tool names to their Claude Code equivalents so existing hooks (which
6
+ read `tool_name="Bash"`, etc.) work unmodified.
7
+
8
+ Codex hook events (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart)
9
+ already match Claude Code's names, so only the `tool_name` field inside
10
+ the JSON payload needs rewriting.
11
+
12
+ Usage (in hooks.json):
13
+ python3 ~/.codex/hooks/hooks-translator.py ~/.codex/hooks/some_hook.py
14
+ """
15
+
16
+ import json
17
+ import subprocess
18
+ import sys
19
+
20
+ # Codex CLI → Claude Code tool name mapping (reverse of adapter TOOL_MAP)
21
+ CODEX_TO_CLAUDE = {
22
+ "shell": "Bash",
23
+ "read": "Read",
24
+ "edit": "Edit",
25
+ "write": "Write",
26
+ "glob": "Glob",
27
+ "grep": "Grep",
28
+ "list": "LS",
29
+ "web_search": "WebSearch",
30
+ "fetch": "WebFetch",
31
+ "ExitPlanMode": "ExitPlanMode",
32
+ }
33
+
34
+
35
+ def main():
36
+ if len(sys.argv) < 2:
37
+ print("Usage: hooks-translator.py <hook-script> [args...]", file=sys.stderr)
38
+ sys.exit(1)
39
+
40
+ target_script = sys.argv[1]
41
+ extra_args = sys.argv[2:]
42
+
43
+ try:
44
+ raw = sys.stdin.read()
45
+ data = json.loads(raw) if raw.strip() else {}
46
+ except json.JSONDecodeError as e:
47
+ print(f"hooks-translator: invalid JSON from stdin: {e}", file=sys.stderr)
48
+ sys.exit(1)
49
+
50
+ tool_name = data.get("tool_name", "")
51
+ if tool_name in CODEX_TO_CLAUDE:
52
+ data["tool_name"] = CODEX_TO_CLAUDE[tool_name]
53
+
54
+ translated = json.dumps(data)
55
+ result = subprocess.run(
56
+ ["python3", target_script] + extra_args,
57
+ input=translated,
58
+ stdout=None,
59
+ stderr=None,
60
+ text=True,
61
+ )
62
+
63
+ sys.exit(result.returncode)
64
+
65
+
66
+ if __name__ == "__main__":
67
+ main()
@@ -3,12 +3,14 @@ import { ClaudeAdapter } from "../claude/adapter";
3
3
  import { JunieAdapter } from "../junie/adapter";
4
4
  import { GeminiAdapter } from "../gemini/adapter";
5
5
  import { GhCopilotAdapter } from "../gh-copilot/adapter";
6
+ import { CodexAdapter } from "../codex/adapter";
6
7
 
7
8
  const ADAPTERS: PlatformAdapter[] = [
8
9
  new ClaudeAdapter(),
9
10
  new JunieAdapter(),
10
11
  new GeminiAdapter(),
11
12
  new GhCopilotAdapter(),
13
+ new CodexAdapter(),
12
14
  ];
13
15
 
14
16
  export function detectPlatforms(): DetectedPlatform[] {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ima-claude",
3
- "version": "2.26.0",
3
+ "version": "2.27.0",
4
4
  "description": "IMA's Claude Code skills for functional programming, architecture, and team standards. 63 skills, 24 hooks, default persona, 3-tier memory system.",
5
5
  "author": {
6
6
  "name": "IMA",