Skip to content
Draft
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
37 changes: 37 additions & 0 deletions .github/workflows/windows-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Windows checks

on:
pull_request:
paths:
- 'windows/**'
- '.github/workflows/windows-check.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
check:
runs-on: windows-latest
defaults:
run:
working-directory: windows
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: windows/package-lock.json
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
windows/target
key: windows-check-${{ hashFiles('windows/Cargo.lock') }}
- run: npm ci
- run: npm run test:hooks
- run: cargo test --workspace --release --locked
- run: npm run pack
- run: ./scripts/test-relay.ps1
16 changes: 11 additions & 5 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,17 @@ Send newline-terminated JSON to the socket:

## Supported events

All standard Claude Code hook events are supported, **except `PermissionRequest`**:
approval cards are not yet implemented for third-party agents (only Claude Code gets
one). A `PermissionRequest` from an external agent is answered immediately with no
decision, so the relay writes nothing and the agent re-asks in its terminal.
Approval support for other agents will be added with Codex support.
Generic third-party integrations support standard Claude Code hook events,
**except `PermissionRequest`**. These requests receive no decision, so the relay
writes nothing and the external agent re-asks in its terminal.
On Windows, Codex now has a dedicated opt-in integration with approval cards;
other third-party agents still fall back to their own approval flow. See
[Windows Codex setup](../windows/README.md#codex). Codex hook commands must use
`coucou-hook.exe --agent codex <EventName>` with an explicit event so that the
relay can return neutral valid JSON for `Stop` and `SubagentStop` even when
Coucou is closed. Codex activity is filtered to metadata; its permission requests
carry complete original tool-argument JSON. The generic payload routing described
above does not by itself install or trust Codex hooks.

The pill lifecycle:

Expand Down
94 changes: 89 additions & 5 deletions windows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Mochi doesn't get a notch on a PC — so it lives at the top of your screen instead.**

Approve Claude Code permissions, watch your session work, drop a file, chat with Claude, keep an eye on your services — without leaving what you're doing.
Approve agent permissions, watch your session work, drop a file, chat with Claude or OpenAI, keep an eye on your services — without leaving what you're doing.

![Windows 10/11](https://img.shields.io/badge/Windows-10%2F11-0078D4?logo=windows)
![Tauri 2](https://img.shields.io/badge/Tauri-2-FFC131?logo=tauri&logoColor=black)
Expand Down Expand Up @@ -68,11 +68,95 @@ in time, Coucou stays quiet and Claude Code asks in the terminal as usual.

It works from any terminal — Windows Terminal, PowerShell, VS Code, Git Bash.

## Codex

Native Windows Codex sessions can use the same local relay as Claude Code. Codex
has its own pill, activity state and permission card. No API key is needed for
this integration; the built-in chat uses a separate Claude or OpenAI API key.

1. Open **Settings… → Codex → Install hooks…** and review the proposed changes.
2. Confirm the write. Coucou merges only its handlers into
`%CODEX_HOME%\hooks.json`, or `%USERPROFILE%\.codex\hooks.json` when
`CODEX_HOME` is unset, and creates a dated backup if the file exists.
3. In a Codex version with [hooks support](https://learn.chatgpt.com/docs/hooks),
review and trust those hooks using `/hooks` in the CLI. Restart or open a new
session as needed. Installing hooks does not grant trust, and Coucou never
changes Codex's `config.toml`, authentication or approval policy.

The supported events are `SessionStart`, `UserPromptSubmit`, `PreToolUse`,
`PostToolUse`, `PermissionRequest`, `Stop`, `SubagentStart`, `SubagentStop`,
`Interrupt` and `SessionEnd`. This integration is for local, native Windows
sessions. A Windows named pipe is not available to WSL or remote/cloud sessions.
Hosted tools may not emit local tool hooks; the indicator is not a complete
audit trail.

Ordinary Codex activity forwards only local session identity, working directory,
event and tool names. It does not forward prompts, tool arguments/results,
transcript paths or assistant messages. A **permission request includes the exact
tool arguments**, because you need to see what you are approving. Those details
can contain sensitive text: review them locally and do not share screenshots of
the card casually. Nothing in this integration sends the events to a network
service or reads the session transcript.

The relay labels Codex packets with `coucou_agent: "codex"`; an absent agent label
keeps the existing Claude behavior. A closed or unresponsive Coucou, declined
card, or unanswered request falls back to Codex's normal approval flow. Allow
and Deny apply only to that request; they do not create permanent approval rules.
Only one approval card is shown at a time; additional requests are returned to
their original agent.

To remove the integration, use **Codex → Uninstall hooks…**. Unrelated hooks,
including other handlers sharing the same event group, are preserved. Invalid
configuration or a file changed since the preview is refused rather than
overwritten.

### Checking a contribution

```powershell
npm ci
npm run test:hooks
cargo test --workspace --release --locked
npm run pack
# Close Coucou first: the smoke test uses its local named-pipe name.
pwsh -File ./scripts/test-relay.ps1
```

The relay smoke test exchanges synthetic events and approval decisions. It
does not execute the commands in those events or modify any agent configuration.

For visual review, open `/dev/codex-preview.html` in the Vite development server.
It uses synthetic events with the real handler and views, is excluded from the
application build, and never executes the displayed commands.

![Codex permission card with synthetic test arguments](dev/codex-preview.png)

## Chat and keys

**Settings… → Claude** takes your Anthropic API key. Keys live in the **Windows
Credential Manager**, never on disk and never in the interface — the island can
only ask whether a key exists. Same for every integration key.
Choose **Settings… → Chat → Provider**, then save the matching key under
**Claude** (Anthropic) or **OpenAI**. Claude remains the default for existing
installations. Each provider keeps its own model setting. OpenAI defaults to
`gpt-4.1-mini`; you can enter another Responses-compatible model available to
your API account. API usage is billed by the selected provider.

Keys are stored in the **Windows Credential Manager** (Secret Service on Linux),
not in preferences or chat history. After saving, the UI can only ask whether a
key exists. Requests and attachment encoding happen in Rust.

OpenAI uses `https://api.openai.com/v1/responses`, with `store: false` and local
in-memory conversation history. It supports multiple turns, UTF-8 text/code
attachments up to 200 KB, and PDFs/images up to 20 MiB. Supported image formats
are PNG, JPEG, WebP and GIF (the API supports non-animated GIFs). The selected
model must support the attachment type. Live web search is currently available
only in the Claude chat. No provider is contacted automatically as a fallback.

Changing the provider or its active model starts a fresh conversation and clears
the attached file. Old conversations are not forwarded to the newly selected
provider. A failed request does not enter the conversation history, and a late
response after reset is discarded.

To test with your own key: choose OpenAI, save your key, open **Ask Mochi**, send
a short question, then a follow-up referring to the answer. Check a small text
attachment separately. Never paste keys into issue reports or screenshots.

No telemetry. The only network requests Coucou makes are to the services you
configure yourself.
Expand Down Expand Up @@ -126,7 +210,7 @@ windows/
island/ state machine, hooks, integrations
views/ every island view
settings/ the settings window
src-tauri/ Rust backend: window, named pipe, Claude API, pollers
src-tauri/ Rust backend: window, named pipe, Claude/OpenAI APIs, pollers
hook/ coucou-hook.exe, the Claude Code relay
scripts/ icon generator
```
Expand Down
17 changes: 17 additions & 0 deletions windows/dev/codex-preview.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Coucou — Codex hook preview</title>
<style>#preview-controls { pointer-events:auto; padding:12px; background:#24272d; } #preview-controls button { padding:8px 12px; margin:8px 4px 8px 0; cursor:pointer; }</style></head>
<body>
<div id="root"></div>
<aside id="preview-controls" style="position:fixed;bottom:24px;left:24px;color:#eee;font:14px system-ui;z-index:9999">
<p>Local Codex preview · synthetic events · no commands execute</p>
<button id="preview-activity">Activity</button>
<button id="preview-approval">Permission request</button>
<button id="preview-finished">Finished</button>
<button id="preview-interrupt">Interrupted</button>
<p id="preview-status"></p>
</aside>
<script type="module" src="./codex-preview.ts"></script>
</body>
</html>
Binary file added windows/dev/codex-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions windows/dev/codex-preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Browser-only visual fixture. Excluded from the application bundle by Vite's
// explicit entry points. The real hook handler and views receive synthetic data.
import "../src/style.css";
import { State } from "../src/core/state";
import { Island } from "../src/island/island";
import { createHookHandlers } from "../src/island/hooks";

document.body.style.background = "#24272d";
State.settings.soundEnabled = false;
State.settings.activeIntegrations = [];
State.loadIntegrationTasks();
State.setFocus("integration_codex");
const island = new Island(document.getElementById("root")!);
island.applySettings();
island.launch();
const handlers = createHookHandlers(island);
let turn = 0;
const status = () => {
document.getElementById("preview-status")!.textContent =
State.pendingApproval ? "Synthetic permission request is active" : `Preview: ${State.view}`;
};
const send = (event: string, fields = {}) => handlers.handle({
coucou_agent: "codex", session_id: "preview-session", turn_id: `preview-${turn}`,
cwd: "C:/demo/coucou", hook_event_name: event, ...fields,
});
const activity = () => {
if (State.pendingApproval) handlers.approvalEnded(State.pendingApproval.requestId);
turn++;
send("UserPromptSubmit");
send("PreToolUse", { tool_name: "Bash" });
island.alert("overview");
status();
};
document.getElementById("preview-activity")!.addEventListener("click", activity);
document.getElementById("preview-approval")!.addEventListener("click", () => {
activity();
const input = { command: "npm run test:hooks", description: "Run local hook integration tests" };
send("PermissionRequest", { request_id: `preview-request-${turn}`, tool_name: "Bash",
tool_input: input, coucou_tool_input_json: JSON.stringify(input, null, 2) });
status();
});
document.getElementById("preview-finished")!.addEventListener("click", () => { activity(); send("Stop"); status(); });
document.getElementById("preview-interrupt")!.addEventListener("click", () => { send("Interrupt"); island.alert("overview"); status(); });
status();
window.setTimeout(activity, 1800);
4 changes: 2 additions & 2 deletions windows/hook/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "coucou-hook"
description = "Relays Claude Code hook events to Coucou over a named pipe (Windows) or a Unix socket (Linux)"
description = "Relays Claude Code and Codex hooks over a Windows named pipe or Linux Unix socket"
version.workspace = true
edition.workspace = true
license.workspace = true
Expand All @@ -10,7 +10,7 @@ name = "coucou-hook"
path = "src/main.rs"

[dependencies]
serde_json = "1"
serde_json = { version = "1", features = ["raw_value"] }

# Just enough Win32 to know our own SID and to check who is serving the pipe.
[target.'cfg(windows)'.dependencies]
Expand Down
Loading