clui-cc-claude-overlay

Command Line User Interface for Claude Code — a floating macOS desktop overlay with multi-tab sessions, permission approval UI, voice input, and skills…

INSTALLATION
npx skills add https://github.com/aradotso/trending-skills --skill clui-cc-claude-overlay
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

# 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 <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

IPC API ( window.clui )

The preload bridge exposes window.clui in the renderer. Key methods:

// 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[]>

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: 24px;

}

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 &#x26; 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 &#x26; 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

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card