Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ docs/superpowers/
# IDE vscode/cursor
.vscode/
.cursor/
xcuserdata/
*.xcuserstate

# Tmp files
*.log
Expand All @@ -25,7 +27,45 @@ android-watch/local.properties
android-watch/**/build/

# Code agents config files
.adal/
.aider-desk/
.agents/
.augment/
.bob/
.claude/
.codeartsdoer/
.codebuddy/
.codemaker/
.codestudio/
.commandcode/
.continue/
.cortex/
.crush/
.devin/
.factory/
.forge/
.goose/
.hermes/
.iflow/
.junie/
.kilocode/
.kiro/
.kode/
.mcpjam/
.mux/
.neovate/
.openhands/
.pi/
.pochi/
.qoder/
.qwen/
.roo/
.rovodev/
.tabnine/
.trae/
.vibe/
.windsurf/
.zencoder/
skills/
skills-lock.json
CLAUDE.md
6 changes: 6 additions & 0 deletions Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,11 @@
<string>oqLtx5s2hc8Xgsp4rEuTwnQ8UGRT4ma4tjlf+1i3YHA=</string>
<key>SUScheduledCheckInterval</key>
<integer>14400</integer>
<key>NSBonjourServices</key>
<array>
<string>_codeisland._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>CodeIsland advertises itself on the local network so iPhone can mirror the island state.</string>
</dict>
</plist>
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ It connects to **12 AI coding tools** via Unix socket IPC, displaying session st
- **Smart suppress** — Tab-level terminal detection: only suppresses notifications when you're looking at the specific session tab, not just the terminal app
- **Sound effects** — Optional 8-bit sound notifications for session events
- **Auto hook install** — Automatically configures hooks for all detected CLI tools, with auto-repair and version tracking
- **iPhone & Apple Watch Buddy** — Mirror session status to Dynamic Island, Lock Screen, StandBy, and Apple Watch
- **Bilingual UI** — English and Chinese, auto-detects system language
- **Multi-display** — Works with external monitors, auto-detects notch displays

Expand Down Expand Up @@ -73,6 +74,16 @@ brew install --cask codeisland

> **Note:** On first launch, macOS may show a security warning. Go to **System Settings → Privacy & Security** and click **Open Anyway**.

### iPhone & Apple Watch Buddy

Code Island Buddy is available on the App Store:

[Download Code Island Buddy](https://apps.apple.com/us/app/code-island-buddy/id6773881129)

The iPhone app mirrors your Mac sessions to Dynamic Island, Lock Screen, StandBy, and Apple Watch. The Mac app publishes lightweight session snapshots over your local network while the iPhone app is open, and sends compact Bluetooth summaries for background refreshes such as Live Activities and Watch updates.

Code Island Buddy is completely free and open source. It does not require an account or an external server; the companion source code lives in this repository under `ios/CodeIslandCompanion` and `apple-companion`.

### Build from Source

Requires **macOS 14+** and **Swift 5.9+**.
Expand All @@ -98,6 +109,7 @@ AI Tool (Claude/Codex/Gemini/Cursor/...)
→ Unix socket → /tmp/codeisland-<uid>.sock
→ CodeIsland app receives event
→ Updates UI in real time
→ Optional local Buddy sync to iPhone / Apple Watch
```

CodeIsland installs lightweight hooks into each AI tool's config. When the tool triggers an event (session start, tool call, permission request, etc.), the hook sends a JSON message through a Unix socket. CodeIsland listens on this socket and updates the notch panel instantly.
Expand Down
12 changes: 12 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ CodeIsland 住在你 MacBook 的刘海区域,实时展示 AI 编码 Agent 的
- **智能通知抑制** — 标签页级终端检测:只在你正在看该会话的标签页时抑制通知,而不是整个终端应用
- **音效提示** — 可选的 8-bit 风格音效通知
- **自动安装 Hook** — 自动为所有检测到的 CLI 工具配置 hooks,支持自动修复和版本追踪
- **iPhone 与 Apple Watch Buddy** — 将会话状态同步到灵动岛、锁屏、StandBy 和 Apple Watch
- **中英双语** — 支持中文和英文,自动跟随系统语言
- **多显示器** — 支持外接显示器,自动检测刘海屏幕

Expand Down Expand Up @@ -73,6 +74,16 @@ brew install --cask codeisland

> **提示:** 首次启动时 macOS 可能弹出安全提示,前往 **系统设置 → 隐私与安全性** 点击 **仍要打开** 即可。

### iPhone 与 Apple Watch Buddy

Code Island Buddy 已在 App Store 上架:

[下载 Code Island Buddy](https://apps.apple.com/us/app/code-island-buddy/id6773881129)

iPhone App 可以把 Mac 上的会话状态同步到灵动岛、锁屏、StandBy 和 Apple Watch。它的工作方式很轻量:iPhone App 前台打开时,Mac 端通过本地网络发送会话快照;需要后台刷新实时活动和手表状态时,则通过蓝牙发送压缩后的状态摘要。

Code Island Buddy 完全免费,并且开源。它不需要账号,也不依赖外部服务器;伴随端源码就在本仓库的 `ios/CodeIslandCompanion` 和 `apple-companion` 目录中。

### 从源码构建

需要 **macOS 14+** 和 **Swift 5.9+**。
Expand All @@ -98,6 +109,7 @@ AI 工具 (Claude/Codex/Gemini/Cursor/...)
→ Unix socket → /tmp/codeisland-<uid>.sock
→ CodeIsland 接收事件
→ 实时更新 UI
→ 可选同步到 iPhone / Apple Watch Buddy
```

CodeIsland 在每个 AI 工具的配置中安装轻量级 hooks。当工具触发事件(会话开始、工具调用、权限请求等)时,hook 通过 Unix socket 发送 JSON 消息。CodeIsland 监听此 socket 并即时更新刘海面板。
Expand Down
3 changes: 1 addition & 2 deletions Sources/CodeIsland/AntiGravityView.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import SwiftUI
import CodeIslandCore

/// AntiGravityBot — AntiGravity mascot, rainbow gradient swoosh character.
/// Multicolor gradient inspired by the AntiGravity "A" logo.
struct AntiGravityView: View {
let status: AgentStatus
let status: MascotAgentStatus
var size: CGFloat = 27
@State private var alive = false
@Environment(\.mascotSpeed) private var speed
Expand Down
19 changes: 19 additions & 0 deletions Sources/CodeIsland/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
guard let appState else { return }
appState.handleBuddyControlCommand(command)
}
AppleCompanionPublisher.shared.attach(appState)
AppleCompanionPublisher.shared.onFocusRequest = { [weak appState] mascot in
guard let appState else { return }
ESP32FocusCoordinator.handle(mascot: mascot, appState: appState)
}
AppleCompanionPublisher.shared.onControlCommand = { [weak appState] command in
guard let appState else { return }
appState.handleBuddyControlCommand(command)
}
AppleCompanionPublisher.shared.onQuestionAnswer = { [weak appState] answer in
guard let appState else { return }
appState.answerCompanionQuestion(answer)
}
let buddyEnabled = UserDefaults.standard.bool(forKey: SettingsKey.esp32BridgeEnabled)
let buddySyncInterval = UserDefaults.standard.double(forKey: SettingsKey.esp32HeartbeatSeconds)
let buddyBrightness = UserDefaults.standard.double(forKey: SettingsKey.buddyScreenBrightnessPercent)
Expand All @@ -73,6 +86,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
brightnessPercent: buddyBrightness > 0 ? buddyBrightness : SettingsDefaults.buddyScreenBrightnessPercent,
screenOrientation: buddyScreenOrientation
)
let appleCompanionEnabled = UserDefaults.standard.bool(forKey: SettingsKey.appleCompanionEnabled)
let appleCompanionHeartbeat = UserDefaults.standard.double(forKey: SettingsKey.appleCompanionHeartbeatSeconds)
AppleCompanionPublisher.shared.configure(
enabled: appleCompanionEnabled,
heartbeatSeconds: appleCompanionHeartbeat > 0 ? appleCompanionHeartbeat : SettingsDefaults.appleCompanionHeartbeatSeconds
)

// Hooks auto-recovery: periodic + app activation trigger
hookRecoveryTimer = Timer.scheduledTimer(withTimeInterval: 300, repeats: true) { [weak self] _ in
Expand Down
34 changes: 34 additions & 0 deletions Sources/CodeIsland/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ final class AppState {
rotatingSessionId = cachedActiveIds.first
}
ESP32StatePublisher.shared.notifyDirty()
AppleCompanionPublisher.shared.notifyDirty()
}

/// Start monitoring the CLI process for a session.
Expand Down Expand Up @@ -875,6 +876,7 @@ final class AppState {
if activeSessionCount != summary.activeSessionCount { activeSessionCount = summary.activeSessionCount }
if totalSessionCount != summary.totalSessionCount { totalSessionCount = summary.totalSessionCount }
ESP32StatePublisher.shared.notifyDirty()
AppleCompanionPublisher.shared.notifyDirty()
}

private func refreshProviderTitle(for trackedSessionId: String, providerSessionId: String? = nil) {
Expand Down Expand Up @@ -1153,6 +1155,38 @@ final class AppState {
}
}

func answerCompanionQuestion(_ answer: String) {
guard !questionQueue.isEmpty else {
log.info("Ignored companion question answer because question queue is empty")
return
}

if questionQueue[0].isFromPermission,
var askState = questionQueue[0].askUserQuestionState {
guard let index = askState.items.firstIndex(where: { askState.answers[$0.answerKey] == nil }) else {
answerQuestionMulti(askState.items.map {
(question: $0.payload.question, answer: askState.answers[$0.answerKey] ?? "")
})
return
}

let item = askState.items[index]
askState.answers[item.answerKey] = answer
questionQueue[0].askUserQuestionState = askState

if askState.canConfirm {
answerQuestionMulti(askState.items.map {
(question: $0.payload.question, answer: askState.answers[$0.answerKey] ?? "")
})
} else {
refreshDerivedState()
}
return
}

answerQuestion(answer)
}

/// Find an existing session whose source matches and whose CLI PID equals
/// the supplied ppid. Used by HookServer to merge plugin-proxied events
/// (e.g. omo) into their main session when pluginSessionMode == "merge". (#123)
Expand Down
Loading