🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

Mac 工具箱 ddxl123/dsh-mac-tools

DeepSeek 客户端
ddxl12311 天前有提交已提供中文文档扫描:含敏感能力兼容 / 相关生态

截图标注、计算器、剪贴板与系统监控一站搞定

29.8/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/26 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

相关生态插件(非 dsh 原生,按其对应运行时安装)
git clone https://github.com/ddxl123/dsh-mac-tools.git

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
生态应用
桌面端 / Web 外壳,不以 dsh plugin add 安装
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 11 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 129 处,展示前 119 条)
  • 读取本地文件
    src/index.js:73
    const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
  • 写入本地文件
    src/index.js:105
    mkdirSync(saveDirectory, { recursive: true })
  • 写入本地文件
    src/index.js:116
    mkdirSync(directory, { recursive: true, mode: 0o700 })
  • 写入剪贴板
    client/index.js:6501
    Promise.resolve(clipboard.writeText(url)).then(() => {
  • 读取本地文件
    src/capture.js:33
    if (candidate.includes('/') && existsSync(candidate)) return candidate
  • 执行外部命令来自配置/变量
    src/capture.js:88
    const child = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'] })
  • 读取本地文件
    src/capture.js:466
    info = await stat(filePath)
  • 读取本地文件
    src/capture.js:475
    const buffer = await readFile(filePath)
  • 执行外部命令来自配置/变量
    src/capture.js:549
    const child = spawn(AFPLAY, [`${SOUNDS_DIRECTORY}/${name}.aiff`], { stdio: 'ignore', detached: true })
  • 读取本地文件
    src/hotkey.js:160
    if (existsSync(candidate)) return candidate
  • 读取本地文件
    src/hotkey.js:187
    const source = readFileSync(sourcePath)
  • 读取本地文件
    src/hotkey.js:190
    if (existsSync(binary)) return binary
  • 写入本地文件
    src/hotkey.js:191
    mkdirSync(cacheDirectory, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/hotkey.js:197
    chmodSync(binary, 0o700)
  • 删除本地文件
    src/hotkey.js:299
    rmSync(socketPath, { force: true })
  • 写入本地文件
    src/hotkey.js:330
    chmodSync(socketPath, 0o600)
  • 删除本地文件
    src/hotkey.js:463
    rmSync(this.socketPath, { force: true })
  • 读取本地文件
    src/hotkey.js:519
    const document = JSON.parse(readFileSync(statePath, 'utf8'))
  • 写入本地文件
    src/hotkey.js:539
    mkdirSync(dirname(statePath), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/hotkey.js:541
    writeFileSync(temporary, `${JSON.stringify({ accelerator }, null, 2)}\n`, { mode: 0o600 })
  • 写入本地文件
    src/hotkey.js:542
    renameSync(temporary, statePath)
  • 删除本地文件
    src/hotkey.js:552
    rmSync(statePath, { force: true })
  • 写入本地文件
    src/history.js:43
    mkdirSync(this.directory, { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/history.js:56
    document = JSON.parse(readFileSync(this.indexPath, 'utf8'))
  • 读取本地文件
    src/history.js:64
    return existsSync(join(this.directory, entry.file))
  • 写入本地文件
    src/history.js:72
    writeFileSync(temporary, `${JSON.stringify({ version: 1, entries: this.entries }, null, 2)}\n`, { mode: 0o600 })
  • 写入本地文件
    src/history.js:73
    renameSync(temporary, this.indexPath)
  • 写入本地文件
    src/history.js:87
    writeFileSync(join(this.directory, file), capture.buffer, { mode: 0o600 })
  • 删除本地文件
    src/history.js:121
    rmSync(join(this.directory, entry.file), { force: true })
  • 读取本地文件
    src/history.js:165
    return readFileSync(join(this.directory, entry.file))
  • 读取本地文件
    src/history.js:197
    total += statSync(join(this.directory, entry.file)).size
  • 读取本地文件
    src/ops.js:352
    return { buffer: readFileSync(record.path), path: record.path, bytes: record.bytes, source: 'shot', record }
  • 写入本地文件
    src/ops.js:373
    mkdirSync(directory, { recursive: true })
  • 写入本地文件
    src/ops.js:375
    writeFileSync(path, source.buffer, { mode: 0o644 })
  • 读取本地文件
    src/ops.js:415
    if (!existsSync(absolute)) throw new OpsError('not_found', `the file does not exist: ${absolute}`, { path: absolute })
  • 读取本地文件
    src/ops.js:460
    await pins.pin(readFileSync(record.path), record.rect === undefined ? {} : { rect: record.rect })
  • 读取本地文件
    src/ops.js:498
    buffer: readFileSync(record.path),
  • 写入本地文件
    src/ops.js:522
    writeFileSync(path, buffer, { mode: 0o600 })
  • 读取本地文件
    src/ops.js:543
    const buffer = readFileSync(pinRecord.path)
  • 读取本地文件
    src/pin.js:106
    const source = readFileSync(sourcePath)
  • 读取本地文件
    src/pin.js:109
    if (existsSync(binary)) return binary
  • 写入本地文件
    src/pin.js:110
    mkdirSync(cacheDirectory, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/pin.js:116
    chmodSync(binary, 0o700)
  • 写入本地文件
    src/pin.js:183
    mkdirSync(this.tempDirectory, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/pin.js:185
    writeFileSync(imagePath, buffer, { mode: 0o600 })
  • 删除本地文件
    src/pin.js:248
    rmSync(this.eventSocketPath, { force: true })
  • 写入本地文件
    src/pin.js:281
    chmodSync(this.eventSocketPath, 0o600)
  • 删除本地文件
    src/pin.js:355
    rmSync(imagePath, { force: true })
  • 删除本地文件
    src/pin.js:381
    rmSync(this.eventSocketPath, { force: true })
  • 删除本地文件递归
    src/pin.js:386
    rmSync(this.tempDirectory, { recursive: true, force: true })
  • 发起网络请求
    src/route.js:52
    async fetch(request) {
  • 写入本地文件
    src/store.js:62
    mkdirSync(this.directory, { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/store.js:78
    const info = statSync(capture.path)
  • 写入本地文件
    src/store.js:106
    writeFileSync(path, buffer, { mode: 0o600 })
  • 读取本地文件
    src/store.js:132
    return readFileSync(record.path)
  • 删除本地文件
    src/store.js:141
    unlinkSync(dropped.path)
  • 删除本地文件递归
    src/store.js:153
    rmSync(this.directory, { recursive: true, force: true })
  • 读取本地文件
    src/transfer.js:232
    if (!existsSync(join(directory, name))) return name
  • 读取本地文件
    src/transfer.js:237
    if (!existsSync(join(directory, candidate))) return candidate
  • 读取本地文件
    src/transfer.js:265
    if (code === 'EEXIST' || existsSync(target)) {
  • 写入本地文件
    src/transfer.js:270
    renameSync(tempPath, target)
  • 删除本地文件
    src/transfer.js:274
    rmSync(tempPath, { force: true })
  • 写入本地文件
    src/transfer.js:536
    const output = createWriteStream(filePath, { mode: 0o600 })
  • 读取本地文件
    src/transfer.js:587
    if (item.kind === 'file' && (typeof item.path !== 'string' || item.path === '' || !existsSync(item.path))) return false
  • 写入本地文件
    src/transfer.js:807
    mkdirSync(this.directory, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/transfer.js:808
    mkdirSync(this.stateDirectory, { recursive: true, mode: 0o700 })
  • 执行外部命令固定命令
    src/transfer.js:1079
    const child = spawn('/usr/bin/caffeinate', ['-i', '-s', '-w', String(process.pid)], { stdio: 'ignore' })
  • 读取本地文件
    src/transfer.js:1147
    document = JSON.parse(readFileSync(this.indexPath, 'utf8'))
  • 写入本地文件
    src/transfer.js:1185
    writeFileSync(temporary, payload, { mode: 0o600 })
  • 写入本地文件
    src/transfer.js:1186
    renameSync(temporary, this.indexPath)
  • 删除本地文件
    src/transfer.js:1189
    rmSync(temporary, { force: true })
  • 删除本地文件
    src/transfer.js:1256
    rmSync(item.path, { force: true })
  • 删除本地文件
    src/transfer.js:1279
    rmSync(item.path, { force: true })
  • 读取本地文件
    src/transfer.js:1323
    if (!existsSync(item.path)) throw new TransferError('not_found', 'the received file no longer exists', { id, path: item.path })
  • 执行外部命令固定命令
    src/transfer.js:1325
    const child = spawn('/usr/bin/open', ['-R', item.path], { stdio: 'ignore', detached: true })
  • 删除本地文件
    src/transfer.js:1740
    rmSync(tempPath, { force: true })
  • 删除本地文件
    src/transfer.js:1756
    rmSync(tempPath, { force: true })
  • 读取本地文件
    src/transfer-qr.js:52
    if (existsSync(candidate)) return candidate
  • 执行外部命令来自配置/变量
    src/transfer-qr.js:76
    const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] })
  • 读取本地文件
    src/transfer-qr.js:154
    source = readFileSync(sourcePath)
  • 读取本地文件
    src/transfer-qr.js:160
    if (!existsSync(binary)) {
  • 写入本地文件
    src/transfer-qr.js:162
    mkdirSync(cacheDirectory, { recursive: true, mode: 0o700 })
  • 读取本地文件
    src/transfer-qr.js:175
    compiledBytes = statSync(temporary).size
  • 写入本地文件
    src/transfer-qr.js:183
    chmodSync(temporary, 0o700)
  • 写入本地文件
    src/transfer-qr.js:188
    renameSync(temporary, binary)
  • 读取本地文件
    src/transfer-qr.js:191
    if (!existsSync(binary)) throw failure
  • 删除本地文件
    src/transfer-qr.js:192
    rmSync(temporary, { force: true })
  • 删除本地文件
    src/transfer-qr.js:196
    rmSync(temporary, { force: true })
  • 读取本地文件
    src/transfer-qr.js:203
    if (!existsSync(binary)) {
  • 读取本地文件
    src/visibility.js:35
    if (candidate.includes('/') && existsSync(candidate)) return candidate
  • 执行外部命令来自配置/变量
    src/visibility.js:291
    const child = spawn(OSASCRIPT, ['-l', 'JavaScript', '-e', buildVisibilityScript(this.target, 'show')], {
  • 读取本地文件
    src/selection.js:24
    const OSASCRIPT = existsSync('/usr/bin/osascript') ? '/usr/bin/osascript' : 'osascript'
  • 删除本地文件
    src/selection.js:174
    rmSync(this.outputPath, { force: true })
  • 读取本地文件
    src/selection.js:195
    return parseSelectionReport(readFileSync(this.outputPath, 'utf8'))
  • 删除本地文件递归
    src/selection.js:240
    rmSync(this.directory, { recursive: true, force: true })
  • 执行外部命令来自配置/变量
    src/windows.js:143
    execFile(command, args, { timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024 }, (error, stdout, stderr) => {
  • 读取本地文件
    src/alerts.js:104
    text = readFileSync(statePath, 'utf8')
  • 写入本地文件
    src/alerts.js:246
    mkdirSync(dirname(this.statePath), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/alerts.js:248
    writeFileSync(temporary, `${JSON.stringify({ version: SETTINGS_VERSION, settings: this.settings }, null, 2)}\n`, { mode: 0o600 })
  • 写入本地文件
    src/alerts.js:249
    renameSync(temporary, this.statePath)
  • 读取本地文件
    src/fans.js:564
    const parsed = JSON.parse(readFileSync(this.presetsPath, 'utf8'))
  • 写入本地文件
    src/fans.js:587
    mkdirSync(dirname(this.presetsPath), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/fans.js:589
    writeFileSync(temporary, `${JSON.stringify({ version: 1, presets: this.customPresets }, null, 2)}\n`, { mode: 0o600 })
  • 写入本地文件
    src/fans.js:590
    renameSync(temporary, this.presetsPath)
  • 读取本地文件
    src/fans.js:652
    return existsSync(this.plistPath) && existsSync(this.installBinary)
  • 写入本地文件
    src/fans.js:781
    writeFileSync(scriptPath, script, { mode: 0o700 })
  • 删除本地文件递归
    src/fans.js:803
    rmSync(scriptDirectory, { recursive: true, force: true })
  • 发起网络请求
    src/fans.js:849
    await this.request({ op: 'autoAll' }, 2000).catch(() => {})
  • 写入本地文件
    src/fans.js:856
    writeFileSync(scriptPath, buildUninstallScript({
  • 删除本地文件递归
    src/fans.js:876
    rmSync(scriptDirectory, { recursive: true, force: true })
  • 发起网络请求
    src/fans.js:917
    this.request({ op: 'ping' }, 3000).then((response) => {
  • 发起网络请求
    src/fans.js:1045
    await this.request({ op: 'autoAll' }, 5000).catch(() => {})
  • 发起网络请求
    src/fans.js:1100
    const response = await this.request({ op: 'set', fan: index, rpm }, 5000)
  • 执行外部命令来自配置/变量
    src/smc.js:195
    const child = spawn(binary, ['--once'], { stdio: ['ignore', 'pipe', 'pipe'] })
  • 读取本地文件
    src/swift.js:32
    if (existsSync(candidate)) return candidate
  • 读取本地文件
    src/swift.js:79
    for (const file of sources) digest.update(readFileSync(file))
  • 读取本地文件
    src/swift.js:82
    if (existsSync(binary)) return binary
  • 写入本地文件
    src/swift.js:83
    mkdirSync(cacheDirectory, { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/swift.js:92
    chmodSync(binary, 0o700)
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
Mac 工具箱(DeepSeek Harness 插件) 一个 DeepSeek Harness 插件,装好即是一整套 本机 Mac 工具箱: - 截图(类 QQ)——用 macOS 原生区域/窗口选择框选任意应用,然后进入真正的编辑器:裁剪、箭头、矩形、椭圆、画笔、文字、马赛克、高亮,支持撤销/重做、复制到剪贴板、保存到磁盘。 - 计算器——完整的计算器面板,以及智能体可以精确调用的表达式引擎。 - 手机互传——手机和 Mac 连同一个 Wi-Fi,用手机相机扫二维码,就能用浏览器收发文本、文件、照片;不用装 App,文件不出局域网。 - 剪贴板——在面板或智能体里读写 macOS 文本剪贴板。 - 运行状态——类 App Store 应用 State 的实时监控:CPU/内存/磁盘/网络/电池曲线、系统信息、SMC 温度与风扇、进程排行、阈值告警;以及类 Macs Fan Control 的风扇转速控制(自动 / 手动转速 / 温度曲线 / 预设),首次启用需一次性管理员授权。原「系统信息」页已并入这里。 全部在本机完成。这是一个纯面板插件:只在左侧栏增加 Mac 工具箱 入口,不向智能体注册任何工具。 安装 这是一个普通的 DSH bundle。
展开完整文档(README 剩余 1 节)
Mac 工具箱(DeepSeek Harness 插件)

一个 DeepSeek Harness 插件,装好即是一整套 本机 Mac 工具箱:

- 截图(类 QQ)——用 macOS 原生区域/窗口选择框选任意应用,然后进入真正的编辑器:裁剪、箭头、矩形、椭圆、画笔、文字、马赛克、高亮,支持撤销/重做、复制到剪贴板、保存到磁盘。
- 计算器——完整的计算器面板,以及智能体可以精确调用的表达式引擎。
- 手机互传——手机和 Mac 连同一个 Wi-Fi,用手机相机扫二维码,就能用浏览器收发文本、文件、照片;不用装 App,文件不出局域网。
- 剪贴板——在面板或智能体里读写 macOS 文本剪贴板。
- 运行状态——类 App Store 应用 State 的实时监控:CPU/内存/磁盘/网络/电池曲线、系统信息、SMC 温度与风扇、进程排行、阈值告警;以及类 Macs Fan Control 的风扇转速控制(自动 / 手动转速 / 温度曲线 / 预设),首次启用需一次性管理员授权。原「系统信息」页已并入这里。

全部在本机完成。这是一个纯面板插件:只在左侧栏增加 Mac 工具箱 入口,不向智能体注册任何工具。

安装

这是一个普通的 DSH bundle。在任意会话里对插件目录安装即可:

plugin_manager install_bundle target: /绝对路径/dsh-mac-tools

或使用 CLI:

dsh plugin --profile desktop add /绝对路径/dsh-mac-tools

本地目录安装是链接方式。安装后刷新 Web 页面,左侧栏会出现 Mac 工具箱。如果安装时 Host 已经在运行,最稳妥的方式是重启一次 DeepSeek Harness,让两个半端版本一致。在创造模式(启用 HMR)下无需重启:在插件页把 mac-tools 行关掉再打开,会触发配置重组并原地重新加载链接的 Host 代码。

权限

截图需要 系统设置 → 隐私与安全性 → 屏幕录制 → 允许 DeepSeek Harness。没有授权时 macOS 会静默返回一张只有壁纸的图片(或拒绝窗口截图),面板会显示 permission 失败码并提示去哪个开关授权。

不需要辅助功能(Accessibility)权限:区域/窗口选择用的是 macOS 自带的原生选择界面,插件只额外读写剪贴板。

面板

从左侧栏打开,或按 ⌥⌘M。⇧⌘A 直接开始一次交互截图,并打开面板进入编辑器。

⌃⌥A(可配置)是系统级截图快捷键:在任何其它应用拥有焦点时也能按。
它会拉起 macOS 原生区域/窗口选择,然后把截图贴回原来的位置(置顶悬浮窗,覆盖刚才框选的屏幕区域),同时复制到剪贴板、保存到截图目录,并播放 Glass 提示音(不需要自动贴图可设 pinAfterGlobalCapture: false)。
不需要额外权限;原生小助手首次会用 Xcode Command Line Tools 编译一次(开发者 Mac 通常已装)。

快捷键可以在面板里直接自定义:截图按钮下方的「全局截图快捷键」一行点 修改,然后按下新组合(Control/Option/Command + 一个按键;Esc 取消)。自定义结果保存在 profile 的插件状态目录(<profile home>/dsh-mac-tools/hotkey.json),重启不丢失;点 恢复默认 回到 globalHotkey 配置值。
面板内的快速截图快捷键(默认 ⇧⌘A)是 DSH 普通快捷键,可在 设置 → 快捷键 中改键。

截图页

- 区域 / 窗口截图——屏幕变暗后拖拽框选;按 空格 切换到窗口点选模式,按 Esc 取消。
- 全屏截图、窗口截图(点选一个窗口)、3 秒后全屏(延时截图)、选择应用窗口(列出所有可见应用窗口,可搜索,前台窗口有标记,点击即截取)。
- 历史截图:每一次截图(面板截图或 ⌥A 全局截图)都会自动持久保存,并在编辑器下方以缩略图列表展示;点击可重新载入编辑器,也可直接贴图 / 复制 / 删除。
- 贴图可当场标注:双击贴图(或按 A、右键「标注图片」),贴图上会浮出小工具栏——画笔 / 箭头 / 矩形 / 椭圆 / 高亮、颜色、粗细、撤销、清空、完成;在贴图上直接画,复制/保存会包含标注。想用完整面板编辑器时,右键选择「在面板中编辑」即可。
- 「截图时隐藏 DSH」(默认开启):屏幕截图时自动隐藏 DeepSeek Harness 窗口,截完立即恢复,避免自己的窗口挡住要截的内容;按钮就在截图工具栏上,可随时关闭。
该开关同样作用于全局快捷键截图——快捷键截图没有页面可询问,所以由 Host 记住这个选择(当前 DSH 进程内有效)。
- 编辑器支持 裁剪、箭头、矩形、椭圆、画笔、文字、马赛克、高亮,六种颜色、三档线宽,撤销/重做/还原,复制(PNG 进剪贴板)、保存(保存到配置目录,并可在 Finder 中显示)与 贴图:把编辑后的图片作为置顶悬浮窗钉在桌面上,位置就是截图时框选的屏幕区域。之后可拖动、滚轮缩放、右键菜单(复制 / 保存 / 不透明度 / 关闭),Esc 关闭,⌘C 复制。
- 编辑器快捷键:⌘Z 撤销、⇧⌘Z 重做、Enter 应用裁剪、Esc 先取消裁剪再关闭截图。

计算器页

显示屏 + 键盘 + 历史记录。计算走 Host 的引擎,因此面板和工具对同一个表达式给出相同结果。支持 + - * / % ^、括号、正负号、隐式乘法(2(3+4))、百分号(50%)、常量 pi/e/tau,以及函数 sqrt, cbrt, abs, round, floor, ceil, trunc, sign, exp, ln, log, log2, sin, cos, tan, asin, acos, atan, atan2, pow, hypot, min, max, mod。三角函数默认按角度制计算。

手机互传页

一个只在局域网内工作的 Mac ↔ 手机中转站。点「开启手机互传」后,Host 会临时起一个 HTTP 监听,显示二维码(和 URL),并开始倒计时。用手机系统相机扫码(或手动输入 URL)即可打开一个免登录网页——不用装 App、不用账号、不走云。

- 手机 → Mac:发文本(进面板收件箱,可一键复制到 macOS 剪贴板,也可配置自动复制)、选文件/照片,或直接拍照上传。上传带进度,文件保存到传输目录(默认 ~/Downloads/DSH 手机互传),智能体可以像普通文件一样读取处理。
- Mac → 手机:输入文本发送、用系统选择框选任意文件,或在最近截图上一键「发送到手机」;手机页面会列出这些内容,可预览、复制或下载。截图直接取自历史记录。
- 安全:每次会话一个随机 token,30 分钟过期;监听只在会话期间存在;服务端校验 Host 头、净化文件名,不向公网暴露。下载响应固定为 attachment + application/octet-stream(绝不当网页渲染),交给手机浏览器/下载管理器原生保存。
- 注意:手机和 Mac 必须连同一个 Wi-Fi(开了 AP 隔离的访客网络不行);第一次开启时 macOS 可能询问是否允许 DeepSeek Harness 接受传入连接;手机 VPN 可能拦截局域网访问;较新的 macOS 还可能询问本地网络权限。传输期间别让 Mac 休眠。
- 排障:如果手机上传一直停在 0%,或点下载没反应,先把页面重新打开一次再重试;手机页会把浏览器异常、上传超时/停滞、下载失败上报到 <插件状态目录>/transfer/diagnostics.log(即 ~/.dsh/dsh-mac-tools/transfer/diagnostics.log),同时记录每次上传开始/完成与下载请求,不用远程调试就能看出是哪一步失败。

配置

bundle 行支持以下配置(写在 cordis.patch.yml 或之后的 patch 层),都有默认值:

| 键 | 默认值 | 含义 |
| saveDirectory | ~/Pictures/DSH Mac Tools | 截图保存目录,支持 ~ 展开。面板的“保存”和工具都写到这里。 |
| maxShots | 20 | 面板临时保留的截图数量,超过后淘汰最旧的;已保存文件不会被淘汰。 |
| interactiveTimeoutMs | 180000 | 原生选择界面等待操作者的最长时间。 |
| hideAppDuringCapture | true | 屏幕截图时短暂隐藏 DeepSeek Harness 窗口(仅当它是前台应用)并在截完后恢复;面板上有单次开关。 |
| globalHotkey | ctrl+alt+a | 系统级截图快捷键的默认值,任何应用在前台时都可用;面板里修改的值会覆盖它(保存到插件状态文件);留空则初始不启用全局快捷键。 |
| globalHotkeySound | true | 全局快捷键截图成功后播放 Glass 提示音。 |
| pinAfterGlobalCapture | true | 全局快捷键截图后自动贴图到屏幕(面板编辑器里也始终有手动「贴图」按钮)。 |
| historyLimit | 60 | 历史截图最多保留多少张,超出后删除最旧的。 |
| transferPort | 8791 | 手机互传临时监听的优先端口;被占用时顺延尝试后面 9 个。 |
| transferDirectory | ~/Downloads/DSH 手机互传 | 手机发来的文件保存目录,支持 ~ 展开。 |
| transferSessionMinutes | 30 | 一次手机互传会话的有效期,到期后二维码失效、监听停止。 |
| transferMaxFileMB | 512 | 手机单次上传文件大小上限(MB)。 |
| transferAutoCopyText | false | 收到手机文本时自动复制到 macOS 剪贴板。 |
| monitorSampleMs | 1000 | 「运行状态」页 Host 侧采样间隔(毫秒,500–10000)。软件指标最多每 2 秒采集一次,面板轮询频率可以更快或更慢。 |
| alertCpuPercent | 90 | 首次运行的 CPU 占用告警阈值(%)。面板运行时可改,持久化的选择优先于该默认值。 |
| alertTemperatureCelsius | 95 | 首次运行的温度告警阈值(°C,取 SMC 温度组)。 |
| alertMemoryPercent | 90 | 首次运行的内存占用告警阈值(%)。 |
| alertCooldownSeconds | 300 | 同一指标两次告警之间的最短间隔(秒)。 |
| alertNotify | true | 告警触发时是否发 macOS 通知。 |

- id: mac-tools
config:
saveDirectory: ~/Desktop/Screenshots
maxShots: 30

实现说明

- 原生小助手(assets/):hotkey.swift 监听系统级快捷键,pin.swift 把贴图显示为浮动的 AppKit 窗口,qrcode.swift 用 CoreImage 生成手机互传二维码;三者首次用 swiftc 编译并缓存到临时目录。smc-core.swift 是共享的只读 IOKit AppleSMC 传输层;state.swift 是常驻采样器,每个采样输出一行 JSON(风扇 + 全部可读温度键);fan-daemon.swift 是风扇卡片安装的特权助手——只提供 Unix socket JSON 的 F0Md/F0Tg 写入,目标转速夹在 SMC 上下限内,并带 12 秒看门狗自动恢复系统自动控制。
- Host 半端(src/):在 Connection 的 /api 通道上注册受认证的 /api/dsh-mac-tools 路由,维护临时截图注册表,用 /usr/sbin/screencapture 截图、用 /usr/bin/osascript 获取窗口列表、写图片剪贴板和打开文件选择框。「运行状态」页是一条采样循环(src/monitor.js),底层是 src/metrics.js(CPU/内存/磁盘/网络/电池)、src/smc.js(采样器子进程)、src/alerts.js(阈值)与 src/fans.js(风扇控制与 socket 协议)。手机互传监听(src/transfer.js)是一个独立的、按需启动的 node:http 服务,只在会话期间绑定局域网;它提供手机网页和上传/下载 API,收发索引存在插件状态目录。不注册任何智能体工具。
- Client 半端(client/index.js):一个由 window.__ModuleLoader__ 加载的纯 React bundle,注册左侧栏入口和 main 面板,渲染四个分页,只通过上面的路由与 Host 通信。
- 截图引擎是对系统工具的一层薄封装——区域/窗口选择、取消、窗口点选都是 macOS 原生行为,不是重新实现。

POST /api/dsh-mac-tools { "endpoint": "...", "payload": { ... } }
GET /api/dsh-mac-tools?shot=<id> → image/png
GET /api/dsh-mac-tools?history=<id> → image/png
GET /api/dsh-mac-tools?inbox=<id> → 手机发来文件的字节

?inbox= 预览只对安全的栅格图片(png、jpeg、gif、webp、avif、bmp)内联返回;其它文件一律以 application/octet-stream 附件返回,手机上传的 HTML/SVG 因此不可能在 Web UI 源上执行。

端点:status、windows、capture、cancelCapture、copy、save、calc、clipboard、system、reveal,手机互传的 transferStatus、transferStart、transferStop、transferInbox、transferOutbox,运行状态的 monitor、monitorProcesses、monitorKill、monitorAlertSave、monitorAlertTest,以及风扇控制的 fanInstall、fanUninstall、fanApply、fanPreset。返回 { ok: true, value } 或 { ok: false, error: { code, message, details } }。

手机访问的是它自己的临时服务 http://<Mac局域网IP>:<端口>/t/<token>/(页面、api/state、api/text、api/upload、api/items/<id>),那不是 Harness 的 API,也不携带 Harness 会话。

开发

npm run check # 所有模块 node --check + node --test test/

Host 半端除 dsh 自带的 @deepseek-ai/schemastery 配置 schema 外无第三方依赖;Client 半端只需要 react。

许可证

MIT
预览分享卡片长什么样 ↓ddxl123/dsh-mac-tools
分享 · DeepSeek Harness Hub
ddxl123/dsh-mac-tools

截图标注、计算器、剪贴板与系统监控一站搞定

这是你的仓库?认领条目

回到顶部