Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [1.1.0] - 2026-07-19

### Added

- **Codex app: thinking-depth dial.** Right-stick flick right/left steps the composer's reasoning effort via the app's user-assignable "Increase/Decrease reasoning effort" shortcuts (one-time setup: assign `⌃⌥=` / `⌃⌥-` in the app's Keyboard shortcuts — assignments are account-synced, so OpenMicro cannot set them at install). Emitted as discrete modifier key-downs; the app's shortcut listener ignores inline `using {...}` chords. Note the app's own Decrease shortcut stops one step short of the lowest effort level.
- **Codex app: right-stick click (R3) opens the model picker** (assign "Open model picker" to `⌃⇧M`), so the effort slider is visible while the dial steps it. New `r3` default binding emits the CSI-u encoding of Ctrl+Shift+M; terminal harnesses ignore it. Existing config files predate the binding — add `"r3": { "type": "keys", "bytes": "\u001b[109;6u" }` to Layer 1 to pick it up.

### Changed

- **Thinking dial defaults to right-stick flicks instead of cw/ccw rotation** — rotation proved finicky in live use. `rstick_cw`/`rstick_ccw` gestures remain available for remapping.

### Fixed

- **DualSense: y-axis was inverted vs the project convention** (`dualsense-ts` reports up as positive). Rotation gestures ran backward and up/down stick flicks were swapped on DualSense; both now match the documented directions on all controllers.

## [1.0.2] - 2026-07-19

### Fixed
Expand Down
45 changes: 24 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ openmicro codex-app # drive the Codex macOS desktop app

`openmicro codex-app` drives the Codex desktop app instead of a terminal CLI: new chat and prompt prefill use `codex://` deep links (no permission needed), while submit (Enter), reject (Esc), dictation (Ctrl+Shift+D), d-pad arrows, and Ctrl+U send keystrokes and need Accessibility permission for your terminal. A stick-flick prompt prefills the composer; press submit to send it. The app launches automatically and the terminal shows live status (controller, actions, agent state).

The thinking-depth dial (right-stick flicks) needs one-time setup: the app ships "Increase reasoning effort" and "Decrease reasoning effort" shortcuts unassigned, and assignments are account-synced so OpenMicro cannot set them for you. In the Codex app open Settings → Keyboard shortcuts, search "effort", and assign Increase to `⌃⌥=` (Control+Option+`=`, Ctrl+Alt on Windows) and Decrease to `⌃⌥-` — right-stick flicks then step the composer's reasoning effort. These chords avoid the app's Ctrl/Cmd `+`/`-` zoom shortcuts. Note the app's own Decrease shortcut stops one step short of the lowest effort level (also when pressed physically) — use the model picker for that. Optionally also assign "Open model picker" to `⌃⇧M` — right-stick click then opens the picker so you can watch the effort change. Assigned different chords? Remap `thinking_depth` in your OpenMicro config to matching `keys` bindings.

OpenMicro installs its lifecycle hooks automatically. If Codex reports that its hooks changed, open `/hooks` in Codex and trust the OpenMicro hooks.

Controller support depends on the exact device and connection. Check the [controller compatibility guide](CONTROLLERS.md) before you start, or run `openmicro doctor` to test your controller.
Expand All @@ -30,33 +32,34 @@ Controller support depends on the exact device and connection. Check the [contro

### Text control reference

| Control | Action |
| ----------------------------------------------- | ------------------------------------------------------------ |
| south (✕ / A) | Submit or confirm |
| east (○ / B) | Interrupt or dismiss |
| north (△ / Y) | Push-to-talk |
| west (□ / X) | Start a new chat |
| d-pad | Navigate TUI menus; repeats while held |
| left stick flick up / down / left / right | Review PR / debug / refactor / write tests |
| right stick rotate clockwise / counterclockwise | Increase / decrease thinking depth |
| R1 | Cycle modes (Shift+Tab) |
| R2 | Clear the input line (Ctrl+U) |
| touchpad click | Focus the next session by default, where supported |
| L2 | Cycle herdr spaces; touchpad then cycles agents in the space |

Stick flicks fire after returning to center; each quarter-turn steps thinking depth once. Hold L1 with south, east, west, north, d-pad up, or d-pad down to select one of six layers. The first layer ships with these defaults; the other five start empty. Other controls are unbound by default and remappable.
| Control | Action |
| ----------------------------------------- | ------------------------------------------------------------ |
| south (✕ / A) | Submit or confirm |
| east (○ / B) | Interrupt or dismiss |
| north (△ / Y) | Push-to-talk |
| west (□ / X) | Start a new chat |
| d-pad | Navigate TUI menus; repeats while held |
| left stick flick up / down / left / right | Review PR / debug / refactor / write tests |
| right stick flick right / left | Increase / decrease thinking depth |
| R1 | Cycle modes (Shift+Tab) |
| R2 | Clear the input line (Ctrl+U) |
| right stick click (R3) | Open the model picker (Codex app, with shortcut setup) |
| touchpad click | Focus the next session by default, where supported |
| L2 | Cycle herdr spaces; touchpad then cycles agents in the space |

Stick flicks fire after returning to center. Rotation gestures (`rstick_cw`/`rstick_ccw`, one step per quarter-turn) remain available for remapping. Hold L1 with south, east, west, north, d-pad up, or d-pad down to select one of six layers. The first layer ships with these defaults; the other five start empty. Other controls are unbound by default and remappable.

Voice and thinking-depth support varies by harness; see [OpenMicro feature parity](#openmicro-feature-parity).

## See it in action

Animated demos (rendered, not filmed) of the six core interactions:

| | |
| :----------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: |
| ![Lightbar status colors follow the focused session](assets/demo/status-leds.gif)<br>**Status lightbar** — executing, waiting, done, error | ![Face buttons submit, interrupt, dictate, and start a new chat](assets/demo/command-keys.gif)<br>**Command keys** — ✕ ○ △ □ |
| ![Left-stick flick launches a workflow prompt](assets/demo/workflow-flick.gif)<br>**Workflow flick** — stick up = review PR | ![Right-stick rotation steps thinking depth](assets/demo/thinking-dial.gif)<br>**Thinking dial** — quarter-turns step `/effort` |
| ![L1 plus a face button switches control layers](assets/demo/layers.gif)<br>**Layers** — hold L1 to switch | ![Touchpad click moves focus between sessions](assets/demo/multi-session.gif)<br>**Multi-session** — touchpad switches focus |
| | |
| :----------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------: |
| ![Lightbar status colors follow the focused session](assets/demo/status-leds.gif)<br>**Status lightbar** — executing, waiting, done, error | ![Face buttons submit, interrupt, dictate, and start a new chat](assets/demo/command-keys.gif)<br>**Command keys** — ✕ ○ △ □ |
| ![Left-stick flick launches a workflow prompt](assets/demo/workflow-flick.gif)<br>**Workflow flick** — stick up = review PR | ![Right-stick flicks step thinking depth](assets/demo/thinking-dial.gif)<br>**Thinking dial** — stick flicks step `/effort` |
| ![L1 plus a face button switches control layers](assets/demo/layers.gif)<br>**Layers** — hold L1 to switch | ![Touchpad click moves focus between sessions](assets/demo/multi-session.gif)<br>**Multi-session** — touchpad switches focus |

## What it gives you

Expand Down Expand Up @@ -104,7 +107,7 @@ OpenMicro creates `~/.openmicro/config.json` on first run. Edit bindings, layer
"bindings": {
"south": { "type": "accept" },
"lstick_up": { "type": "workflow", "presetId": "review-pr" },
"rstick_cw": { "type": "thinking_depth", "delta": 1 }
"rstick_right": { "type": "thinking_depth", "delta": 1 }
}
},
{ "name": "Layer 2", "color": { "r": 160, "g": 32, "b": 240 }, "bindings": {} },
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "openmicro",
"version": "1.0.2",
"version": "1.1.0",
"description": "Open-source replica of Work Louder's Codex Micro using a consumer gamepad as the physical AI-agent controller.",
"license": "MIT",
"author": "stephenleo",
Expand Down
15 changes: 11 additions & 4 deletions src/controller/dualsense-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,12 @@ export class DualSenseDriver extends EventEmitter implements ControllerHAL {
this.button(c.touchpad.button, 'touchpad')

this.axis(c.left.analog.x, 'left_x')
this.axis(c.left.analog.y, 'left_y')
// dualsense-ts negates hardware Y (up = +1); re-negate to the project
// convention (y positive = down) that the router and other drivers use —
// otherwise stick rotation direction and up/down flicks come out swapped.
this.axis(c.left.analog.y, 'left_y', true)
this.axis(c.right.analog.x, 'right_x')
this.axis(c.right.analog.y, 'right_y')
this.axis(c.right.analog.y, 'right_y', true)

c.left.trigger.on('change', (t: Trigger) => {
this.emit('data', {
Expand Down Expand Up @@ -107,9 +110,13 @@ export class DualSenseDriver extends EventEmitter implements ControllerHAL {
})
}

private axis(input: Axis, id: AxisId): void {
private axis(input: Axis, id: AxisId, invert = false): void {
input.on('change', (a: Axis) => {
this.emit('data', { kind: 'axis', axis: id, value: a.state } satisfies ControllerEvent)
this.emit('data', {
kind: 'axis',
axis: id,
value: invert ? -a.state : a.state,
} satisfies ControllerEvent)
})
}
}
22 changes: 21 additions & 1 deletion src/harness/codex-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const KEY_EQUIVALENTS: Record<string, string> = {
// Clear the input line. Electron text boxes ignore Cocoa's Ctrl+U kill-line,
// so select-all + delete (newline = sequential System Events statements).
'\x15': 'keystroke "a" using command down\nkey code 51',
// Ctrl+Shift+M (CSI-u) → the app's "Open model picker" shortcut, which users
// assign to ^⇧M (see README). Discrete modifier key downs — the app's
// shortcut listener ignores the inline `using {...}` form.
'\x1b[109;6u': 'key down control\nkey down shift\nkey code 46\nkey up shift\nkey up control',
}

// Whether the dictation key chord is currently held down (see push_to_talk).
Expand Down Expand Up @@ -218,7 +222,23 @@ export const codexAppHarness: Harness = {
case 'reject':
return { bytes: 'osascript:key code 53' } // Esc — stop generation / dismiss
case 'thinking_depth':
return null // documented gap: no reasoning-effort control in the app
// The app ships unassigned "Increase/Decrease reasoning effort"
// shortcuts (Settings → Keyboard shortcuts). Users assign ⌃⌥= and ⌃⌥-
// once (see README) — Ctrl+Alt on Windows, and no clash with the
// Ctrl/Cmd +/- zoom chords. Assignments are account-synced with no
// local store, so openmicro cannot set them at install time.
// Discrete modifier key downs, not `using {...}` — the app's shortcut
// listener needs real flagsChanged events and ignores the inline form
// (verified live; same pattern as the dictation chord).
return action.delta > 0
? {
bytes:
'osascript:key down control\nkey down option\nkey code 24\nkey up option\nkey up control',
} // ⌃⌥=
: {
bytes:
'osascript:key down control\nkey down option\nkey code 27\nkey up option\nkey up control',
} // ⌃⌥-
case 'focus_session': {
// Touchpad: cycle the current project's chats with wrap-around.
const next = cycleThread(scanDesktopThreads())
Expand Down
10 changes: 8 additions & 2 deletions src/layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,14 @@ export const DEFAULT_CONFIG: OpenMicroConfig = {
lstick_left: { type: 'workflow', presetId: 'refactor' },
lstick_right: { type: 'workflow', presetId: 'write-tests' },
l2: { type: 'herdr_space' },
rstick_cw: { type: 'thinking_depth', delta: 1 },
rstick_ccw: { type: 'thinking_depth', delta: -1 },
// Flicks, not cw/ccw rotation — rotation gestures proved finicky to
// perform reliably; left/right flicks are still remappable to cw/ccw.
rstick_right: { type: 'thinking_depth', delta: 1 },
rstick_left: { type: 'thinking_depth', delta: -1 },
// Ctrl+Shift+M (CSI-u encoding). In the Codex app this opens the model
// picker (user-assigned ^⇧M) so the thinking dial has a visual;
// terminal harnesses ignore the sequence.
r3: { type: 'keys', bytes: '\x1b[109;6u' },
touchpad: TOUCHPAD_CYCLE,
},
},
Expand Down
17 changes: 16 additions & 1 deletion test/harness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,25 @@ describe('codex-app harness', () => {
expect(codexAppHarness.resolveAction({ type: 'keys', bytes: '\x15' }, ctx)).toEqual({
bytes: 'osascript:keystroke "a" using command down\nkey code 51',
})
// Ctrl+Shift+M (CSI-u) → the app's user-assigned "Open model picker" chord.
expect(codexAppHarness.resolveAction({ type: 'keys', bytes: '\x1b[109;6u' }, ctx)).toEqual({
bytes:
'osascript:key down control\nkey down shift\nkey code 46\nkey up shift\nkey up control',
})
})

it('maps thinking_depth to the user-assigned effort shortcut chords', () => {
expect(codexAppHarness.resolveAction({ type: 'thinking_depth', delta: 1 }, ctx)).toEqual({
bytes:
'osascript:key down control\nkey down option\nkey code 24\nkey up option\nkey up control',
})
expect(codexAppHarness.resolveAction({ type: 'thinking_depth', delta: -1 }, ctx)).toEqual({
bytes:
'osascript:key down control\nkey down option\nkey code 27\nkey up option\nkey up control',
})
})

it('returns null for documented gaps and core-only actions', () => {
expect(codexAppHarness.resolveAction({ type: 'thinking_depth', delta: 1 }, ctx)).toBeNull()
expect(codexAppHarness.resolveAction({ type: 'keys', bytes: '\x07' }, ctx)).toBeNull()
expect(codexAppHarness.resolveAction({ type: 'workflow', presetId: 'x' }, ctx)).toBeNull()
expect(codexAppHarness.resolveAction({ type: 'layer', index: 1 }, ctx)).toBeNull()
Expand Down