Update capture-screen skill docs and versioning
This commit is contained in:
@@ -4,9 +4,11 @@
|
||||
// Enumerate on-screen windows and print their Window IDs.
|
||||
//
|
||||
// Usage:
|
||||
// swift get_window_id.swift # List all windows
|
||||
// swift get_window_id.swift Excel # Filter by keyword
|
||||
// swift get_window_id.swift "Chrome" # Filter by app name
|
||||
// swift scripts/get_window_id.swift # List all windows
|
||||
// swift scripts/get_window_id.swift Excel # Filter by keyword
|
||||
// swift scripts/get_window_id.swift "Chrome" # Filter by app name
|
||||
// swift scripts/get_window_id.swift --permission-hint screen # Print Screen Recording triage
|
||||
// swift scripts/get_window_id.swift --permission-hint microphone # Print Microphone triage
|
||||
//
|
||||
// Output format:
|
||||
// WID=12345 | App=Microsoft Excel | Title=workbook.xlsx
|
||||
@@ -15,10 +17,192 @@
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
let keyword = CommandLine.arguments.count > 1
|
||||
? CommandLine.arguments[1]
|
||||
: ""
|
||||
enum PermissionKind: String {
|
||||
case screen
|
||||
case microphone
|
||||
}
|
||||
|
||||
let invocationPath = (CommandLine.arguments.first ?? "")
|
||||
let invokerName = URL(fileURLWithPath: invocationPath).lastPathComponent
|
||||
let runtimeProcessName = ProcessInfo.processInfo.processName
|
||||
let invokerIsBundle = invocationPath.hasSuffix(".app") || invocationPath.contains(".app/")
|
||||
let scriptPath: String? = invocationPath.hasSuffix(".swift") ? invocationPath : nil
|
||||
let helperBinaries: Set<String> = [
|
||||
"swift",
|
||||
"swift-frontend",
|
||||
"python",
|
||||
"python3",
|
||||
"node",
|
||||
"uv",
|
||||
"npm",
|
||||
"bun",
|
||||
"pnpm",
|
||||
"yarn",
|
||||
"bash",
|
||||
"zsh",
|
||||
"sh",
|
||||
"osascript",
|
||||
"Terminal",
|
||||
"iTerm2",
|
||||
"iTerm"
|
||||
]
|
||||
|
||||
let invokerCandidates: [String] = {
|
||||
var candidates = [String]()
|
||||
var seen = Set<String>()
|
||||
func append(_ value: String) {
|
||||
guard !value.isEmpty, !seen.contains(value) else { return }
|
||||
seen.insert(value)
|
||||
candidates.append(value)
|
||||
}
|
||||
if let scriptPath = scriptPath {
|
||||
append(scriptPath)
|
||||
}
|
||||
if !invokerName.isEmpty {
|
||||
append(invokerName)
|
||||
}
|
||||
if !runtimeProcessName.isEmpty && runtimeProcessName != invokerName {
|
||||
append(runtimeProcessName)
|
||||
}
|
||||
return candidates
|
||||
}()
|
||||
|
||||
let args = Array(CommandLine.arguments.dropFirst())
|
||||
|
||||
var permissionHintTarget: PermissionKind?
|
||||
var keyword = ""
|
||||
var expectPermissionTarget = false
|
||||
|
||||
func printUsage() {
|
||||
fputs("Usage:\n", stderr)
|
||||
fputs(" swift scripts/get_window_id.swift [keyword]\n", stderr)
|
||||
fputs(" swift scripts/get_window_id.swift --permission-hint [screen|microphone]\n", stderr)
|
||||
fputs("\n", stderr)
|
||||
fputs("Options:\n", stderr)
|
||||
fputs(" --permission-hint [screen|microphone] Print permission triage instructions\n", stderr)
|
||||
fputs(" -h, --help Show this help\n", stderr)
|
||||
fputs("\n", stderr)
|
||||
fputs("Examples:\n", stderr)
|
||||
fputs(" swift scripts/get_window_id.swift Excel\n", stderr)
|
||||
fputs(" swift scripts/get_window_id.swift --permission-hint screen\n", stderr)
|
||||
fputs(" swift scripts/get_window_id.swift --permission-hint microphone\n", stderr)
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
if expectPermissionTarget {
|
||||
if let kind = PermissionKind(rawValue: arg.lowercased()) {
|
||||
permissionHintTarget = kind
|
||||
expectPermissionTarget = false
|
||||
continue
|
||||
}
|
||||
fputs("Unknown permission target: \(arg)\n", stderr)
|
||||
printUsage()
|
||||
exit(2)
|
||||
}
|
||||
|
||||
if arg == "-h" || arg == "--help" {
|
||||
printUsage()
|
||||
exit(0)
|
||||
} else if arg == "--permission-hint" {
|
||||
permissionHintTarget = .screen
|
||||
expectPermissionTarget = true
|
||||
} else if arg.hasPrefix("--permission-hint=") {
|
||||
let target = String(arg.dropFirst("--permission-hint=".count)).lowercased()
|
||||
guard let kind = PermissionKind(rawValue: target) else {
|
||||
fputs("Unknown permission target: \(target)\n", stderr)
|
||||
printUsage()
|
||||
exit(2)
|
||||
}
|
||||
permissionHintTarget = kind
|
||||
} else if arg == "--permission-hint-screen" {
|
||||
permissionHintTarget = .screen
|
||||
} else if arg == "--permission-hint-microphone" || arg == "--permission-hint-mic" {
|
||||
permissionHintTarget = .microphone
|
||||
} else if arg.hasPrefix("-") {
|
||||
fputs("Unknown option: \(arg)\n", stderr)
|
||||
printUsage()
|
||||
exit(2)
|
||||
} else if keyword.isEmpty {
|
||||
keyword = arg
|
||||
}
|
||||
}
|
||||
|
||||
if expectPermissionTarget {
|
||||
fputs("Missing permission hint target after --permission-hint.\n", stderr)
|
||||
printUsage()
|
||||
exit(2)
|
||||
}
|
||||
|
||||
if let kind = permissionHintTarget {
|
||||
switch kind {
|
||||
case .screen:
|
||||
fputs("Screen Recording permission required.\n", stderr)
|
||||
printCommonPermissionHint(
|
||||
pane: "Privacy_ScreenCapture",
|
||||
label: "Screen Recording"
|
||||
)
|
||||
printPermissionContextHint()
|
||||
case .microphone:
|
||||
fputs("Microphone permission required.\n", stderr)
|
||||
printCommonPermissionHint(
|
||||
pane: "Privacy_Microphone",
|
||||
label: "Microphone"
|
||||
)
|
||||
printPermissionContextHint()
|
||||
}
|
||||
exit(0)
|
||||
}
|
||||
|
||||
func printCommonPermissionHint(pane: String, label: String, missing: Bool = true) {
|
||||
let openCommand = "x-apple.systempreferences:com.apple.preference.security?\(pane)"
|
||||
fputs("Troubleshooting:\n", stderr)
|
||||
fputs(" - Open Settings: `open \"\(openCommand)\"`\n", stderr)
|
||||
fputs(" - In Privacy & Security → \(label), enable the target application.\n", stderr)
|
||||
if missing {
|
||||
fputs(" - If the target app is not in the list:\n", stderr)
|
||||
fputs(" - Granting happens by real .app bundle, not by helper/terminal scripts.\n", stderr)
|
||||
fputs(" - For CLI workflows, grant to the host app you launch from (Terminal, iTerm, iTerm2, Swift, etc.) if no dedicated .app exists yet.\n", stderr)
|
||||
fputs(" - Click `+` and add the actual `.app` from `/Applications`.\n", stderr)
|
||||
}
|
||||
fputs(" - If permission status does not refresh, quit/reopen terminal/app and retry.\n", stderr)
|
||||
if helperBinaries.contains(invokerName) || helperBinaries.contains(runtimeProcessName) {
|
||||
fputs(" - Current launcher is a helper/runtime process (`\(runtimeProcessName)`) -> OS may show this entry instead of the tool name.\n", stderr)
|
||||
} else if invokerIsBundle {
|
||||
fputs(" - The launcher path looks like a bundled app, which is the preferred state for permissions.\n", stderr)
|
||||
}
|
||||
if let scriptPath = scriptPath {
|
||||
fputs(" - Script entry: `\(scriptPath)`\n", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func printPermissionContextHint() {
|
||||
fputs(" - Invoker path: `\(invocationPath)`\n", stderr)
|
||||
fputs(" - Runtime process: `\(runtimeProcessName)`\n", stderr)
|
||||
if !invokerCandidates.isEmpty {
|
||||
fputs(" - Candidate identities in System Settings:\n", stderr)
|
||||
for identity in invokerCandidates {
|
||||
fputs(" - \(identity)\n", stderr)
|
||||
}
|
||||
}
|
||||
if invokerIsBundle {
|
||||
fputs(" This looks like a bundled app path, so the setting should match the app identity.\n", stderr)
|
||||
} else {
|
||||
fputs(" If this is not your final app binary, permissions can be inconsistent.\n", stderr)
|
||||
}
|
||||
fputs(" - Recommended for production: keep permission requests inside your signed `.app` process.\n", stderr)
|
||||
if let scriptPath = scriptPath {
|
||||
fputs(" - Script entry currently used: `\(scriptPath)`.\n", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func printScreenRecordingPermissionHint() {
|
||||
fputs("Screen Recording permission required.\n", stderr)
|
||||
fputs("Troubleshooting:\n", stderr)
|
||||
printCommonPermissionHint(pane: "Privacy_ScreenCapture", label: "Screen Recording")
|
||||
printPermissionContextHint()
|
||||
}
|
||||
|
||||
guard let windowList = CGWindowListCopyWindowInfo(
|
||||
.optionOnScreenOnly, kCGNullWindowID
|
||||
@@ -27,6 +211,7 @@ guard let windowList = CGWindowListCopyWindowInfo(
|
||||
fputs("Possible causes:\n", stderr)
|
||||
fputs(" - No applications with visible windows are running\n", stderr)
|
||||
fputs(" - Screen Recording permission not granted (System Settings → Privacy & Security → Screen Recording)\n", stderr)
|
||||
printScreenRecordingPermissionHint()
|
||||
exit(1)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user