clui-cc-claude-overlay

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Clui CC — Claude Code Desktop Overlay

Clui CC — Claude Code 桌面悬浮覆盖层

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.

ara.so开发的技能工具——属于Daily 2026 Skills合集。
Clui CC 将Claude Code CLI封装为一款透明的macOS悬浮覆盖层,支持多标签会话、权限审批UI(PreToolUse HTTP钩子)、基于Whisper的语音输入、对话历史记录以及技能市场。它需要已认证的
claude
CLI,且完全本地运行——无遥测数据或云端依赖。

Prerequisites

前置要求

RequirementMinimumNotes
macOS13+Overlay is macOS-only
Node.js18+LTS 20 or 22 recommended
Python3.10+Needs
setuptools
on 3.12+
Claude Code CLIanyMust be authenticated
Whisper CLIanyFor voice input
bash
undefined
要求最低版本说明
macOS13+悬浮覆盖层仅支持macOS系统
Node.js18+推荐使用LTS 20或22版本
Python3.10+3.12+版本需要安装
setuptools
Claude Code CLI任意版本必须已完成认证
Whisper CLI任意版本用于语音输入功能
bash
undefined

1. Xcode CLI tools (native module compilation)

1. Xcode CLI工具(用于原生模块编译)

xcode-select --install
xcode-select --install

2. Node.js via Homebrew

2. 通过Homebrew安装Node.js

brew install node node --version # confirm ≥18
brew install node node --version # 确认版本≥18

3. Python setuptools (required on Python 3.12+)

3. Python setuptools(Python 3.12+版本必需)

python3 -m pip install --upgrade pip setuptools
python3 -m pip install --upgrade pip setuptools

4. Claude Code CLI

4. 安装Claude Code CLI

npm install -g @anthropic-ai/claude-code
npm install -g @anthropic-ai/claude-code

5. Authenticate Claude Code

5. 完成Claude Code认证

claude
claude

6. Whisper for voice input

6. 安装Whisper用于语音输入

brew install whisper-cli

---
brew install whisper-cli

---

Installation

安装步骤

Recommended: App installer (non-developer)

推荐:应用安装包(非开发者用户)

bash
git clone https://github.com/lcoutodemos/clui-cc.git
bash
git clone https://github.com/lcoutodemos/clui-cc.git

Then open the clui-cc folder in Finder and double-click install-app.command

然后在访达中打开clui-cc文件夹,双击install-app.command运行


On first launch macOS may block the unsigned app — go to **System Settings → Privacy & Security → Open Anyway**.

首次启动时macOS可能会阻止未签名应用——前往**系统设置 → 隐私与安全性 → 仍要打开**。

Developer workflow

开发者工作流

bash
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
bash
git clone https://github.com/lcoutodemos/clui-cc.git
cd clui-cc
npm install
npm run dev       # 热重载渲染器;主进程变更需重启

Command scripts

命令脚本

bash
./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

bash
./commands/setup.command    # 环境检测 + 安装依赖
./commands/start.command    # 构建并从源码启动
./commands/stop.command     # 停止所有Clui CC进程

npm run build               # 生产构建(不打包)
npm run dist                # 打包为macOS .app文件 → release/目录
npm run doctor              # 环境诊断

Key Shortcuts

常用快捷键

ShortcutAction
⌥ + Space
Show / hide the overlay
Cmd + Shift + K
Fallback toggle (if ⌥+Space is claimed)

快捷键操作
⌥ + Space
显示/隐藏悬浮覆盖层
Cmd + Shift + K
备用切换快捷键(当⌥+Space被占用时)

Architecture

架构

UI prompt → Main process spawns claude -p → NDJSON stream → live render
                                         → tool call? → permission UI → approve/deny
UI输入 → 主进程启动claude -p → NDJSON流 → 实时渲染
                                         → 工具调用? → 权限UI → 批准/拒绝

Process flow

