Route Claude's desktop computer use through Codex's computer-use engine, one Codex session per Claude session so threads can drive apps in parallel. Toggle…

Every free resource from the Early AI-dopters Gumroad, mirrored here and kept in sync. Each folder holds the actual files (zips unpacked so you can browse them on GitHub), the resource content page, and a README. Where a resource came from a YouTube video, the video is paired to it. Watch or star this repo to catch every new drop.
180 published resources, grouped by topic and newest-first within each. Sync scheduled hourly. See the latest verified check below.
<!-- sync-status:start --> Last verified sync: October 09, 2026 at 05:12 AM EDT · 180/180 published resources accounted for; 313 mirrored attachments verified. Oversized attachments available on Gumroad: 1. Sync details.
<!-- sync-status:end -->
The newest resources with their videos. Updates automatically on every sync.
| Resource | Watch | Get it | |
|---|---|---|---|
| Jev vs OpenAI Decisions: Results + Setup Guide | - | Gumroad | |
| <a href="https://youtu.be/UcvD53jqHRU"><img src="https://i.ytimg.com/vi/UcvD53jqHRU/maxresdefault.jpg" width="200"></a> | Two Mods for Claude Code: Computer Use + Threads | ▶ Watch | Gumroad |
| <a href="https://youtu.be/uvIopT_2sY0"><img src="https://i.ytimg.com/vi/uvIopT_2sY0/maxresdefault.jpg" width="200"></a> | Claude Mods Starter Kit: 10 Mods + Build-Your-Own Guide | ▶ Watch | Gumroad |
| <a href="https://markkashef.gumroad.com/l/wisprclone"><img src="https://public-files.gumroad.com/q6j0hn2ue1kx8c4yedfxltj6ctiq" width="200"></a> | WisprFlow Clone: Mac App Source + Build Prompts | - | Gumroad |
| <a href="https://markkashef.gumroad.com/l/use-both-claude-codex-workflow-kit"><img src="https://public-files.gumroad.com/lpvokrqflyo89ahmuqj6b9udw1b3" width="200"></a> | Use Both: Claude + Codex Workflow Kit | - | Gumroad |
| <a href="https://youtu.be/z8My0bX2-ZU"><img src="https://i.ytimg.com/vi/z8My0bX2-ZU/maxresdefault.jpg" width="200"></a> | Your Own Jev: Prompt, Guide & Local AI Starter Kit | ▶ Watch | Gumroad |
| <a href="https://youtu.be/zZNm4zP_lEE"><img src="https://i.ytimg.com/vi/zZNm4zP_lEE/maxresdefault.jpg" width="200"></a> | Jev Starter Kit: Explainer + Live Playground | ▶ Watch | Gumroad |
| <a href="https://youtu.be/575v0WYJQsE"><img src="https://i.ytimg.com/vi/575v0WYJQsE/maxresdefault.jpg" width="200"></a> | Codex + Ollama Setup Kit | ▶ Watch | Gumroad |
| <a href="https://markkashef.gumroad.com/l/sf-ig-frontend-design-loop"><img src="https://public-files.gumroad.com/v3ormgouaorqmqdyfdbc5khgkis4" width="200"></a> | Frontend Design & Feedback Kit | - | Gumroad |
| <a href="https://markkashef.gumroad.com/l/sf-ig-voice-qualification"><img src="https://public-files.gumroad.com/5gvxgzzd9otxkkqu5gs3kiyhlc3b" width="200"></a> | Voice AI Qualification Checklist | - | Gumroad |
| Resource | What it is | Video | Get it |
|---|---|---|---|
| Dynamic Workflows Prompt Pack 🎛️ | Most people use Claude Code like a chatbot. | ▶ Watch | Gumroad |
| Polyskill Kit 🧩 | The Polyskill Meta Skill: A ready to install skill workspace. | ▶ Watch | Gumroad |
| The Skill Audit Prompt 🔍 | The Skill Audit Prompt: A copy paste prompt that tags the claude code guide subagent and runs a full structural review on any Claude Code skill you… | ▶ Watch | Gumroad |
| Claude Code Monthly Insights Skill 🛠️ | Monthly Insights Skill for Claude Code: A drop in skill that turns /insights into a full monthly review system. | ▶ Watch | Gumroad |
| One-Shot Skill Creation Prompt 🎥 | One Shot Skill Prompt: The exact polished prompt from the video to extract a comprehensive SOP from any screen recording using Gemini Video… | ▶ Watch | Gumroad |
| Claude Code Guide Prompts ⭐️ | 5 Level Prompt Blueprint: Copy paste prompts for every level of @claude code guide — from asking a basic question to having Claude fix its own broken… | ▶ Watch | Gumroad |
| The Council Skill for Claude Code 👑 | The Council Skill (.skill file): Drop it into Claude Code and immediately tap into OpenAI and Google models without leaving your terminal. | ▶ Watch | Gumroad |
| ClaudeClaw - Mega Prompt + Visual Guide 🦞 | 1. | ▶ Watch | Gumroad |
| Agent Team Prompts for Claude Code 👥 👥 | 7 Copy Paste Agent Team Prompts: Ready to use prompts that spawn multi agent teams in Claude Code for real business tasks not coding exercises. | ▶ Watch | Gumroad |
| Claude Code Skills Guide 📓 🤖 | Skill Examples (Bad vs Good): Two complete bad skills and two complete good skills side by side. | ▶ Watch | Gumroad |
| ChatGPT Atlas → n8n Builder Prompt ⚙️ | A copy paste Atlas Agent Mode meta prompt that clicks n8n “Build with AI”, drafts nodes, configures settings, injects an LLM system prompt (<800… | ▶ Watch | Gumroad |
| Claude Skills Factory Prompt 🏭 | Claude Code ready master prompt you can copy paste to scaffold complete skill folders, SKILL.md, sample prompt.md, and ZIP packaging for immediate… | ▶ Watch | Gumroad |
| ChatGPT Thinking Modes Analysis 🧠 | Full breakdown of ChatGPT’s new Light, Standard, Extended, Heavy thinking levels, mapped against speed, style, and best fit use cases. | ▶ Watch | Gumroad |
| Emoji Prompting Pack 🎨🤖 | 11 minute video showing where emoji prompts shine (clarity, speed, structure) and where they fail (bloat, misreads). | ▶ Watch | Gumroad |
| Universal AI Browser Prompts 🌐 | Copy paste Quick Start Template with all fields pre wired: PERSONA, AIM, RECIPIENTS, THEME, CONTEXT, TASK, VERIFICATION, OUTPUT, SOURCES. | ▶ Watch | Gumroad |
| Model Migration Master Prompt 🔄 | A refined master prompt that instantly compares any two AI models and gives you a step by step migration guide. | ▶ Watch | Gumroad |
| Agent Mode Vibe Coding 🔮 | A masterfully crafted prompt for lovable.dev, guiding an AI agent to autonomously build, refine, and troubleshoot a visually stunning, life changing… | ▶ Watch | Gumroad |
| Abacus AI Plug & Play Prompts 🤖 | 10 ready to use, high impact prompts designed for instant deployment with Abacus AI. | ▶ Watch | Gumroad |
| Agent Mode Prompt Pack 🧠📋 | 10 real world Agent Mode prompts copy pasted directly from the video—no filler, just the exact text that worked. | ▶ Watch | Gumroad |
| Scrape Anything with Apify MCP Connector | Ready to Deploy MCP Config Schema: A clean JSON schema preconfigured with Apify’s top scrapers—YouTube, TikTok, Instagram, Google Maps, X (Twitter)… | ▶ Watch | Gumroad |
| OpenAI Operator Sample Prompt 💻🌐 | Exact prompt blueprint for one n8n two phase workflow (Gmail trigger → OpenAI model calls → Gmail send) Step by step instructions to configure Gmail… | ▶ Watch | Gumroad |
| Perplexity Labs Prompts 🥼 🔬 | Five fully detailed, ready to use prompts tailored specifically for Perplexity Labs, enabling deep analysis of AI, robotics, AGI impacts, and… | ▶ Watch | Gumroad |
| GenSpark Prompt Vault (7 Examples) 🐣 | A curated set of high performance prompts optimized for GenSpark’s multi tool AI automation suite. | ▶ Watch | Gumroad |
| Gemini Vibe Coding Prompts 😎 💖 | Ready to use initial prompt optimized specifically for Gemini 2.5 Pro, ensuring precision and reducing guesswork. | ▶ Watch | Gumroad |
| Ultimate o3 Prompt Bundle 🔋 | 10 expertly crafted prompts tailored for leveraging OpenAI’s advanced o3 model. | ▶ Watch | Gumroad |
| 4o Image Generation Suite 🖼️ | A detailed Knowledge Base file loaded with expert guidelines, examples, and best practices for creating precise prompts that seamlessly replace… | ▶ Watch | Gumroad |
| n8n Prompt Engineer Workflow ✏️ | Comprehensive Prompt Engineering Workflows: Ready to use n8n workflows specifically optimized for OpenAI, Google Gemini, Anthropic Claude, and other… | ▶ Watch | Gumroad |
| Deep Research Prompt Examples 📖 | What You’ll Get: In Depth Notion Guide Sample inspiration prompts covering real world use cases—from vendor selection to cross language research—so… | ▶ Watch | Gumroad |
| ChatGPT Task Prompting Guide + Custom GPT 🤖✏️ | Discover a new feature in ChatGPT that can help you be that much more productive — ChatGPT Tasks. | ▶ Watch | Gumroad |
| Prompt Engineer Anything ✏️ 📸 🎬 🎵 | You'll be able to access the meta prompts I demonstrated in the video, to help give you a kickstart to your prompt engineering in the following… | ▶ Watch | Gumroad |
| Sora Zero-to-Hero Prompting Guide 🎬 | Comprehensive Sora Prompting Guide: A detailed breakdown of shot types, camera movements, lighting techniques, and advanced descriptors to create… | ▶ Watch | Gumroad |
| Prompt Compression Guide 🗂️ | With the Prompt Compression Guide, you’ll gain access to a comprehensive set of experimental tools and resources designed to help you explore prompt… | ▶ Watch | Gumroad |
| Blended Persona Prompt | Step into the shoes of a renowned speaker, blending the directness and passion of Gordon Ramsay, the empathy and emotional depth of Oprah Winfrey… | ▶ Watch | Gumroad |
| Sample Prompt Engineer Validation Prompt | Step into the role of a prompt engineer and master the art of creating detailed, effective, and succinct prompts that lead to precise outcomes. | ▶ Watch | Gumroad |
| DNA Extraction Prompt | Unlock the art of replicating any writing style with precision. | ▶ Watch | Gumroad |
| Prompt Battle Testing (Level 1 to Level 4) | Unlock the secrets of effective AI prompt engineering with this comprehensive Prompt Battle Testing toolkit. | ▶ Watch | Gumroad |
| Temperature & Top P Prompt + Google Sheet + Slides | Temperature vs. | ▶ Watch | Gumroad |
| Resource | What it is | Video | Get it |
|---|---|---|---|
| Master Canvas Guide for n8n Vibe Automation | Import ready n8n workflow JSON covering core triggers (Manual, Schedule, Webhook, Google Sheets), path splitters (If/Switch/Merge/Wait), and AI… | ▶ Watch | Gumroad |
| Claude Skills in n8n Workflow Template 🍳 | Import ready n8n template (Anthropic Agent Skills Workflows.json) for PPTX, PDF, DOCX, XLSX. | ▶ Watch | Gumroad |
| Claude Agent Skills Pack 🧰 | CSV → Slides automator: a ready to run skill that turns spreadsheets into presentation ready PowerPoints (tables + charts). | ▶ Watch | Gumroad |
| Agent Builder Starter Clone Kit 🧩 | Visual Workflow Builder Spec: full stack architecture, six node canvas (Start/Agent/Tool/Router/Transform/End), OpenAI Responses API integration (GPT… | ▶ Watch | Gumroad |
| Claude Code Agentic Architect 🧱 | A single, plug and play prompt that analyzes any scenario and designs a complete 1–3 agent system end to end. | ▶ Watch | Gumroad |
| n8n Data Tables Kit 🧠 | Import ready n8n workflow (.json) with three scenarios: Sheet→Table sync, AI Sales analysis, and a Sheets vs Data Tables speed test. | ▶ Watch | Gumroad |
| FULL Agentic Patterns Kit 🧠 | Direct link to the Agentic Design Patterns README with all 20+ patterns (Core, Advanced, System, Optimization, Strategic). | ▶ Watch | Gumroad |
| Anti-Shiny-Object Coach 🧭 | A custom GPT that cuts through tool overwhelm and gives 2–3 clear next steps. | ▶ Watch | Gumroad |
| Base44 & n8n Presentation Generator 🖼️ | A ready to import n8n workflow that connects directly to Gamma and auto generates professional slide decks in under 2 minutes. | ▶ Watch | Gumroad |
| n8n Text-to-Workflow Prompts ⚡️ | 3 ready to copy prompts built for n8n’s new Text to Workflow: Lead Qualification, Mock Calendly Webhook JSON, and Fireflies Transcript → Nurture… | ▶ Watch | Gumroad |
| AI Model Comparison Automation ⚖️ | Level 1 Workflow JSON (Workflow 1 of 2): Import ready n8n file that runs on a schedule for hands free model tests—its matching Judge workflow lives… | ▶ Watch | Gumroad |
| MCP Workflow Power Bundle 🤖 ☢️ | Level 1 Ready to Run Docs: Step by step Claude Desktop + Remote MCP Server setup documentation—no guesswork, just follow along and launch. | ▶ Watch | Gumroad |
| Meeting Prep n8n Workflow 🗓️ ➡️ 🎧 | A plug and play n8n workflow that transforms Calendly bookings into personalized audio briefings. | ▶ Watch | Gumroad |
| X Live Search Vibe Code Pack 🐦 | A comprehensive Cheat Sheet Guide (Google Doc) detailing every prompt and step to build the UI foundation, deploy a Supabase edge function, and… | ▶ Watch | Gumroad |
| Promptify Chrome Extension ⚙️ | Ready to run Chrome extension for instant prompt injection into Windsurf, Replit, Bolt.new, Lovable, and more. | ▶ Watch | Gumroad |
| Vibe Coding Starter Pack 🔥 | Four pro PDF cheat sheets for Lovable 2.0, Bolt + Stripe, Replit Agent v2, and Cursor AI—perfect side kicks while you watch the free YouTube… | ▶ Watch | Gumroad |
| OpenAI Image API n8n Agents 🤖 🖼️ | A complete n8n automation bundle to generate and edit high quality images using OpenAI's new Image API (gpt image 1, dall e 3, and dall e 2… | ▶ Watch | Gumroad |
| Manus AI n8n Automation Pack 🤖 🫰🏽 | 3 comprehensive sets of n8n workflow automations (10 automations per set), fully created by the autonomous Manus AI system. | ▶ Watch | Gumroad |
| Claude Project to n8n Workflow 🤖 | A complete prompt and setup guide to transform Claude project instructions into valid n8n JSON—no screenshots, no fluff, just working workflows. | ▶ Watch | Gumroad |
| Easy Fine-Tuning Automation ⚙️ | All In One JSONL Prompt A specialized prompt that auto generates exactly 50 training samples in JSONL format, perfectly tailored to your brand voice… | ▶ Watch | Gumroad |
| n8n Agent-to-Agent Workflow 🤖 | What You’ll Get Main AI Agent Creator Flow A powerful n8n workflow that dynamically builds AI agents on demand A secondary workflow that handles in… | ▶ Watch | Gumroad |
| Make.com to n8n Converter Automation 🔁 | What You’ll Get n8n Workflow Converter – A pre built n8n workflow that automatically ingests and converts Make.com JSON files. | ▶ Watch | Gumroad |
| Prompt-to-Agent n8n Guide 🦾 | What You’ll Get High Performance Prompt for Deep Research The exact prompt used in the video, proven to yield the best results when generating n8n… | ▶ Watch | Gumroad |
| Lovable Suite for AI-Powered App Building | What You’ll Get: Lovable Prompt Engineer Assistant A comprehensive, shell first guide with codeblock outputs that walks you through framing your… | ▶ Watch | Gumroad |
| [Email Automations U
hooks/register.tsx 372 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { CodexCuPending } from '../types'
5
6// Codex's computer-use engine is cua_repl from the ChatGPT app. The bridge tool reaches it
7// through a small daemon in ~/.claude/mcp/codex-cu that keeps one server alive on a Unix
8// socket. Codex asks before using each app; the daemon answers yes only for apps the person
9// allowed in the pane below, so approval works the same on every surface. (A function hook
10// cannot answer an MCP elicitation, so the session's own codex-cu connection is not used.)
11const SERVER = 'codex-cu'
12const CODEX_JS = 'mcp__codex-cu__js'
13// The mod's own tools. The plugin is not named codex-cu: its tools would then sit in the
14// codex-cu server's mcp__codex-cu__ namespace, which the engine refuses.
15const BRIDGE = 'mcp__codex-computer-use__cua'
16const BRIDGE_RESET = 'mcp__codex-computer-use__cua_reset'
17const CODEX_TOOLS = [CODEX_JS, 'mcp__codex-cu__js_reset', BRIDGE, BRIDGE_RESET]
18const OWN_COMPUTER_USE = /^mcp__computer-use__/
19const NODE = '/Applications/ChatGPT.app/Contents/Resources/cua_node/bin/node'
20const PANE = 'codex-approval'
21// The daemon this mod speaks to: one cua_repl server per Claude session (and per subagent),
22// so several sessions, such as threads, can drive different apps at the same time.
23const DAEMON_VERSION = 'v2'
24// The person's standing approvals, shared with the daemon:
25// { "apps": [...always allowed...], "autoApproveAll": true|false }.
26const ALWAYS_FILE = '.claude/mcp/codex-cu/always-allowed.json'
27
28type Standing = { apps: string[]; autoApproveAll: boolean }
29
30async function readStanding($: EngineInterface, home: string): Promise<Standing> {
31 try {
32 const parsed = JSON.parse(String(await $.fs.read(`${home}/${ALWAYS_FILE}`)))
33 return { apps: Array.isArray(parsed.apps) ? parsed.apps : [], autoApproveAll: parsed.autoApproveAll === true }
34 } catch {
35 return { apps: [], autoApproveAll: false }
36 }
37}
38
39async function readAlways($: EngineInterface, home: string): Promise<string[]> {
40 return (await readStanding($, home)).apps
41}
42
43async function writeAlways($: EngineInterface, home: string, apps: string[]) {
44 const standing = await readStanding($, home)
45 await $.fs.write(`${home}/${ALWAYS_FILE}`, `${JSON.stringify({ ...standing, apps }, null, 2)}\n`)
46}
47
48async function writeAutoApprove($: EngineInterface, home: string, autoApproveAll: boolean) {
49 const standing = await readStanding($, home)
50 await $.fs.write(`${home}/${ALWAYS_FILE}`, `${JSON.stringify({ ...standing, autoApproveAll }, null, 2)}\n`)
51}
52
53const pending = atom({ plugin: 'codex-computer-use', key: 'pending' } as const, null as CodexCuPending)
54const allowed = atom({ plugin: 'codex-computer-use', key: 'allowed' } as const, [] as string[])
55
56const ROUTING = `# Computer use goes through Codex (codex-cu mod is on)
57
58For any task that needs to see or operate a desktop app on this Mac, use Codex's computer-use engine through \`${BRIDGE}\` (reset with \`${BRIDGE_RESET}\`). The mcp__computer-use__* tools are blocked while this mode is on.
59
60How to drive it well:
61- It runs JavaScript in a persistent REPL with a \`cua\` object. First call (or after a reset): exactly one entry call, such as \`let app = await cua.getApp("Calculator");\`, and nothing else. Its result carries the API documentation and the app's accessibility tree. Read it before the next call and use only documented APIs.
62- Prefer element indices from the accessibility tree (\`app.click(12)\`, \`app.setValue(9, "text")\`) over coordinates, and keyboard shortcuts (\`app.pressKey("cmd+t")\`) where the app has them.
63- Batch deterministic steps in one call and end it with \`await app.getAXState()\`, which returns a diff of what changed. Re-read indices after every action; never reuse stale ones. Ask for \`{ disableDiffing: true }\` only when you need the full tree.
64- Return only what you need: observation calls print their result by default, so to filter, read with \`{ emit: false }\` (e.g. \`const t = await app.getAXState({ emit: false, disableDiffing: true })\`) and print just the relevant lines with \`nodeRepl.write(...)\`.
65- It works in the background with real clicks, typing and drags, without moving the person's cursor. There is no hover.
66- After any error or surprise, read the state before retrying: an action can fail yet still apply, and an app dialog may have opened. Answer a dialog that would change the person's settings or data with its least-change option (Not Now, Cancel) unless the task calls for it.
67- When the person wants to watch, bring the app to the front first (\`open -a <App>\` from the shell activates it), then pace the steps with short pauses so each change is visible.
68- Leave apps as you found them (close tabs or windows you opened) unless the task says otherwise.
69- Because it works in the background, the app stays behind the person's other windows. When the result is something they will want to look at (a note, a document, a form you filled), bring the app forward at the end: raise its window with \`performSecondaryAction(<window index>, "Raise")\` and say where the result is.
70- Each app needs approval. If the result says the person is being asked to approve an app, stop and wait for their answer; the mod tells you when they decide. Never try to get around a denial.
71- Other Claude sessions may be using computer use at the same time, each in its own Codex session. An app one of them is driving is leased to it: if the result says an app is in use by another session, work in a different app, or wait about a minute and retry. Never fight over an app.
72- Prefer purpose-built tools first (a CLI, an API, an MCP connector, the built-in browser or Claude in Chrome for web pages). Use Codex computer use for native apps and anything only the GUI can do.`
73
74export const register: Register = on => {
75 let isOn = true
76 let isUsedThisTurn = false
77 let home = ''
78
79 on('session.start', async ($, e, next) => {
80 isOn = (await $.store.get('enabled')) !== false
81 home = (await $.process.run(['printenv', 'HOME'])).stdout.trim()
82 try {
83 await $.command.register({
84 name: 'codex-cu',
85 description: 'Route computer use through Codex: on, off, status, auto on/off, forget <app>',
86 argumentHint: '[on|off|status|auto on|auto off|forget <app>]',
87 immediate: true,
88 })
89 } catch {
90 // The command file still offers it as /codex-computer-use:codex-cu.
91 }
92 $.ui.status(isOn ? 'Codex CU on' : undefined)
93 try {
94 await registerBridge()
95 } catch (error) {
96 $.ui.toast(`codex-cu: bridge tool not registered (${String(error).slice(0, 120)})`)
97 }
98
99 return next(e)
100
101 async function registerBridge() {
102 await $.tool.register({
103 name: 'cua',
104 description:
105 "Codex computer use: control native Mac apps in the background (real clicks, typing, drags; no hover). Runs JavaScript in Codex's persistent cua_repl with a `cua` object. First call (or after cua_reset): exactly one entry call, e.g. `let app = await cua.getApp(\"Calculator\");` or `await cua.getState();`. Its result carries the API docs and UI state; read it, then use only documented APIs. Apps need the person's approval.",
106 inputSchema: {
107 type: 'object',
108 properties: {
109 code: { type: 'string', description: 'JavaScript to execute using the initialized cua_repl runtime.' },
110 timeout_ms: { type: 'integer', minimum: 1, description: 'Execution timeout in milliseconds (default 30000).' },
111 title: { type: 'string', maxLength: 80, description: 'Short user-facing description of what the code does.' },
112 },
113 required: ['code'],
114 },
115 })
116 await $.tool.register({
117 name: 'cua_reset',
118 description: 'Reset the Codex cua_repl JavaScript session. Does not close apps or tabs.',
119 })
120 }
121 })
122
123 on('tool.call', { tool: [BRIDGE, BRIDGE_RESET] }, async ($, e) => {
124 const { tool, tool_use_id, agentId, ...args } = e as typeof e & { agentId?: string }
125 // its own Codex session per Claude session, and per subagent inside it
126 const session = `${await $.session.id()}${agentId ? `/${agentId}` : ''}`
127 const isReset = tool === BRIDGE_RESET
128 isUsedThisTurn = true
129 const fail = (text: string) => ({ result: text, text, isError: true as const })
130
131 // The daemon, started on first use.
132 const dir = `${home}/.claude/mcp/codex-cu`
133 const socketPath = `${dir}/daemon.sock`
134 let isUp = false
135 try {
136 const health = await $.http.fetch('http://codex-cu/health', { socketPath })
137 isUp = health.ok && health.text.trim() === `ok ${DAEMON_VERSION}`
138 // an older daemon shares one Codex session among every caller: replace it
139 if (health.ok && !isUp) {
140 await $.process.run(['pkill', '-f', `${dir}/daemon.mjs`])
141 await $.clock.sleep(300)
142 }
143 } catch {
144 isUp = false
145 }
146 if (!isUp) {
147 await $.process.run([
148 '/bin/sh',
149 '-c',
150 `nohup "${NODE}" "${dir}/daemon.mjs" > "${dir}/daemon.log" 2>&1 < /dev/null &`,
151 ])
152 for (let i = 0; i < 40 && !isUp; i++) {
153 await $.clock.sleep(250)
154 try {
155 isUp = (await $.http.fetch('http://codex-cu/health', { socketPath })).ok
156 } catch {
157 isUp = false
158 }
159 }
160 if (!isUp) return fail(`The Codex computer-use daemon did not start. See ${dir}/daemon.log.`)
161 }
162
163 const approve = await read($, allowed)
164 const res = await $.http.fetch(`http://codex-cu/${isReset ? 'reset' : 'js'}`, {
165 method: 'POST',
166 socketPath,
167 body: JSON.stringify(isReset ? { session } : { ...args, approve, session }),
168 })
169 const busy: { app: string; holder: string; idleSeconds: number }[] = JSON.parse(res.headers['x-codex-busy'] ?? '[]')
170 if (busy.length > 0) {
171 const { app, idleSeconds } = busy[0]
172 return fail(
173 `${app} is being driven by another Claude session right now (its last call was ${idleSeconds}s ago), so it is leased to that session. Work in a different app, or wait about a minute and retry; the lease lapses 2 minutes after that session's last call, or when it resets or ends. Do not try to get around it.`,
174 )
175 }
176 const declined: string[] = JSON.parse(res.headers['x-codex-declined'] ?? '[]')
177 if (declined.length > 0) {
178 const app = declined[0]
179 await update($, pending, () => ({ app }))
180 await $.ui.open({ id: PANE, title: 'Codex computer use', focus: true, closeOnEscape: true, rows: 6 })
181 return fail(
182 `Codex needs the person's approval to use ${app}. They are being asked in the codex-cu prompt now. Stop and wait: a message will say whether they allowed it, and then you can retry the same call.`,
183 )
184 }
185
186 return res.ok ? { result: res.text, text: res.text } : fail(res.text)
187 })
188
189 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
190 const { Box, Button, Text } = $.ui.resolve(e)
191 const ask = await read($, pending)
192 if (ask === null) return <Text dimColor>No approval waiting.</Text>
193 const app = ask.app
194
195 return (
196 <Box flexDirection="column">
197 <Text>Allow Codex computer use to use {app}?</Text>
198 <Text dimColor>It sends real clicks and keystrokes to the app in the background.</Text>
199 <Box>
200 <Button
201 key="allow"
202 label="This session"
203 hotkey="a"
204 variant="primary"
205 autoFocus
206 onPress={async () => {
207 await update($, allowed, list => (list.includes(app) ? list : [...list, app]))
208 await update($, pending, () => null)
209 await $.ui.close({ id: PANE })
210 await $.prompt.submit({ text: `I allowed Codex computer use to use ${app} for this session. Retry the last call and continue the task.`, asUser: true })
211 }}
212 />
213 <Text> </Text>
214 <Button
215 key="always"
216 label="Always allow"
217 hotkey="l"
218 onPress={async () => {
219 const always = await readAlways($, home)
220 if (!always.includes(app)) await writeAlways($, home, [...always, app])
221 await update($, allowed, list => (list.includes(app) ? list : [...list, app]))
222 await update($, pending, () => null)
223 await $.ui.close({ id: PANE })
224 await $.prompt.submit({ text: `I always allowed Codex computer use to use ${app}. Retry the last call and continue the task.`, asUser: true })
225 }}
226 />
227 <Text> </Text>
228 <Button
229 key="deny"
230 label="Deny"
231 hotkey="d"
232 role="dismiss"
233 onPress={async () => {
234 await update($, pending, () => null)
235 await $.ui.close({ id: PANE })
236 await $.prompt.submit({ text: `I denied Codex computer use for ${app}. Do not use ${app}; tell me another way or stop.`, asUser: true })
237 }}
238 />
239 </Box>
240 </Box>
241 )
242 })
243
244 // `/codex-cu` is registered at session start; the plugin's commands/codex-cu.md lists it in
245 // every menu as `/codex-computer-use:codex-cu`. Both land here.
246 on('command.run', { command: ['codex-cu', 'codex-computer-use:codex-cu'] }, async ($, e) => {
247 const arg = e.args.trim().toLowerCase()
248 if (arg === 'auto on' || arg === 'auto off') {
249 const isAuto = arg === 'auto on'
250 await writeAutoApprove($, home, isAuto)
251 return {
252 text: isAuto
253 ? 'Auto-approve is on: Codex computer use may use any app without asking (Codex\'s own safety and organization blocks still apply).'
254 : 'Auto-approve is off: apps not on the always-allowed list ask in the approval pane.',
255 }
256 }
257 if (arg.startsWith('forget')) {
258 const target = e.args.trim().slice('forget'.length).trim()
259 if (target === '') return { text: 'Usage: /codex-cu forget <app name> (or: all)' }
260 const always = await readAlways($, home)
261 const kept = target.toLowerCase() === 'all' ? [] : always.filter(one => one.toLowerCase() !== target.toLowerCase())
262 await writeAlways($, home, kept)
263 await update($, allowed, list => (target.toLowerCase() === 'all' ? [] : list.filter(one => one.toLowerCase() !== target.toLowerCase())))
264 return { text: kept.length === always.length ? `${target} was not on the always-allowed list.` : `Removed. Always allowed now: ${kept.length > 0 ? kept.join(', ') : 'none'}.` }
265 }
266 if (arg === 'on' || arg === 'off') {
267 isOn = arg === 'on'
268 await $.store.set('enabled', isOn)
269 $.ui.status(isOn ? 'Codex CU on' : undefined)
270 } else if (arg !== '' && arg !== 'status') {
271 return { text: 'Usage: /codex-cu [on|off|status|auto on|auto off|forget <app>]' }
272 }
273
274 const tools = await $.tool.list()
275 const isConnected = tools.some(tool => tool.name === CODEX_JS)
276 const apps = await read($, allowed)
277 const mode = isOn
278 ? 'On. Desktop computer use goes through Codex; my own computer-use tools are blocked.'
279 : 'Off. I use my own computer-use tools.'
280 const route = `Route: the ${BRIDGE} bridge through the codex-cu daemon (${DAEMON_VERSION}: one Codex session per Claude session, apps leased to one session at a time)${isConnected ? '; the codex-cu server is also connected directly' : ''}.`
281 const standing = await readStanding($, home)
282 const always = standing.apps
283 const autoLine = standing.autoApproveAll
284 ? 'Auto-approve: ON, every app is allowed without asking (/codex-cu auto off to ask again).'
285 : 'Auto-approve: off (/codex-cu auto on to allow every app without asking).'
286 const allowedLine = apps.length > 0 ? `Allowed this session: ${apps.join(', ')}.` : 'No apps allowed through the pane yet this session.'
287 const alwaysLine = `Always allowed: ${always.length > 0 ? always.join(', ') : 'none'} (revoke with /codex-cu forget <app>). Apps set to "Always" inside Codex are allowed too.`
288
289 let usersLine = 'Sessions using it now: none (the daemon is not running).'
290 try {
291 const res = await $.http.fetch('http://codex-cu/sessions', { socketPath: `${home}/.claude/mcp/codex-cu/daemon.sock` })
292 const me = await $.session.id()
293 const users: { session: string; busy: boolean; leases: string[]; idleSeconds: number }[] = res.ok ? JSON.parse(res.text) : []
294 usersLine = users.length === 0
295 ? 'Sessions using it now: none.'
296 : `Sessions using it now (each with its own Codex session): ${users
297 .map(u => `${u.session.startsWith(me) ? 'this session' : u.session.slice(0, 8)}${u.session.includes('/') ? ' (subagent)' : ''}${u.busy ? ' working' : ` idle ${u.idleSeconds}s`}${u.leases.length > 0 ? `, holds ${u.leases.join(', ')}` : ''}`)
298 .join('; ')}.`
299 } catch {
300 // daemon not running
301 }
302
303 return { text: `Codex computer use: ${mode}\n${route}\n${autoLine}\n${allowedLine}\n${alwaysLine}\n${usersLine}` }
304 })
305
306 // a session that ends frees its Codex sessions and app leases at once
307 on('session.end', async ($, e, next) => {
308 const done = await next(e)
309 try {
310 await $.http.fetch('http://codex-cu/end', {
311 method: 'POST',
312 socketPath: `${home}/.claude/mcp/codex-cu/daemon.sock`,
313 body: JSON.stringify({ session: e.sessionId }),
314 })
315 } catch {
316 // no daemon running: nothing to free
317 }
318
319 return done
320 })
321
322 on('prompt.compose', async ($, e, next) => {
323 const composed = await next(e)
324 if (!isOn) return composed
325
326 return {
327 ...composed,
328 sections: [...composed.sections, { id: 'codex-cu:routing', text: ROUTING, scope: 'session' as const }],
329 }
330 })
331
332 on('tool.call', { tool: OWN_COMPUTER_USE }, ($, e, next) =>
333 isOn
334 ? {
335 deny: `codex-cu mode is on, so desktop computer use goes through Codex. Use ${BRIDGE} instead (load it with ToolSearch if needed). The person can switch back with /codex-cu off.`,
336 }
337 : next(e),
338 )
339
340 on('tool.call', { tool: CODEX_TOOLS }, async ($, e, next) => {
341 isUsedThisTurn = true
342 const ran = await next(e)
343 // The server's own tool can only ask through the engine's prompt; point at the bridge,
344 // which asks through the pane, when that approval did not come through.
345 if (e.tool === CODEX_JS && /was not approved to use/.test(String(ran.text ?? ''))) {
346 return { ...ran, text: `${ran.text}\n\n[codex-cu] Use ${BRIDGE} with the same code instead: it asks the person for approval in its own prompt.` }
347 }
348
349 return ran
350 })
351
352 // Codex tells its computer-use runtime when a turn ends so it can release the
353 // session; do the same on the direct connection.
354 on('turn.complete', async ($, e, next) => {
355 const done = await next(e)
356 if (isUsedThisTurn && e.agentId === undefined) {
357 isUsedThisTurn = false
358 try {
359 await $.mcp.call(SERVER, 'turn_ended', {
360 hook_event_name: 'Stop',
361 session_id: await $.session.id(),
362 turn_id: e.turnId,
363 })
364 } catch {
365 // No direct connection: the daemon's server releases on its own.
366 }
367 }
368
369 return done
370 })
371}
372types/index.d.ts 8 lines1export type CodexCuPending = { app: string } | null
2
3declare module 'claude-code' {
4 interface PluginState {
5 'codex-computer-use': { pending: CodexCuPending; allowed: string[] }
6 }
7}
8