Clui CC — Claude Code Desktop Overlay Skill by ara.so — Daily 2026 Skills collection. Clui CC wraps the Claude Code CLI in a transparent, floating macOS overlay with multi-tab sessions, a permission approval UI (PreToolUse HTTP hooks), voice input via Whisper, conversation history, and a skills marketplace. It requires an authenticated claude CLI and runs entirely local — no telemetry or cloud dependency. Prerequisites Requirement Minimum Notes macOS 13+ Overlay is macOS-only Node.js 18+ LTS 20 or 22 recommended Python 3.10+ Needs setuptools on 3.12+ Claude Code CLI any Must be authenticated Whisper CLI any For voice input
1. Xcode CLI tools (native module compilation)
xcode-select --install
2. Node.js via Homebrew
brew install node node --version
confirm ≥18
3. Python setuptools (required on Python 3.12+)
python3 -m pip install --upgrade pip setuptools
4. Claude Code CLI
npm install -g @anthropic-ai/claude-code
5. Authenticate Claude Code
claude
6. Whisper for voice input
brew install whisper-cli Installation Recommended: App installer (non-developer) git clone https://github.com/lcoutodemos/clui-cc.git
Then open the clui-cc folder in Finder and double-click install-app.command
On first launch macOS may block the unsigned app — go to System Settings → Privacy & Security → Open Anyway . Developer workflow git clone https://github.com/lcoutodemos/clui-cc.git cd clui-cc npm install npm run dev
Hot-reloads renderer; restart for main-process changes
Command scripts ./commands/setup.command
Environment check + install deps
./commands/start.command
Build and launch from source
./commands/stop.command
Stop all Clui CC processes
npm run build
Production build (no packaging)
npm run dist
Package as macOS .app → release/
npm run doctor
Environment diagnostic
Key Shortcuts
Shortcut
Action
⌥ + Space
Show / hide the overlay
Cmd + Shift + K
Fallback toggle (if ⌥+Space is claimed)
Architecture
UI prompt → Main process spawns claude -p → NDJSON stream → live render
→ tool call? → permission UI → approve/deny
Process flow
Each tab spawns
claude -p --output-format stream-json
as a subprocess.
RunManager
parses NDJSON;
EventNormalizer
normalizes events.
ControlPlane
manages tab lifecycle:
connecting → idle → running → completed/failed/dead
.
Tool permission requests arrive via HTTP hooks to
PermissionServer
(localhost only).
Renderer polls backend health every 1.5 s and reconciles tab state.
Sessions resume with
--resume
// Approve or deny a pending tool-use permission window . clui . resolvePermission ( requestId : string , approved : boolean ) : Promise < void
// Create a new tab (spawns a new claude -p process) window . clui . createTab ( ) : Promise < { tabId : string }
// Resume a past session by id window . clui . resumeSession ( tabId : string , sessionId : string ) : Promise < void
// Subscribe to normalized events from a tab window . clui . onTabEvent ( tabId : string , callback : ( event : NormalizedEvent ) => void ) : ( ) => void // Get conversation history list window . clui . getHistory ( ) : Promise < SessionMeta [ ]
Working with Tabs and Sessions Creating a tab and sending a prompt (renderer) import { useEffect , useState } from 'react' export function useClaudeTab ( ) { const [ tabId , setTabId ] = useState < string | null
( null ) const [ messages , setMessages ] = useState < NormalizedEvent [ ]
( [ ] ) useEffect ( ( ) => { window . clui . createTab ( ) . then ( ( { tabId } ) => { setTabId ( tabId ) const unsubscribe = window . clui . onTabEvent ( tabId , ( event ) => { setMessages ( ( prev ) => [ ... prev , event ] ) } ) return unsubscribe } ) } , [ ] ) const send = ( text : string ) => { if ( ! tabId ) return window . clui . sendPrompt ( tabId , text ) } return { messages , send } } Resuming a past session async function resumeLastSession ( ) { const history = await window . clui . getHistory ( ) if ( history . length === 0 ) return const { tabId } = await window . clui . createTab ( ) const lastSession = history [ 0 ] // most recent first await window . clui . resumeSession ( tabId , lastSession . sessionId ) } Permission Approval UI Tool calls are intercepted by PermissionServer via PreToolUse HTTP hooks before execution. The renderer receives a permission_request event and must resolve it. // Renderer: listen for permission requests window . clui . onTabEvent ( tabId , async ( event ) => { if ( event . type !== 'permission_request' ) return const { requestId , toolName , toolInput } = event // Show your approval UI, then: const approved = await showApprovalDialog ( { toolName , toolInput } ) await window . clui . resolvePermission ( requestId , approved ) } ) // Main process: PermissionServer registers a hook with claude -p // The hook endpoint receives POST requests from Claude Code like: // { "tool": "bash", "input": { "command": "rm -rf dist/" }, "session_id": "..." } // It holds the request until the renderer resolves it. Voice Input Voice input uses Whisper locally. It is installed automatically by install-app.command or via brew install whisper-cli . No API key is needed — transcription runs entirely on-device. // Triggered from InputBar component via IPC window . clui . startVoiceInput ( ) : Promise < void
window . clui . stopVoiceInput ( ) : Promise < { transcript : string }
Skills Marketplace Install skills (plugins) from Anthropic's GitHub repos without leaving the UI. // Fetch available skills (cached 5 min, fetched from raw.githubusercontent.com) const skills = await window . clui . marketplace . list ( ) // [{ id, name, description, repoUrl, version }, ...] // Install a skill (downloads tarball from api.github.com) await window . clui . marketplace . install ( skillId : string ) // List installed skills const installed = await window . clui . marketplace . listInstalled ( ) Network calls made by the marketplace: Endpoint Purpose Required raw.githubusercontent.com/anthropics/ Skill catalog (5 min cache) No — graceful fallback api.github.com/repos/anthropics//tarball/* Skill tarball download No — skipped on failure Theme Configuration // src/renderer/theme.ts — dual palette with CSS custom properties // Toggle via the UI or programmatically: window . clui . setTheme ( 'dark' | 'light' | 'system' ) Custom CSS properties are applied to :root and can be overridden in renderer stylesheets: :root { --clui-bg : rgba ( 20 , 20 , 20 , 0.85 ) ; --clui-text :
f0f0f0
; --clui-accent :
7c5cfc
;
--clui-pill-radius
:
24
px
;
}
Adding a Custom Skill
Skills are auto-loaded from
~/.clui/skills/
. A skill is a directory with a
skill.js
entry:
// ~/.clui/skills/my-skill/skill.js
module
.
exports
=
{
name
:
'my-skill'
,
version
:
'1.0.0'
,
description
:
'Does something useful'
,
// Called when the skill is activated by a matching prompt
async
onPrompt
(
context
)
{
const
{
prompt
,
tabId
,
clui
}
=
context
if
(
!
prompt
.
includes
(
'my trigger'
)
)
return
false
// pass through
await
clui
.
sendMessage
(
tabId
,
Handled by my-skill:
${
prompt
}
)
return
true
// consumed — don't forward to claude
}
,
}
Troubleshooting
Self-check
npm
run doctor
Common issues
App blocked on first launch
→ System Settings → Privacy & Security → Open Anyway
node-pty
fails to compile
xcode-select
--install
python3
-m
pip
install
--upgrade
pip setuptools
npm
install
claude
not found
npm
install
-g
@anthropic-ai/claude-code
claude
authenticate
which claude
confirm it's on PATH
Whisper not found brew install whisper-cli which whisper-cli Port conflict on PermissionServer The HTTP hook server runs on localhost only. If another process occupies its port, restart with: ./commands/stop.command ./commands/start.command setuptools missing (Python 3.12+) python3 -m pip install --upgrade pip setuptools Overlay not showing Try the fallback shortcut: Cmd + Shift + K Check that Clui CC has Accessibility permission: System Settings → Privacy & Security → Accessibility Tested Versions Component Version macOS 15.x Sequoia Node.js 20.x LTS, 22.x Python 3.12 (+ setuptools) Electron 33.x Claude Code CLI 2.1.71 References Claude Code docs Architecture deep-dive Troubleshooting guide MIT License