# Writing tweaks A tweak is a folder containing `manifest.json` and an entry script. Drop it into your tweaks dir: - macOS: `~/Library/Application Support/codex-plusplus/tweaks/` - Linux: `~/.local/share/codex-plusplus/tweaks/` - Windows: `%APPDATA%/codex-plusplus/tweaks/` …then reload Codex (Cmd/Ctrl+R in the window, or restart the app). The Tweak Manager (in Settings → Tweaks) shows everything that loaded. ## Minimal example ``` my-tweak/ ├── manifest.json └── index.js ``` `manifest.json`: ```json { "id": "com.you.my-tweak", "name": "My Tweak", "version": "0.1.0", "githubRepo": "you/my-tweak", "description": "Does a thing.", "scope": "renderer" } ``` `index.js`: ```js module.exports = { start(api) { api.settings.register({ id: "main", title: "My Tweak", render(root) { root.innerHTML = "

hello

"; }, }); }, stop() {}, }; ``` ## Manifest fields | Field | Required | Notes | |---|---|---| | `id` | yes | Reverse-DNS-ish unique id. | | `name` | yes | Human display name. | | `version` | yes | Semver. | | `githubRepo` | yes | GitHub repository in `owner/repo` form. Used for daily release checks. | | `author` | no | Free-form. | | `description` | no | One-liner shown in the manager. | | `scope` | no | `"renderer"` (default), `"main"`, or `"both"`. | | `main` | no | Entry filename. Defaults to `index.js`/`.cjs`/`.mjs`. | | `minRuntime` | no | Semver of the minimum runtime your tweak needs. | ## Update checks Codex++ checks `https://api.github.com/repos///releases/latest` at most once per day per tweak. If the latest release tag is a higher semver than `version`, the Tweaks page shows **Update Available** and links to the GitHub release. Updates are never installed automatically. Users should review release notes, code changes, and repository ownership before manually replacing local tweak files. ## API surface Your tweak default-exports `{ start(api), stop?() }`. The shape of `api` depends on `scope`: - **renderer**: `api.settings`, `api.react`, `api.ipc`, `api.fs`, `api.storage`, `api.log` - **main**: `api.ipc.handle`, `api.fs`, `api.storage`, `api.log` - **both**: `start(api)` is called once per process; check `api.process` to disambiguate ### Settings (renderer) ```ts api.settings.register({ id: "section-id", title: "Visible title", description: "Optional subtitle", render(root) { // imperatively populate the root element however you like. // return a cleanup function or void. }, }); ``` Each registered section becomes a row inside Codex's Settings → Tweaks tab. ### React fiber utilities (renderer) ```ts const fiber = api.react.getFiber(domNode); // null if not React const card = api.react.findOwnerByName(node, "Card"); // by component displayName/name const el = await api.react.waitForElement("[data-testid=foo]", 5000); ``` These let you reach into Codex's React tree for advanced injection (reading props, locating a specific component instance). We deliberately do not give you a React reference — render your own UI imperatively or with your own bundled framework. ### Storage Per-tweak namespaced KV. Renderer storage uses `localStorage`; main storage is currently in-memory (will move to `/storage/.json` in a future release). ```ts api.storage.set("foo", 42); api.storage.get("foo", 0); ``` ### IPC Channels are namespaced by tweak id. Renderer: ```ts const off = api.ipc.on("event", (...args) => {}); api.ipc.send("event", payload); const result = await api.ipc.invoke("compute", input); ``` Main: ```ts api.ipc.handle("compute", (input) => /* ... */); ``` ### Filesystem sandbox Each tweak gets its own writable directory at `/tweak-data//`. Use `api.fs.read/write/exists` to access it. ## Lifecycle - `start(api)` is called once after the runtime decides this tweak should run. For renderer tweaks, this is after the page's DOM is ready. - `stop()` is called on: - app shutdown (main side) - the user disabling the tweak (planned) - hot reload (planned) Make `stop()` idempotent. Clean up DOM nodes you added, IPC listeners, timers. ## TypeScript If you want type checking, install the SDK and import types: ```sh npm i -D @codex-plusplus/sdk ``` ```ts import { defineTweak } from "@codex-plusplus/sdk"; export default defineTweak({ start(api) { /* ... */ }, stop() {}, }); ``` You'll need to bundle/transpile to JS yourself before dropping into the tweaks dir. The runtime does not transpile TS. ## Patterns ### Wait for a Codex element to exist ```ts const composer = await api.react.waitForElement("[data-testid='composer']"); composer.style.outline = "2px solid hotpink"; ``` ### Read props from a Codex component ```ts const node = document.querySelector("[data-testid='message-row']"); const fiber = api.react.getFiber(node); console.log(fiber?.memoizedProps); ``` ### Replace a button's behavior ```ts const btn = await api.react.waitForElement("button[aria-label='Send']"); const clone = btn.cloneNode(true); btn.replaceWith(clone); clone.addEventListener("click", (e) => { /* your behavior */ }, true); ``` ### Add a global keybind ```ts addEventListener("keydown", (e) => { if (e.metaKey && e.shiftKey && e.key === "K") doThing(); }); ``` ## Debugging - Open DevTools (View menu, or the Codex command palette). - Filter console for `[codex-plusplus]`. - Check `/log/main.log` for main-process errors. - `codex-plusplus doctor` from a terminal for installer/integrity issues.