Codex++ Tweak System
Skill by ara.so — Codex Skills collection.
Codex++ is a tweak system for the Codex desktop app that lets you inject custom features, fix UI bugs, and manage extensions without rebuilding the app. It patches the local Codex installation to load a runtime that discovers and executes small ESM modules (tweaks) with full access to the Electron renderer process.
Installation
macOS/Linux (Homebrew)
brew install b-nnett/codex-plusplus/codexplusplus
codexplusplus install
macOS/Linux (Source Bootstrap)
curl -fsSL https://raw.githubusercontent.com/b-nnett/codex-plusplus/main/install.sh | bash
Windows (PowerShell)
irm https://raw.githubusercontent.com/b-nnett/codex-plusplus/main/install.ps1 | iex
Using Bun
bun install -g github:b-nnett/codex-plusplus
codexplusplus install
What the installer does:
- Backs up Codex.app to
~/.codex-plusplus/backup/ - Patches
app.asarto load the Codex++ runtime - Re-signs the app with a local signing identity (macOS)
- Installs a watcher that auto-repairs on Codex updates
- Installs default tweaks from GitHub releases
Installation flags:
--no-default-tweaks— Skip installing default tweaks--local— Use stable local signing identity (macOS)
Key Commands
# Check installation status
codexplusplus status
# Validate and repair installation
codexplusplus doctor
codexplusplus repair
# Update Codex++ to latest release
codexplusplus update
# Update Codex++ from development branch (advanced)
codexplusplus update --ref main
# Update Codex app itself (macOS - required for patched apps)
codexplusplus update-codex
# Create a new tweak from template
codexplusplus create-tweak my-tweak
# Validate tweak manifest and structure
codexplusplus validate-tweak ~/path/to/tweak
# Enter safe mode (disable all tweaks)
codexplusplus safe-mode
codexplusplus safe-mode --off
# Development mode (watch and reload)
codexplusplus dev
# Uninstall Codex++ completely
codexplusplus uninstall
User Data Directories
| OS | Location |
|---|---|
| macOS | ~/Library/Application Support/codex-plusplus/ |
| Linux | ~/.local/share/codex-plusplus/ |
| Windows | %APPDATA%/codex-plusplus/ |
Directory structure:
codex-plusplus/
├── runtime/ # Codex++ runtime code
├── tweaks/ # Installed tweaks
│ ├── my-tweak/
│ │ ├── manifest.json
│ │ └── index.js
├── config.json # Runtime configuration
└── backup/ # Codex.app backup
Creating a Tweak
Minimal Tweak Structure
my-tweak/
├── manifest.json
└── index.js
manifest.json
{
"id": "com.example.my-tweak",
"name": "My Tweak",
"version": "1.0.0",
"githubRepo": "username/my-tweak",
"author": "Your Name",
"description": "Adds custom functionality to Codex",
"minRuntime": "0.1.0"
}
Required fields:
id— Reverse-DNS unique identifiername— Display nameversion— Semver versiongithubRepo—owner/repofor update checksminRuntime— Minimum Codex++ runtime version
Basic Tweak (JavaScript)
// index.js
export default {
start(api) {
api.log.info('Tweak started');
// Add settings panel
api.settings.register({
id: 'my-tweak',
title: 'My Tweak Settings',
render: (root) => {
root.innerHTML = `
<div>
<h3>Custom Settings</h3>
<button id="test-btn">Click me</button>
</div>
`;
root.querySelector('#test-btn').addEventListener('click', () => {
api.log.info('Button clicked!');
});
}
});
},
stop() {
// Cleanup resources
}
};
TypeScript Tweak with Full API
// index.ts
import type { Tweak, CodexPlusPlusAPI } from "@codex-plusplus/sdk";
interface MyTweakConfig {
enabled: boolean;
customColor: string;
}
export default {
start(api: CodexPlusPlusAPI) {
// Access configuration
const config = api.config.get<MyTweakConfig>('my-tweak', {
enabled: true,
customColor: '#ff0000'
});
// Logging
api.log.info('Starting tweak with config:', config);
api.log.warn('This is a warning');
api.log.error('This is an error');
// DOM manipulation
const observer = new MutationObserver(() => {
const chatInput = document.querySelector('[contenteditable="true"]');
if (chatInput && config.enabled) {
chatInput.style.borderColor = config.customColor;
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Settings panel with persistence
api.settings.register({
id: 'my-tweak',
title: 'My Tweak',
render: (root) => {
root.innerHTML = `
<div class="tweak-settings">
<label>
<input type="checkbox" id="enabled" ${config.enabled ? 'checked' : ''}>
Enable custom styling
</label>
<label>
Custom color:
<input type="color" id="color" value="${config.customColor}">
</label>
<button id="save">Save</button>
</div>
`;
root.querySelector('#save')?.addEventListener('click', () => {
const enabled = (root.querySelector('#enabled') as HTMLInputElement).checked;
const customColor = (root.querySelector('#color') as HTMLInputElement).value;
api.config.set('my-tweak', { enabled, customColor });
api.log.info('Settings saved');
// Reload to apply changes
location.reload();
});
}
});
// Store observer for cleanup
(api as any)._observer = observer;
},
stop() {
const observer = (this as any)._observer;
if (observer) {
observer.disconnect();
}
}
} satisfies Tweak;
Advanced: Injecting Custom UI
export default {
start(api) {
// Wait for Codex UI to be ready
const injectCustomButton = () => {
const toolbar = document.querySelector('.chat-toolbar');
if (!toolbar) return;
const btn = document.createElement('button');
btn.textContent = '✨ Custom Action';
btn.className = 'toolbar-button';
btn.onclick = () => {
api.log.info('Custom action triggered');
// Your custom logic
};
toolbar.appendChild(btn);
};
// Observe for toolbar appearance
const observer = new MutationObserver(() => {
injectCustomButton();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
injectCustomButton(); // Try immediately
},
stop() {}
};
Keyboard Shortcuts Tweak
export default {
start(api) {
const handleKeydown = (e: KeyboardEvent) => {
// Cmd/Ctrl + Shift + K
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'k') {
e.preventDefault();
api.log.info('Custom shortcut triggered');
// Your action here
}
};
document.addEventListener('keydown', handleKeydown);
// Store for cleanup
(api as any)._keyHandler = handleKeydown;
},
stop() {
const handler = (this as any)._keyHandler;
if (handler) {
document.removeEventListener('keydown', handler);
}
}
};
Configuration
Runtime Config
Located at <user-data-dir>/config.json:
{
"enabledTweaks": [
"com.example.my-tweak",
"co.bennett.custom-keyboard-shortcuts"
],
"autoUpdate": true,
"safeMode": false,
"logLevel": "info"
}
Per-Tweak Config
Tweaks can store configuration using the API:
// Get config with defaults
const config = api.config.get('my-tweak', { theme: 'dark' });
// Update config
api.config.set('my-tweak', { theme: 'light' });
// Config is persisted automatically
Tweak Distribution & Updates
Publishing a Tweak
- Create a GitHub repository for your tweak
- Add tweak files (manifest.json, index.js/ts)
- Create a GitHub Release with a semver tag (e.g.,
v1.0.0) - Attach a
.zipof the tweak folder to the release
Release structure:
my-tweak-v1.0.0.zip
└── my-tweak/
├── manifest.json
└── index.js
Installing Tweaks
Users copy tweak folders to <user-data-dir>/tweaks/ and enable them in Settings → Tweaks.
Update Checking
Codex++ checks githubRepo in manifest.json for newer releases once per day. Users review release notes and manually update.
Common Patterns
Persistent State
let myState = api.config.get('my-tweak', { count: 0 });
function incrementCounter() {
myState.count++;
api.config.set('my-tweak', myState);
}
React to Codex Events
// Watch for new messages
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node instanceof HTMLElement && node.matches('.message')) {
api.log.info('New message detected');
}
});
});
});
observer.observe(document.querySelector('#chat-container'), {
childList: true,
subtree: true
});
CSS Injection
const style = document.createElement('style');
style.textContent = `
.custom-theme {
--primary-color: #00ff00;
}
.chat-message {
border-left: 3px solid var(--primary-color);
}
`;
document.head.appendChild(style);
// Store for cleanup
(api as any)._styleElement = style;
Troubleshooting
Codex won't launch after install
# Check status
codexplusplus status
# Repair installation
codexplusplus repair
# If repair fails, uninstall and reinstall
codexplusplus uninstall
codexplusplus install
Tweak not loading
# Validate tweak structure
codexplusplus validate-tweak ~/Library/Application\ Support/codex-plusplus/tweaks/my-tweak
# Check runtime logs (look for errors)
tail -f ~/Library/Logs/codex-plusplus/runtime.log
Safe mode (disable all tweaks)
codexplusplus safe-mode
# Launch Codex, fix issues, then:
codexplusplus safe-mode --off
macOS Gatekeeper issues
After first launch of re-signed Codex:
- System Preferences → Privacy & Security
- Click "Open Anyway" for Codex.app
- Or:
xattr -cr /Applications/Codex.app
Codex update breaks Codex++
# macOS: Use official updater
codexplusplus update-codex
# After Codex updates, watcher auto-repairs
# Or manually:
codexplusplus repair
Windows: Wrong app launches
Launch Codex++ from Start Menu/Desktop, not the Microsoft Store Codex shortcut. The Store version is unpatched.
Development workflow
# Watch for changes and auto-reload
codexplusplus dev
# Edit tweak files in:
# macOS: ~/Library/Application Support/codex-plusplus/tweaks/my-tweak/
# Reload Codex (Cmd+R / Ctrl+R) to see changes
Clear all config
# Remove config but keep tweaks
rm ~/Library/Application\ Support/codex-plusplus/config.json
# Full reset
rm -rf ~/Library/Application\ Support/codex-plusplus/
codexplusplus install
Security Notes
- Tweaks run with full Electron renderer privileges
- Only install tweaks from trusted sources
- Review tweak code before installing
githubRepoin manifest enables update notifications but doesn't auto-update- Codex++ never executes code without user consent
Example: Complete UI Tweak
// Dark mode toggle tweak
import type { Tweak } from "@codex-plusplus/sdk";
export default {
start(api) {
const config = api.config.get('dark-mode', { enabled: false });
const applyTheme = (dark: boolean) => {
document.body.classList.toggle('dark-theme', dark);
};
applyTheme(config.enabled);
api.settings.register({
id: 'dark-mode',
title: 'Dark Mode',
render: (root) => {
root.innerHTML = `
<label>
<input type="checkbox" id="toggle" ${config.enabled ? 'checked' : ''}>
Enable dark mode
</label>
`;
root.querySelector('#toggle')?.addEventListener('change', (e) => {
const enabled = (e.target as HTMLInputElement).checked;
api.config.set('dark-mode', { enabled });
applyTheme(enabled);
});
}
});
// Inject CSS
const style = document.createElement('style');
style.textContent = `
.dark-theme {
background: #1a1a1a;
color: #ffffff;
}
`;
document.head.appendChild(style);
(api as any)._style = style;
},
stop() {
document.body.classList.remove('dark-theme');
const style = (this as any)._style;
if (style) style.remove();
}
} satisfies Tweak;