流程说明

  1. Each tab spawns
    claude -p --output-format stream-json
    as a subprocess.
  2. RunManager
    parses NDJSON;
    EventNormalizer
    normalizes events.
  3. ControlPlane
    manages tab lifecycle:
    connecting → idle → running → completed/failed/dead
    .
  4. Tool permission requests arrive via HTTP hooks to
    PermissionServer
    (localhost only).
  5. Renderer polls backend health every 1.5 s and reconciles tab state.
  6. Sessions resume with
    --resume <session-id>
    .
  1. 每个标签页都会启动
    claude -p --output-format stream-json
    作为子进程。
  2. RunManager
    解析NDJSON;
    EventNormalizer
    标准化事件格式。
  3. ControlPlane
    管理标签页生命周期:
    连接中 → 空闲 → 运行中 → 完成/失败/终止
  4. 工具权限请求通过HTTP钩子发送至
    PermissionServer
    (仅本地主机)。
  5. 渲染器每1.5秒轮询后端健康状态并同步标签页状态。
  6. 通过
    --resume <session-id>
    恢复会话。

Project structure

项目结构

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 definitions

src/
├── main/
│   ├── claude/       # ControlPlane、RunManager、EventNormalizer
│   ├── hooks/        # PermissionServer(PreToolUse HTTP钩子)
│   ├── marketplace/  # 插件目录获取与安装
│   ├── skills/       # 技能自动安装器
│   └── index.ts      # 窗口创建、IPC处理器、托盘管理
├── renderer/
│   ├── components/   # 标签栏、对话视图、输入栏等
│   ├── stores/       # Zustand会话存储
│   ├── hooks/        # 事件监听器、健康状态同步
│   └── theme.ts      # 双色调色板 + CSS自定义属性
├── preload/          # 安全IPC桥接(window.clui API)
└── shared/           # 标准类型定义、IPC通道定义

IPC API (
window.clui
)

IPC API (
window.clui
)

The preload bridge exposes
window.clui
in the renderer. Key methods:
typescript
// 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[]>

预加载桥接层在渲染器中暴露
window.clui
对象。核心方法:
typescript
// 向当前活跃标签页的claude进程发送输入
window.clui.sendPrompt(tabId: string, text: string): Promise<void>

// 批准或拒绝待处理的工具使用权限
window.clui.resolvePermission(requestId: string, approved: boolean): Promise<void>

// 创建新标签页(启动新的claude -p进程)
window.clui.createTab(): Promise<{ tabId: string }>

// 通过会话ID恢复历史会话
window.clui.resumeSession(tabId: string, sessionId: string): Promise<void>

// 订阅标签页的标准化事件
window.clui.onTabEvent(tabId: string, callback: (event: NormalizedEvent) => void): () => void

// 获取对话历史列表
window.clui.getHistory(): Promise<SessionMeta[]>

Working with Tabs and Sessions

标签页与会话管理

Creating a tab and sending a prompt (renderer)

创建标签页并发送输入(渲染器端)

typescript
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 }
}
typescript
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

恢复历史会话

typescript
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)
}

typescript
async function resumeLastSession() {
  const history = await window.clui.getHistory()
  if (history.length === 0) return

  const { tabId } = await window.clui.createTab()
  const lastSession = history[0] // 最新会话排在首位
  await window.clui.resumeSession(tabId, lastSession.sessionId)
}

Permission Approval UI

权限审批UI

Tool calls are intercepted by
PermissionServer
via PreToolUse HTTP hooks before execution. The renderer receives a
permission_request
event and must resolve it.
typescript
// 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)
})
typescript
// 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.

工具调用会在执行前被
PermissionServer
通过PreToolUse HTTP钩子拦截。渲染器会收到
permission_request
事件并需要处理该请求。
typescript
// 渲染器:监听权限请求
window.clui.onTabEvent(tabId, async (event) => {
  if (event.type !== 'permission_request') return

  const { requestId, toolName, toolInput } = event

  // 显示自定义审批UI,然后执行:
  const approved = await showApprovalDialog({ toolName, toolInput })
  await window.clui.resolvePermission(requestId, approved)
})
typescript
// 主进程:PermissionServer向claude -p注册钩子
// 钩子端点会接收来自Claude Code的POST请求,格式如下:
// { "tool": "bash", "input": { "command": "rm -rf dist/" }, "session_id": "..." }
// 它会暂停请求直到渲染器完成审批。

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.
typescript
// Triggered from InputBar component via IPC
window.clui.startVoiceInput(): Promise<void>
window.clui.stopVoiceInput(): Promise<{ transcript: string }>

