diff --git a/README.md b/README.md index ff70c47f..20df9579 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ It connects to **14 AI coding tools** via Unix socket IPC, displaying session st ## Features - **Notch-native UI** — Expands from the MacBook notch, collapses when idle -- **14 AI tools supported** — Claude Code, Codex, Grok CLI, Gemini CLI, Cursor, Copilot, Trae/Traecli, Qoder, Factory, CodeBuddy, OpenCode, Kimi Code CLI, Cline, Pi / Oh My Pi +- **15 AI tools supported** — Claude Code, Codex, Grok CLI, Gemini CLI, Cursor, Copilot, Trae/Traecli, Qoder, Factory, CodeBuddy, OpenCode, Kimi Code CLI, Cline, Pi / Oh My Pi, DeepSeek Harness - **Live status tracking** — See active sessions, tool calls, and AI responses in real time - **Permission management** — Approve/deny tool permissions directly from the panel - **Question answering** — Respond to agent questions without leaving your current app @@ -57,6 +57,7 @@ It connects to **14 AI coding tools** via Unix socket IPC, displaying session st | | OpenCode | All | APP/Terminal | Full | | | Cline | 5 | VSCode | Full | | | Pi / Oh My Pi | 8 | Terminal | Full | +| | DeepSeek Harness | 9 | Terminal | Full | ## Installation @@ -118,6 +119,14 @@ CodeIsland installs lightweight hooks into each AI tool's config. When the tool For **OpenCode**, a JS plugin connects directly to the socket — no bridge binary needed. +For **DeepSeek Harness (DSH)**, the [dsh-island](https://github.com/cdxiaodong/dsh-island) cordis plugin listens to DSH's built-in events (`session/created`, `tools/pre-execute`, `approval/request`, …) and writes the same JSON over the Unix socket. Install it inside DSH: + +```bash +dsh plugin --profile add github:cdxiaodong/dsh-island +``` + +DSH is plugin-native, so no hook configuration is installed — CodeIsland only needs to know the `dsh` source name to render its session card. + ## Settings CodeIsland provides a 7-tab settings panel: diff --git a/README.zh-CN.md b/README.zh-CN.md index 95b051bf..42f8f6f5 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -26,7 +26,7 @@ CodeIsland 住在你 MacBook 的刘海区域,实时展示 AI 编码 Agent 的 ## 功能特性 - **刘海原生 UI** — 从 MacBook 刘海处展开,空闲时自动收起 -- **支持 14 种 AI 工具** — Claude Code、Codex、Grok CLI、Gemini CLI、Cursor、Copilot、Trae/Traecli、Qoder、Factory、CodeBuddy、OpenCode、Kimi Code CLI、Cline、Pi / Oh My Pi +- **支持 15 种 AI 工具** — Claude Code、Codex、Grok CLI、Gemini CLI、Cursor、Copilot、Trae/Traecli、Qoder、Factory、CodeBuddy、OpenCode、Kimi Code CLI、Cline、Pi / Oh My Pi、DeepSeek Harness - **实时状态追踪** — 查看活跃会话、工具调用和 AI 回复 - **权限管理** — 直接在面板上审批/拒绝工具权限请求 - **问题回答** — 无需离开当前应用即可回答 Agent 的问题 @@ -57,6 +57,7 @@ CodeIsland 住在你 MacBook 的刘海区域,实时展示 AI 编码 Agent 的 | | OpenCode | All | APP/终端 | 完整 | | | Cline | 5 | VSCode | 完整 | | | Pi / Oh My Pi | 8 | 终端 | 完整 | +| | DeepSeek Harness | 9 | 终端 | 完整 | ## 安装 @@ -118,6 +119,14 @@ CodeIsland 在每个 AI 工具的配置中安装轻量级 hooks。当工具触 **OpenCode** 使用 JS 插件直接连接 socket,无需 bridge 二进制。 +**DeepSeek Harness(DSH)** 通过 [dsh-island](https://github.com/cdxiaodong/dsh-island) cordis 插件接入:插件监听 DSH 内置事件(`session/created`、`tools/pre-execute`、`approval/request` 等),把同样的 JSON 写入 Unix socket。在 DSH 内安装: + +```bash +dsh plugin --profile add github:cdxiaodong/dsh-island +``` + +DSH 是插件原生运行时,因此无需安装任何 hook 配置 —— CodeIsland 只需识别 `dsh` source 即可渲染它的会话卡片。 + ## 设置 CodeIsland 提供 7 个标签页的设置面板: diff --git a/Sources/CodeIsland/ConfigInstaller.swift b/Sources/CodeIsland/ConfigInstaller.swift index f815ad52..56d92de2 100644 --- a/Sources/CodeIsland/ConfigInstaller.swift +++ b/Sources/CodeIsland/ConfigInstaller.swift @@ -606,6 +606,19 @@ struct ConfigInstaller { format: .none, events: [] ), + // DSH (DeepSeek Harness) — cordis plugin runtime ("everything is a + // plugin"). No shell hooks: the dsh-island plugin + // (github:cdxiaodong/dsh-island) listens to DSH's built-in events and + // forwards them over the CodeIsland socket directly. CodeIsland only + // needs the source name so DSH events render as their own session card. + CLIConfig( + name: "DeepSeek Harness", + source: "dsh", + configPath: ".dsh", + configKey: "", + format: .none, + events: [] + ), // ZCode (Z.ai) — Electron desktop app, NOT a Claude Code fork. Reads // hooks from ~/.zcode/cli/config.json (strict event whitelist, no // hot-reload — see `.zcode` HookFormat doc for details) (#245). @@ -935,7 +948,7 @@ struct ConfigInstaller { if !installHermesHooks(fm: fm) { ok = false } } else if cli.format == .zcode { if !installZcodeHooks(fm: fm) { ok = false } - } else if cli.source == "pi" || cli.source == "omp" || cli.source == "openclaw" { + } else if cli.source == "pi" || cli.source == "omp" || cli.source == "openclaw" || cli.source == "dsh" { continue } else { if !installExternalHooks(cli: cli, fm: fm) { ok = false } @@ -992,6 +1005,8 @@ struct ConfigInstaller { uninstallOmpExtension(fm: fm) } else if cli.source == "openclaw" { uninstallOpenclawPlugin(fm: fm) + } else if cli.source == "dsh" { + // DSH is bridged via the dsh-island plugin; no shell hooks to remove. } else { uninstallHooks(cli: cli, fm: fm) } @@ -1013,6 +1028,7 @@ struct ConfigInstaller { if source == "pi" { return isPiExtensionInstalled(fm: FileManager.default) } if source == "omp" { return isOmpExtensionInstalled(fm: FileManager.default) } if source == "openclaw" { return isOpenclawPluginInstalled(fm: FileManager.default) } + if source == "dsh" { return isDshInstalled() } if source == "traecli" { return isTraecliHooksInstalled(fm: FileManager.default) } if source == "hermes" { return isHermesHooksInstalled(fm: FileManager.default) } if source == "zcode" { return isZcodeHooksInstalled(fm: FileManager.default) } @@ -1030,6 +1046,13 @@ struct ConfigInstaller { if source == "pi" { return FileManager.default.fileExists(atPath: piAgentDir) } if source == "omp" { return FileManager.default.fileExists(atPath: ompAgentDir) } if source == "openclaw" { return FileManager.default.fileExists(atPath: openclawDir) } + if source == "dsh" { + // DSH harness home defaults to ~/.dsh (override with $DSH_HOME). + if let home = ProcessInfo.processInfo.environment["DSH_HOME"], !home.isEmpty { + return FileManager.default.fileExists(atPath: home) + } + return FileManager.default.fileExists(atPath: NSHomeDirectory() + "/.dsh") + } if source == "grok" { return FileManager.default.fileExists(atPath: grokHome()) } if source == "copilot" { return FileManager.default.fileExists(atPath: NSHomeDirectory() + "/.copilot") } if source == "cline" { @@ -1053,6 +1076,17 @@ struct ConfigInstaller { return FileManager.default.fileExists(atPath: cli.dirPath) } + /// DSH (DeepSeek Harness) is bridged via the dsh-island plugin + /// (`dsh plugin add github:cdxiaodong/dsh-island`). CodeIsland cannot + /// introspect the cordis plugin store, so "installed" means the harness is + /// present on this machine — its plugin wiring is the user's responsibility. + static func isDshInstalled() -> Bool { + if let home = ProcessInfo.processInfo.environment["DSH_HOME"], !home.isEmpty { + return FileManager.default.fileExists(atPath: home) + } + return FileManager.default.fileExists(atPath: NSHomeDirectory() + "/.dsh") + } + // Keep backward compat static func isCodexInstalled() -> Bool { isInstalled(source: "codex") } diff --git a/Sources/CodeIsland/NotchPanelView.swift b/Sources/CodeIsland/NotchPanelView.swift index 50c9bc7b..210dd5e4 100644 --- a/Sources/CodeIsland/NotchPanelView.swift +++ b/Sources/CodeIsland/NotchPanelView.swift @@ -2918,6 +2918,7 @@ private let cliIconFiles: [String: String] = [ "omp": "pi", "opencode": "opencode", "cline": "cline", + "dsh": "dsh", // Rendered from the in-house pixel mascots via // MascotRenderHarness/testRenderCliIcons (MASCOT_ICON_DIR=…). "kiro": "kiro", diff --git a/Sources/CodeIsland/Resources/cli-icons/dsh.png b/Sources/CodeIsland/Resources/cli-icons/dsh.png new file mode 100644 index 00000000..d1a4d983 Binary files /dev/null and b/Sources/CodeIsland/Resources/cli-icons/dsh.png differ diff --git a/Sources/CodeIslandCore/Models.swift b/Sources/CodeIslandCore/Models.swift index 4fdd04a9..92a6862f 100644 --- a/Sources/CodeIslandCore/Models.swift +++ b/Sources/CodeIslandCore/Models.swift @@ -15,6 +15,10 @@ public enum CLIProcessResolver { return lowercasedPath.hasSuffix("/codex") || lowercasedPath.contains("/codex ") case "claude": return lowercasedPath.hasSuffix("/claude") || lowercasedPath.contains("/claude ") + case "dsh": + // DeepSeek Harness CLI ships a `dsh` binary. It has no shell hooks — + // the dsh-island plugin forwards events over the socket directly. + return lowercasedPath.hasSuffix("/dsh") || lowercasedPath.contains("/dsh ") case "qwen": return lowercasedPath.hasSuffix("/qwen") || lowercasedPath.hasSuffix("/qwen-code") diff --git a/Sources/CodeIslandCore/SessionSnapshot.swift b/Sources/CodeIslandCore/SessionSnapshot.swift index 6bfe801c..b8bb3c86 100644 --- a/Sources/CodeIslandCore/SessionSnapshot.swift +++ b/Sources/CodeIslandCore/SessionSnapshot.swift @@ -38,6 +38,7 @@ public struct SessionSnapshot: Sendable { "pi", "kiro", "cline", + "dsh", "zcode", ]