
Lovstudio Finder Action
- 10 installs
- Updated August 4, 2026
- lovstudio/dev-skills
Helps with ai & agent building tasks.
About
lovstudio-finder-action is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- lovstudio-finder-action
- AI & Agent Building
- AI-coding skill
Lovstudio Finder Action by the numbers
- 10 all-time installs (skills.sh)
- +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #11,959 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lovstudio/dev-skills --skill lovstudio-finder-actionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 10 |
|---|---|
| Last updated | August 4, 2026 |
| Repository | lovstudio/dev-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
finder-action — Mac Finder 右键菜单动作生成器
根据用户描述生成 Finder 右键菜单动作,自动判断模式:
| 场景 | 模式 | 技术方案 |
|---|---|---|
| 右键文件/文件夹 | Quick Action | Automator workflow |
| 右键空白处 | Finder Extension | Swift + xcodegen |
参数格式
<动作名称> [触发描述]
示例:
pdf2png .pdf 将PDF所有页面纵向拼接成一张PNG→ Quick Action新建md文件 在空白处右键创建markdown文件→ Finder Extension
模式判断
关键词命中 → Finder Extension 模式:
- 提到「空白处」「背景」「目录背景」「新建文件」「blank space」「background」
- 动作不需要选中文件即可触发
其他情况 → Quick Action 模式
---
Mode A: Quick Action(Automator workflow)
Step 1: 分析需求
收集(缺失时 AskUserQuestion):
- 动作名称:右键菜单显示名
- 触发文件类型:
.pdf、.md、.jpg等 - 核心命令:用什么工具做什么
Step 2: 检查依赖
which <所需工具>不存在则提示 brew install <tool>。
Step 3: 生成 shell 脚本
#!/bin/bash
for f in "$@"; do
[[ "$f" == *.<ext> ]] || continue
output="${f%.<ext>}.<out_ext>"
<具体命令> "$f" -o "$output"
done- 工具路径用绝对路径(Quick Action 环境没有
$PATH) "$@"接收文件参数(inputMethod=1)
Step 4: 创建 Automator workflow
创建 ~/Library/Services/<动作名称>.workflow/Contents/document.wflow。
模板见 references/automator-template.xml。
关键配置:
inputMethod:1serviceInputTypeIdentifier:com.apple.Automator.fileSystemObjectworkflowTypeIdentifier:com.apple.Automator.servicesMenu
Step 5: 验证注册
plutil -lint ~/Library/Services/<动作名称>.workflow/Contents/document.wflow
/System/Library/CoreServices/pbs -update
killall FinderStep 6: Automator 保存(关键)
open -a Automator ~/Library/Services/<动作名称>.workflow必须在 Automator 中 Cmd+S 保存一次才会正式注册。
---
Mode B: Finder Sync Extension(Swift app)
Step 1: 检查工具链
which xcodegen && which xcodebuild缺 xcodegen 则 brew install xcodegen。
Step 2: 创建项目结构
<ProjectName>/
├── project.yml
├── <ProjectName>/
│ └── AppDelegate.swift
└── FinderExtension/
└── FinderSync.swiftStep 3: 生成 project.yml
模板见 references/xcodegen-template.yml。替换 APP_NAME 和 BUNDLE_ID。
关键点:
- 宿主 App:
LSUIElement: true(无 Dock 图标) - Extension:
NSExtensionPointIdentifier: com.apple.FinderSync - 签名:
CODE_SIGN_IDENTITY: "-"(ad-hoc) - 沙盒必须开启(
app-sandbox: true),否则扩展不会被系统加载 - 文件写入需用
temporary-exception.files.absolute-path.read-write: [/],files.user-selected.read-write无效
Step 4: 生成 AppDelegate.swift
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {}
}Step 5: 生成 FinderSync.swift
模板见 references/finder-sync-template.swift。
核心 API:
FIFinderSyncController.default().directoryURLs = [URL(fileURLWithPath: "/")]— 监控所有目录menu(for: .contextualMenuForContainer)— 空白处右键菜单FIFinderSyncController.default().targetedURL()— 获取当前目录
常见 action 模式:
- 创建文件:
FileManager.default.createFile+ 自动递增文件名 - 打开终端:AppleScript 控制 iTerm2/Terminal(见
references/applescript-iterm.swift) - 执行脚本:
Process()启动 shell 命令
AppleScript 自动化需要在 entitlements 中添加:
com.apple.security.automation.apple-events: trueStep 6: 构建安装
xcodegen generate
xcodebuild -project APP_NAME.xcodeproj -scheme APP_NAME -configuration Debug build
cp -R ~/Library/Developer/Xcode/DerivedData/APP_NAME-*/Build/Products/Debug/APP_NAME.app /Applications/
open /Applications/APP_NAME.app
pluginkit -e use -i BUNDLE_ID.FinderExtensionStep 7: 验证
pluginkit -m -i BUNDLE_ID.FinderExtension不出现时指引:系统设置 → 通用 → 登录项与扩展 → 已添加的扩展 → 勾选。
沙盒限制与 Helper App 方案
Finder Sync Extension 的沙盒限制非常严格:
| 操作 | 是否允许 | 说明 |
|---|---|---|
| 写入 /tmp | ❌ | 即使添加 temporary-exception 也被阻止 |
| Process() 子进程 | ❌ | 无法启动外部命令 |
| NSAppleScript | ❌ | 无法控制其他应用 |
| NSWorkspace.open(file) | ❌ | 无法打开文件/目录 |
| NSWorkspace.open(app) | ✅ | 可以打开应用 |
| NSPasteboard | ✅ | 可以读写剪贴板 |
推荐方案:创建一个非沙盒的 Helper App,Extension 把命令放入剪贴板后打开 Helper App,由 Helper App 执行实际操作。
Helper App 示例
mkdir -p "/Applications/OpenCCHelper.app/Contents/MacOS"
cat > "/Applications/OpenCCHelper.app/Contents/Info.plist" << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key><string>run.sh</string>
<key>CFBundleIdentifier</key><string>com.lovstudio.OpenCCHelper</string>
<key>LSUIElement</key><true/>
</dict>
</plist>
EOF
cat > "/Applications/OpenCCHelper.app/Contents/MacOS/run.sh" << 'EOF'
#!/bin/bash
CMD=$(pbpaste)
osascript << APPLESCRIPT
tell application "iTerm"
activate
tell current window
create tab with default profile
tell current session
write text "$CMD"
end tell
end tell
end tell
APPLESCRIPT
EOF
chmod +x "/Applications/OpenCCHelper.app/Contents/MacOS/run.sh"Extension 中调用:
let command = "cd '\(targetPath)' && claude"
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(command, forType: .string)
NSWorkspace.shared.open(URL(fileURLWithPath: "/Applications/OpenCCHelper.app"))其他已知限制
- Bundle ID 不能含下划线:使用连字符或驼峰命名(
OpenCC而非open_cc) - NSHomeDirectory() 返回容器路径:沙盒中返回
~/Library/Containers/<bundle-id>/Data/,监控目录需硬编码真实路径 - NSMenuItem 必须设置 target:
item.target = self,否则 action 不会触发 - Finder Extension 菜单项位置由系统决定,无法排在「新建文件夹」之前
- Quick Action 环境没有
$PATH,工具路径必须用绝对路径 - Automator workflow 需在 Automator 中打开保存才能注册
- Extension 使用 ad-hoc 签名,仅限本机使用
Changelog
[0.3.0] - 2026-04-15
Added
- Add helper app pattern to bypass sandbox restrictions
- Document NSMenuItem.target requirement
- Document NSHomeDirectory() sandbox behavior
- Document Bundle ID naming restrictions
- Update example to OpenCC with helper app
[0.2.1] - 2026-04-14
Fixed
- Fix sandbox entitlements: use temporary-exception.files.absolute-path.read-write instead of files.user-selected.read-write
- Document sandbox-must-be-on requirement in SKILL.md and known limitations
- Update xcodegen-template.yml with correct entitlements
0.2.0 — 2026-04-13
- Added Mode B: Finder Sync Extension for blank-space right-click menus (Swift + xcodegen)
- Auto mode detection based on keywords (空白处/background → Extension, otherwise → Quick Action)
- Added xcodegen template, FinderSync.swift template, AppDelegate template
- Full build-install-register pipeline (xcodegen → xcodebuild → pluginkit)
- Documented known limitation: menu item position controlled by system
0.1.0 — 2025-01-01
- Initial release: Automator Quick Action mode for file/folder context menus
lovstudio:finder-action
Generate Mac Finder right-click menu actions. Automator Quick Actions for file/folder menus, Finder Sync Extensions (Swift) for blank-space menus. Auto-detects which mode to use.
Part of lovstudio/skills — by lovstudio.ai
Install
npx skills add lovstudio/skills --skill lovstudio:finder-actionRequires: macOS 14+, Xcode (for Mode B), brew install xcodegen (for Mode B)
Usage
/lovstudio:finder-action pdf2png .pdf 将PDF转PNG
/lovstudio:finder-action 新建md文件 空白处右键创建markdownModes
| Trigger | Mode | Tech |
|---|---|---|
| Right-click file/folder | Quick Action | Automator workflow |
| Right-click blank space | Finder Extension | Swift + xcodegen |
// AppleScript snippet for opening iTerm2 at a specific path
// Use inside FinderSync.swift menuAction
func openInITerm(at path: String) {
let escapedPath = path.replacingOccurrences(of: "'", with: "'\\''")
let script = """
tell application "iTerm"
activate
tell current window
create tab with default profile
tell current session
write text "cd '\(escapedPath)'"
end tell
end tell
end tell
"""
var error: NSDictionary?
if let appleScript = NSAppleScript(source: script) {
appleScript.executeAndReturnError(&error)
if let error = error {
NSLog("AppleScript error: \(error)")
}
}
}
// For Terminal.app instead of iTerm:
func openInTerminal(at path: String) {
let escapedPath = path.replacingOccurrences(of: "'", with: "'\\''")
let script = """
tell application "Terminal"
activate
do script "cd '\(escapedPath)'"
end tell
"""
var error: NSDictionary?
if let appleScript = NSAppleScript(source: script) {
appleScript.executeAndReturnError(&error)
}
}
import Cocoa
import FinderSync
class FinderSync: FIFinderSync {
override init() {
super.init()
FIFinderSyncController.default().directoryURLs = [URL(fileURLWithPath: "/")]
}
override func menu(for menuKind: FIMenuKind) -> NSMenu? {
// .contextualMenuForContainer = blank-space right-click
// .contextualMenuForItems = file/folder right-click
// Support both modes for maximum flexibility
guard menuKind == .contextualMenuForContainer || menuKind == .contextualMenuForItems else {
return nil
}
let menu = NSMenu(title: "")
let item = NSMenuItem(
title: "MENU_TITLE",
action: #selector(menuAction(_:)),
keyEquivalent: ""
)
item.image = NSImage(systemSymbolName: "SF_SYMBOL_NAME", accessibilityDescription: nil)
menu.addItem(item)
return menu
}
@objc func menuAction(_ sender: AnyObject?) {
var targetPath: String
// Priority: selected folder > current directory
if let selectedItems = FIFinderSyncController.default().selectedItemURLs(),
let firstItem = selectedItems.first {
var isDir: ObjCBool = false
if FileManager.default.fileExists(atPath: firstItem.path, isDirectory: &isDir), isDir.boolValue {
targetPath = firstItem.path
} else {
targetPath = firstItem.deletingLastPathComponent().path
}
} else if let target = FIFinderSyncController.default().targetedURL() {
targetPath = target.path
} else {
return
}
// ACTION_IMPLEMENTATION
// Example: Open terminal at targetPath
// Example: Create file at targetPath
// Example: Run AppleScript
}
}
name: APP_NAME
options:
bundleIdPrefix: com.lovstudio
deploymentTarget:
macOS: "14.0"
targets:
APP_NAME:
type: application
platform: macOS
sources:
- APP_NAME
info:
path: APP_NAME/Info.plist
properties:
LSUIElement: true
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: BUNDLE_ID
PRODUCT_NAME: APP_NAME
MARKETING_VERSION: "0.1.0"
CURRENT_PROJECT_VERSION: 1
CODE_SIGN_IDENTITY: "-"
CODE_SIGN_STYLE: Manual
ENABLE_HARDENED_RUNTIME: true
MACOSX_DEPLOYMENT_TARGET: "14.0"
entitlements:
path: APP_NAME/APP_NAME.entitlements
properties:
com.apple.security.app-sandbox: true
dependencies:
- target: FinderExtension
embed: true
FinderExtension:
type: app-extension
platform: macOS
sources:
- FinderExtension
info:
path: FinderExtension/Info.plist
properties:
NSExtension:
NSExtensionPointIdentifier: com.apple.FinderSync
NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).FinderSync
NSExtensionAttributes: {}
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: BUNDLE_ID.FinderExtension
PRODUCT_NAME: FinderExtension
MARKETING_VERSION: "0.1.0"
CURRENT_PROJECT_VERSION: 1
CODE_SIGN_IDENTITY: "-"
CODE_SIGN_STYLE: Manual
ENABLE_HARDENED_RUNTIME: true
MACOSX_DEPLOYMENT_TARGET: "14.0"
LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"
entitlements:
path: FinderExtension/FinderExtension.entitlements
properties:
com.apple.security.app-sandbox: true
# File system access for creating files
com.apple.security.temporary-exception.files.absolute-path.read-write:
- /
# AppleScript automation (for launching terminals, etc.)
com.apple.security.automation.apple-events: true