语音输入使用本地的Whisper工具。它会被
install-app.command
自动安装,也可通过
brew install whisper-cli
手动安装。无需API密钥——转录完全在本地设备上运行。
typescript
// 通过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.
typescript
// 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:
EndpointPurposeRequired
raw.githubusercontent.com/anthropics/*
Skill catalog (5 min cache)No — graceful fallback
api.github.com/repos/anthropics/*/tarball/*
Skill tarball downloadNo — skipped on failure

无需离开UI即可从Anthropic的GitHub仓库安装技能(插件)。
typescript
// 获取可用技能(缓存5分钟,从raw.githubusercontent.com获取)
const skills = await window.clui.marketplace.list()
// [{ id, name, description, repoUrl, version }, ...]

// 安装技能(从api.github.com下载压缩包)
await window.clui.marketplace.install(skillId: string)

// 列出已安装技能
const installed = await window.clui.marketplace.listInstalled()
技能市场的网络请求:
端点用途是否必需
raw.githubusercontent.com/anthropics/*
技能目录(5分钟缓存)否——无网络时会优雅降级
api.github.com/repos/anthropics/*/tarball/*
技能压缩包下载否——失败时会跳过

Theme Configuration

主题配置

typescript
// 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:
css
:root {
  --clui-bg: rgba(20, 20, 20, 0.85);
  --clui-text: #f0f0f0;
  --clui-accent: #7c5cfc;
  --clui-pill-radius: 24px;
}

typescript
// src/renderer/theme.ts — 带CSS自定义属性的双色调色板
// 通过UI或编程方式切换:
window.clui.setTheme('dark' | 'light' | 'system')
自定义CSS属性会应用到
:root
,可在渲染器样式表中覆盖:
css
:root {
  --clui-bg: rgba(20, 20, 20, 0.85);
  --clui-text: #f0f0f0;
  --clui-accent: #7c5cfc;
  --clui-pill-radius: 24px;
}

Adding a Custom Skill

添加自定义技能

Skills are auto-loaded from
~/.clui/skills/
. A skill is a directory with a
skill.js
entry:
typescript
// ~/.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
  },
}

技能会从
~/.clui/skills/
目录自动加载。一个技能是包含
skill.js
入口文件的目录:
typescript
// ~/.clui/skills/my-skill/skill.js
module.exports = {
  name: 'my-skill',
  version: '1.0.0',
  description: '实现某些实用功能',

  // 当输入匹配触发条件时激活技能
  async onPrompt(context) {
    const { prompt, tabId, clui } = context
    if (!prompt.includes('我的触发词')) return false   // 不处理,转发给claude

    await clui.sendMessage(tabId, `由my-skill处理:${prompt}`)
    return true  // 已处理,不转发给claude
  },
}

Troubleshooting

故障排除

Self-check

自检命令

bash
npm run doctor
bash
npm run doctor

Common issues

常见问题

App blocked on first launch → System Settings → Privacy & Security → Open Anyway
node-pty
fails to compile
bash
xcode-select --install
python3 -m pip install --upgrade pip setuptools
npm install
claude
not found
bash
npm install -g @anthropic-ai/claude-code
claude   # authenticate
which claude   # confirm it's on PATH
Whisper not found
bash
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:
bash
./commands/stop.command
./commands/start.command
setuptools
missing (Python 3.12+)
bash
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

首次启动时应用被阻止 → 系统设置 → 隐私与安全性 → 仍要打开
node-pty
编译失败
bash
xcode-select --install
python3 -m pip install --upgrade pip setuptools
npm install
找不到
claude
命令
bash
npm install -g @anthropic-ai/claude-code
claude   # 完成认证
which claude   # 确认已添加到PATH
找不到Whisper
bash
brew install whisper-cli
which whisper-cli
PermissionServer端口冲突 HTTP钩子服务器仅在本地主机运行。如果端口被其他进程占用,通过以下命令重启:
bash
./commands/stop.command
./commands/start.command
缺少
setuptools
(Python 3.12+)
bash
python3 -m pip install --upgrade pip setuptools
悬浮覆盖层不显示
  • 尝试备用快捷键:
    Cmd + Shift + K
  • 检查Clui CC是否已获得辅助功能权限:系统设置 → 隐私与安全性 → 辅助功能

Tested Versions

已测试版本

ComponentVersion
macOS15.x Sequoia
Node.js20.x LTS, 22.x
Python3.12 (+ setuptools)
Electron33.x
Claude Code CLI2.1.71

组件版本
macOS15.x Sequoia
Node.js20.x LTS、22.x
Python3.12(需安装setuptools)
Electron33.x
Claude Code CLI2.1.71

References

参考链接