Loading...
Loading...
Command Line User Interface for Claude Code — a floating macOS desktop overlay with multi-tab sessions, permission approval UI, voice input, and skills marketplace.
npx skill4agent add aradotso/trending-skills clui-cc-claude-overlaySkill by ara.so — Daily 2026 Skills collection.
claude| Requirement | Minimum | Notes |
|---|---|---|
| macOS | 13+ | Overlay is macOS-only |
| Node.js | 18+ | LTS 20 or 22 recommended |
| Python | 3.10+ | Needs |
| 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-cligit clone https://github.com/lcoutodemos/clui-cc.git
# Then open the clui-cc folder in Finder and double-click install-app.commandgit clone https://github.com/lcoutodemos/clui-cc.git
cd clui-cc
npm install
npm run dev # Hot-reloads renderer; restart for main-process changes./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| Shortcut | Action |
|---|---|
| Show / hide the overlay |
| Fallback toggle (if ⌥+Space is claimed) |
UI prompt → Main process spawns claude -p → NDJSON stream → live render
→ tool call? → permission UI → approve/denyclaude -p --output-format stream-jsonRunManagerEventNormalizerControlPlaneconnecting → idle → running → completed/failed/deadPermissionServer--resume <session-id>src/
├── main/
│ ├── claude/ # ControlPlane, RunManager, EventNormalizer
│ ├── hooks/ # PermissionServer (PreToolUse HTTP hooks)
│ ├── marketplace/ # Plugin catalog fetch + install
│ ├── skills/ # Skill auto-installer
│ └── index.ts # Window creation, IPC handlers, tray
├── renderer/
│ ├── components/ # TabStrip, ConversationView, InputBar, …
│ ├── stores/ # Zustand session store
│ ├── hooks/ # Event listeners, health reconciliation
│ └── theme.ts # Dual palette + CSS custom properties
├── preload/ # Secure IPC bridge (window.clui API)
└── shared/ # Canonical types, IPC channel definitionswindow.cluiwindow.clui// Send a prompt to the active tab's claude process
window.clui.sendPrompt(tabId: string, text: string): Promise<void>
// 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[]>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 }
}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)
}PermissionServerpermission_request// 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.install-app.commandbrew install whisper-cli// Triggered from InputBar component via IPC
window.clui.startVoiceInput(): Promise<void>
window.clui.stopVoiceInput(): Promise<{ transcript: string }>// 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()| Endpoint | Purpose | Required |
|---|---|---|
| Skill catalog (5 min cache) | No — graceful fallback |
| Skill tarball download | No — skipped on failure |
// src/renderer/theme.ts — dual palette with CSS custom properties
// Toggle via the UI or programmatically:
window.clui.setTheme('dark' | 'light' | 'system'):root:root {
--clui-bg: rgba(20, 20, 20, 0.85);
--clui-text: #f0f0f0;
--clui-accent: #7c5cfc;
--clui-pill-radius: 24px;
}~/.clui/skills/skill.js// ~/.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
},
}npm run doctornode-ptyxcode-select --install
python3 -m pip install --upgrade pip setuptools
npm installclaudenpm install -g @anthropic-ai/claude-code
claude # authenticate
which claude # confirm it's on PATHbrew install whisper-cli
which whisper-cli./commands/stop.command
./commands/start.commandsetuptoolspython3 -m pip install --upgrade pip setuptoolsCmd + Shift + K| 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 |