mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
feat: 实现 4 个 NAPI 包 — modifiers/image-processor/audio-capture/url-handler
- modifiers-napi: 使用 Bun FFI 调用 macOS CGEventSourceFlagsState 检测修饰键 - image-processor-napi: 集成 sharp 库,macOS 剪贴板图像读取 (osascript) - audio-capture-napi: 基于 SoX/arecord 的跨平台音频录制 - url-handler-napi: 完善函数签名(保持 null fallback) - 修复 image-processor 类型兼容性问题 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,5 +4,8 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
"types": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"sharp": "^0.33.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,125 @@
|
||||
export function getNativeModule(): null {
|
||||
import sharpModule from 'sharp'
|
||||
|
||||
export const sharp = sharpModule
|
||||
|
||||
interface NativeModule {
|
||||
hasClipboardImage(): boolean
|
||||
readClipboardImage(
|
||||
maxWidth?: number,
|
||||
maxHeight?: number,
|
||||
): {
|
||||
png: Buffer
|
||||
width: number
|
||||
height: number
|
||||
originalWidth: number
|
||||
originalHeight: number
|
||||
} | null
|
||||
}
|
||||
|
||||
function createDarwinNativeModule(): NativeModule {
|
||||
return {
|
||||
hasClipboardImage(): boolean {
|
||||
try {
|
||||
const result = Bun.spawnSync({
|
||||
cmd: [
|
||||
'osascript',
|
||||
'-e',
|
||||
'try\nthe clipboard as «class PNGf»\nreturn "yes"\non error\nreturn "no"\nend try',
|
||||
],
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
})
|
||||
const output = result.stdout.toString().trim()
|
||||
return output === 'yes'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
readClipboardImage(
|
||||
maxWidth?: number,
|
||||
maxHeight?: number,
|
||||
) {
|
||||
try {
|
||||
// Use osascript to read clipboard image as PNG data and write to a temp file,
|
||||
// then read the temp file back
|
||||
const tmpPath = `/tmp/claude_clipboard_native_${Date.now()}.png`
|
||||
const script = `
|
||||
set png_data to (the clipboard as «class PNGf»)
|
||||
set fp to open for access POSIX file "${tmpPath}" with write permission
|
||||
write png_data to fp
|
||||
close access fp
|
||||
return "${tmpPath}"
|
||||
`
|
||||
const result = Bun.spawnSync({
|
||||
cmd: ['osascript', '-e', script],
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
})
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const file = Bun.file(tmpPath)
|
||||
// Use synchronous read via Node compat
|
||||
const fs = require('fs')
|
||||
const buffer: Buffer = fs.readFileSync(tmpPath)
|
||||
|
||||
// Clean up temp file
|
||||
try {
|
||||
fs.unlinkSync(tmpPath)
|
||||
} catch {
|
||||
// ignore cleanup errors
|
||||
}
|
||||
|
||||
if (buffer.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Read PNG dimensions from IHDR chunk
|
||||
// PNG header: 8 bytes signature, then IHDR chunk
|
||||
// IHDR starts at offset 8 (4 bytes length) + 4 bytes "IHDR" + 4 bytes width + 4 bytes height
|
||||
let width = 0
|
||||
let height = 0
|
||||
if (buffer.length > 24 && buffer[12] === 0x49 && buffer[13] === 0x48 && buffer[14] === 0x44 && buffer[15] === 0x52) {
|
||||
width = buffer.readUInt32BE(16)
|
||||
height = buffer.readUInt32BE(20)
|
||||
}
|
||||
|
||||
const originalWidth = width
|
||||
const originalHeight = height
|
||||
|
||||
// If maxWidth/maxHeight are specified and the image exceeds them,
|
||||
// we still return the full PNG - the caller handles resizing via sharp
|
||||
// But we report the capped dimensions
|
||||
if (maxWidth && maxHeight) {
|
||||
if (width > maxWidth || height > maxHeight) {
|
||||
const scale = Math.min(maxWidth / width, maxHeight / height)
|
||||
width = Math.round(width * scale)
|
||||
height = Math.round(height * scale)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
png: buffer,
|
||||
width,
|
||||
height,
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function getNativeModule(): NativeModule | null {
|
||||
if (process.platform === 'darwin') {
|
||||
return createDarwinNativeModule()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const stub: any = {}
|
||||
export default stub
|
||||
export default sharp
|
||||
|
||||
Reference in New Issue
Block a user