mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-23 00:35:51 +00:00
feat: Computer Use — Windows 跨平台支持 + GUI 无障碍增强 + Python Bridge
三平台 Computer Use (macOS + Windows + Linux),Windows 专项增强。
- MCP server: toolCalls/tools/executor/mcpServer 等 12 文件完整实现
- 平台抽象层: platforms/{win32,darwin,linux}.ts
- 跨平台 executor: executorCrossPlatform.ts
- CHICAGO_MCP + VOICE_MODE feature flags 启用
- windowMessage.ts: SendMessageW (WM_CHAR Unicode + 剪贴板粘贴)
- windowBorder.ts: 4 叠加窗口边框 (30fps 跟踪)
- uiAutomation.ts: UI Automation 元素树/点击/写值
- accessibilitySnapshot.ts: 无障碍快照 → 模型感知 GUI
- bridge.py + bridgeClient.ts: Python 长驻进程 (替代 per-call PS)
- window_management: min/max/restore/close/focus (Win32 API)
- click_element / type_into_element: 按名称操作 (无需坐标)
- 截图自动附带 Accessibility Snapshot
- 17 种方法, stdin/stdout JSON 通信
- 窗口枚举 1.5ms vs PS 500ms, 截图 360ms vs PS 800ms
- 依赖: mss + Pillow + pywinauto
This commit is contained in:
@@ -159,28 +159,23 @@ export const apps: AppsAPI = {
|
||||
|
||||
async listInstalled() {
|
||||
try {
|
||||
// Use Spotlight (mdfind) to enumerate .app bundles and mdls to get real bundle IDs.
|
||||
// Searches /Applications, /System/Applications, and /System/Applications/Utilities
|
||||
// so that system apps (Terminal, Chess, etc.) and core services (Finder) are found.
|
||||
const proc = Bun.spawn([
|
||||
'bash', '-c',
|
||||
`for dir in /Applications /System/Applications /System/Applications/Utilities /System/Library/CoreServices; do
|
||||
mdfind 'kMDItemContentType == "com.apple.application-bundle"' -onlyin "$dir" 2>/dev/null
|
||||
done | sort -u | while read -r appPath; do
|
||||
bundleId=$(mdls -raw -name kMDItemCFBundleIdentifier "$appPath" 2>/dev/null)
|
||||
if [ -n "$bundleId" ] && [ "$bundleId" != "(null)" ]; then
|
||||
displayName=$(basename "$appPath" .app)
|
||||
echo "$bundleId|$displayName|$appPath"
|
||||
fi
|
||||
done`,
|
||||
], { stdout: 'pipe', stderr: 'pipe' })
|
||||
const text = await new Response(proc.stdout).text()
|
||||
await proc.exited
|
||||
return text.split('\n').filter(Boolean).map(line => {
|
||||
const [bundleId, displayName, path] = line.split('|', 3)
|
||||
const result = await osascript(`
|
||||
tell application "System Events"
|
||||
set appList to ""
|
||||
repeat with appFile in (every file of folder "Applications" of startup disk whose name ends with ".app")
|
||||
set appPath to POSIX path of (appFile as alias)
|
||||
set appName to name of appFile
|
||||
set appList to appList & appPath & "|" & appName & "\\n"
|
||||
end repeat
|
||||
return appList
|
||||
end tell
|
||||
`)
|
||||
return result.split('\n').filter(Boolean).map(line => {
|
||||
const [path, name] = line.split('|', 2)
|
||||
const displayName = (name ?? '').replace(/\.app$/, '')
|
||||
return {
|
||||
bundleId: bundleId ?? '',
|
||||
displayName: displayName ?? '',
|
||||
bundleId: `com.app.${displayName.toLowerCase().replace(/\s+/g, '-')}`,
|
||||
displayName,
|
||||
path: path ?? '',
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
/**
|
||||
* @ant/computer-use-swift — cross-platform display, apps, and screenshot API
|
||||
* @ant/computer-use-swift — macOS display, apps, and screenshot (Swift native)
|
||||
*
|
||||
* Platform backends:
|
||||
* - darwin: AppleScript/JXA + screencapture
|
||||
* - win32: PowerShell + System.Drawing + Win32 P/Invoke
|
||||
*
|
||||
* Add new platforms by creating backends/<platform>.ts implementing SwiftBackend.
|
||||
* This package wraps the macOS-only Swift .node native module.
|
||||
* For Windows/Linux, use src/utils/computerUse/platforms/ instead.
|
||||
*/
|
||||
|
||||
// Re-export all types
|
||||
export type {
|
||||
DisplayGeometry,
|
||||
PrepareDisplayResult,
|
||||
@@ -18,72 +14,42 @@ export type {
|
||||
ScreenshotResult,
|
||||
ResolvePrepareCaptureResult,
|
||||
WindowDisplayInfo,
|
||||
DisplayAPI,
|
||||
AppsAPI,
|
||||
ScreenshotAPI,
|
||||
SwiftBackend,
|
||||
} from './types.js'
|
||||
} from './backends/darwin.js'
|
||||
|
||||
import type { ResolvePrepareCaptureResult, SwiftBackend } from './types.js'
|
||||
import type { ResolvePrepareCaptureResult } from './backends/darwin.js'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform dispatch
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function loadBackend(): SwiftBackend | null {
|
||||
function loadDarwin() {
|
||||
if (process.platform !== 'darwin') return null
|
||||
try {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return require('./backends/darwin.js') as SwiftBackend
|
||||
case 'win32':
|
||||
return require('./backends/win32.js') as SwiftBackend
|
||||
case 'linux':
|
||||
return require('./backends/linux.js') as SwiftBackend
|
||||
default:
|
||||
return null
|
||||
}
|
||||
return require('./backends/darwin.js')
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const backend = loadBackend()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ComputerUseAPI — Main export (preserves original class interface)
|
||||
// ---------------------------------------------------------------------------
|
||||
const darwin = loadDarwin()
|
||||
|
||||
export class ComputerUseAPI {
|
||||
// When no backend is loaded (unsupported platform), all APIs are no-op stubs.
|
||||
// These stubs should never be reached in practice — callers check isSupported
|
||||
// or the feature gate before invoking.
|
||||
|
||||
apps = backend?.apps ?? {
|
||||
apps = darwin?.apps ?? {
|
||||
async prepareDisplay() { return { activated: '', hidden: [] } },
|
||||
async previewHideSet() { return [] },
|
||||
async findWindowDisplays(ids: string[]) { return ids.map(b => ({ bundleId: b, displayIds: [] as number[] })) },
|
||||
async findWindowDisplays(ids: string[]) { return ids.map((b: string) => ({ bundleId: b, displayIds: [] as number[] })) },
|
||||
async appUnderPoint() { return null },
|
||||
async listInstalled() { return [] },
|
||||
iconDataUrl() { return null },
|
||||
listRunning() { return [] },
|
||||
async open() { throw new Error('computer-use-swift: no backend for this platform') },
|
||||
async open() { throw new Error('@ant/computer-use-swift: macOS only') },
|
||||
async unhide() {},
|
||||
}
|
||||
|
||||
display = backend?.display ?? {
|
||||
getSize() { throw new Error('computer-use-swift: no backend for this platform') },
|
||||
listAll() { throw new Error('computer-use-swift: no backend for this platform') },
|
||||
display = darwin?.display ?? {
|
||||
getSize() { throw new Error('@ant/computer-use-swift: macOS only') },
|
||||
listAll() { throw new Error('@ant/computer-use-swift: macOS only') },
|
||||
}
|
||||
|
||||
screenshot = backend?.screenshot ?? {
|
||||
async captureExcluding() { throw new Error('computer-use-swift: no backend for this platform') },
|
||||
async captureRegion() { throw new Error('computer-use-swift: no backend for this platform') },
|
||||
}
|
||||
|
||||
hotkey = (backend as any)?.hotkey ?? {
|
||||
registerEscape(_cb: () => void): boolean { return false },
|
||||
unregister() {},
|
||||
notifyExpectedEscape() {},
|
||||
screenshot = darwin?.screenshot ?? {
|
||||
async captureExcluding() { throw new Error('@ant/computer-use-swift: macOS only') },
|
||||
async captureRegion() { throw new Error('@ant/computer-use-swift: macOS only') },
|
||||
}
|
||||
|
||||
async resolvePrepareCapture(
|
||||
@@ -93,8 +59,6 @@ export class ComputerUseAPI {
|
||||
targetW: number,
|
||||
targetH: number,
|
||||
displayId?: number,
|
||||
_autoResolve?: boolean,
|
||||
_doHide?: boolean,
|
||||
): Promise<ResolvePrepareCaptureResult> {
|
||||
return this.screenshot.captureExcluding(allowedBundleIds, quality, targetW, targetH, displayId)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user