opencodekit 0.20.7 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/template/.opencode/AGENTS.md +60 -0
- package/dist/template/.opencode/agent/build.md +3 -2
- package/dist/template/.opencode/agent/explore.md +14 -14
- package/dist/template/.opencode/agent/general.md +1 -1
- package/dist/template/.opencode/agent/plan.md +1 -1
- package/dist/template/.opencode/agent/review.md +1 -1
- package/dist/template/.opencode/agent/vision.md +0 -9
- package/dist/template/.opencode/memory.db +0 -0
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/opencode.json +83 -614
- package/dist/template/.opencode/opencodex-fast.jsonc +1 -1
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/copilot-auth.ts +27 -12
- package/dist/template/.opencode/plugin/prompt-leverage.ts +193 -0
- package/dist/template/.opencode/plugin/prompt-leverage.ts.bak +228 -0
- package/dist/template/.opencode/plugin/sdk/copilot/copilot-provider.ts +14 -2
- package/dist/template/.opencode/plugin/sdk/copilot/index.ts +2 -2
- package/dist/template/.opencode/plugin/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-config.ts +18 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-error.ts +22 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-responses-language-model.ts +1770 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-responses-settings.ts +1 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/file-search.ts +127 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/image-generation.ts +114 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/local-shell.ts +64 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
- package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search.ts +102 -0
- package/dist/template/.opencode/pnpm-lock.yaml +791 -9
- package/dist/template/.opencode/skill/api-and-interface-design/SKILL.md +162 -0
- package/dist/template/.opencode/skill/beads/SKILL.md +10 -9
- package/dist/template/.opencode/skill/beads/references/MULTI_AGENT.md +10 -10
- package/dist/template/.opencode/skill/ci-cd-and-automation/SKILL.md +202 -0
- package/dist/template/.opencode/skill/code-search-patterns/SKILL.md +253 -0
- package/dist/template/.opencode/skill/code-simplification/SKILL.md +211 -0
- package/dist/template/.opencode/skill/condition-based-waiting/SKILL.md +12 -0
- package/dist/template/.opencode/skill/defense-in-depth/SKILL.md +16 -6
- package/dist/template/.opencode/skill/deprecation-and-migration/SKILL.md +189 -0
- package/dist/template/.opencode/skill/development-lifecycle/SKILL.md +12 -48
- package/dist/template/.opencode/skill/documentation-and-adrs/SKILL.md +220 -0
- package/dist/template/.opencode/skill/gh-address-comments/SKILL.md +29 -0
- package/dist/template/.opencode/skill/gh-address-comments/scripts/fetch_comments.py +237 -0
- package/dist/template/.opencode/skill/gh-fix-ci/SKILL.md +38 -0
- package/dist/template/.opencode/skill/gh-fix-ci/scripts/inspect_pr_checks.py +509 -0
- package/dist/template/.opencode/skill/incremental-implementation/SKILL.md +191 -0
- package/dist/template/.opencode/skill/performance-optimization/SKILL.md +236 -0
- package/dist/template/.opencode/skill/prompt-leverage/SKILL.md +90 -0
- package/dist/template/.opencode/skill/prompt-leverage/references/framework.md +91 -0
- package/dist/template/.opencode/skill/prompt-leverage/scripts/augment_prompt.py +157 -0
- package/dist/template/.opencode/skill/receiving-code-review/SKILL.md +11 -0
- package/dist/template/.opencode/skill/screenshot/SKILL.md +48 -0
- package/dist/template/.opencode/skill/screenshot/scripts/ensure_macos_permissions.sh +54 -0
- package/dist/template/.opencode/skill/screenshot/scripts/macos_display_info.swift +22 -0
- package/dist/template/.opencode/skill/screenshot/scripts/macos_permissions.swift +40 -0
- package/dist/template/.opencode/skill/screenshot/scripts/macos_window_info.swift +126 -0
- package/dist/template/.opencode/skill/screenshot/scripts/take_screenshot.ps1 +163 -0
- package/dist/template/.opencode/skill/screenshot/scripts/take_screenshot.py +585 -0
- package/dist/template/.opencode/skill/security-and-hardening/SKILL.md +296 -0
- package/dist/template/.opencode/skill/security-threat-model/SKILL.md +36 -0
- package/dist/template/.opencode/skill/security-threat-model/references/prompt-template.md +255 -0
- package/dist/template/.opencode/skill/security-threat-model/references/security-controls-and-assets.md +32 -0
- package/dist/template/.opencode/skill/skill-installer/SKILL.md +58 -0
- package/dist/template/.opencode/skill/skill-installer/scripts/github_utils.py +21 -0
- package/dist/template/.opencode/skill/skill-installer/scripts/install-skill-from-github.py +313 -0
- package/dist/template/.opencode/skill/skill-installer/scripts/list-skills.py +106 -0
- package/dist/template/.opencode/skill/structured-edit/SKILL.md +10 -0
- package/dist/template/.opencode/skill/swarm-coordination/SKILL.md +66 -1
- package/package.json +1 -1
- package/dist/template/.opencode/skill/beads-bridge/SKILL.md +0 -321
- package/dist/template/.opencode/skill/code-navigation/SKILL.md +0 -130
- package/dist/template/.opencode/skill/mqdh/SKILL.md +0 -171
- package/dist/template/.opencode/skill/obsidian/SKILL.md +0 -192
- package/dist/template/.opencode/skill/obsidian/mcp.json +0 -22
- package/dist/template/.opencode/skill/pencil/SKILL.md +0 -72
- package/dist/template/.opencode/skill/ralph/SKILL.md +0 -296
- package/dist/template/.opencode/skill/tilth-cli/SKILL.md +0 -207
- package/dist/template/.opencode/skill/tool-priority/SKILL.md +0 -299
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
if [[ "$(uname)" != "Darwin" ]]; then
|
|
5
|
+
echo "ensure_macos_permissions.sh only supports macOS" >&2
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
if ! command -v swift >/dev/null 2>&1; then
|
|
10
|
+
echo "swift is required to check macOS screen capture permissions" >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
PERM_SWIFT="$SCRIPT_DIR/macos_permissions.swift"
|
|
16
|
+
MODULE_CACHE="${TMPDIR:-/tmp}/codex-swift-module-cache"
|
|
17
|
+
mkdir -p "$MODULE_CACHE"
|
|
18
|
+
|
|
19
|
+
screen_capture_status() {
|
|
20
|
+
local json
|
|
21
|
+
json="$(swift -module-cache-path "$MODULE_CACHE" "$PERM_SWIFT" "$@")"
|
|
22
|
+
python3 -c 'import json, sys; data=json.loads(sys.argv[1]); print("1" if data.get("screenCapture") else "0")' "$json"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if [[ -n "${CODEX_SANDBOX:-}" ]]; then
|
|
26
|
+
echo "Screen capture checks are blocked in the sandbox; rerun with escalated permissions." >&2
|
|
27
|
+
exit 3
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if [[ "$(screen_capture_status)" == "1" ]]; then
|
|
31
|
+
echo "Screen Recording permission already granted."
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
cat <<'MSG'
|
|
36
|
+
This workflow needs macOS Screen Recording permission to capture screenshots.
|
|
37
|
+
macOS will show a single system prompt for Screen Recording. Approve it, then
|
|
38
|
+
return here. If macOS opens System Settings instead of prompting, enable Screen
|
|
39
|
+
Recording for your terminal and rerun the command.
|
|
40
|
+
MSG
|
|
41
|
+
|
|
42
|
+
# Request permission once after explaining why it is needed.
|
|
43
|
+
screen_capture_status --request >/dev/null || true
|
|
44
|
+
|
|
45
|
+
if [[ "$(screen_capture_status)" != "1" ]]; then
|
|
46
|
+
cat <<'MSG'
|
|
47
|
+
Screen Recording is still not granted.
|
|
48
|
+
Open System Settings > Privacy & Security > Screen Recording and enable it for
|
|
49
|
+
your terminal (and Codex if needed), then rerun your screenshot command.
|
|
50
|
+
MSG
|
|
51
|
+
exit 2
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
echo "Screen Recording permission granted."
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
struct Response: Encodable {
|
|
5
|
+
let count: Int
|
|
6
|
+
let displays: [Int]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let count = max(NSScreen.screens.count, 1)
|
|
10
|
+
let displays = Array(1...count)
|
|
11
|
+
|
|
12
|
+
let response = Response(count: count, displays: displays)
|
|
13
|
+
let encoder = JSONEncoder()
|
|
14
|
+
encoder.outputFormatting = [.sortedKeys]
|
|
15
|
+
|
|
16
|
+
if let data = try? encoder.encode(response),
|
|
17
|
+
let json = String(data: data, encoding: .utf8) {
|
|
18
|
+
print(json)
|
|
19
|
+
} else {
|
|
20
|
+
fputs("{\"count\":\(count)}\n", stderr)
|
|
21
|
+
exit(1)
|
|
22
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import CoreGraphics
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
struct Status: Encodable {
|
|
5
|
+
let screenCapture: Bool
|
|
6
|
+
let requested: Bool
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let shouldRequest = CommandLine.arguments.contains("--request")
|
|
10
|
+
|
|
11
|
+
@available(macOS 10.15, *)
|
|
12
|
+
func screenCaptureGranted(request: Bool) -> Bool {
|
|
13
|
+
if CGPreflightScreenCaptureAccess() {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
if request {
|
|
17
|
+
_ = CGRequestScreenCaptureAccess()
|
|
18
|
+
return CGPreflightScreenCaptureAccess()
|
|
19
|
+
}
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let granted: Bool
|
|
24
|
+
if #available(macOS 10.15, *) {
|
|
25
|
+
granted = screenCaptureGranted(request: shouldRequest)
|
|
26
|
+
} else {
|
|
27
|
+
granted = true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let status = Status(screenCapture: granted, requested: shouldRequest)
|
|
31
|
+
let encoder = JSONEncoder()
|
|
32
|
+
encoder.outputFormatting = [.sortedKeys]
|
|
33
|
+
|
|
34
|
+
if let data = try? encoder.encode(status),
|
|
35
|
+
let json = String(data: data, encoding: .utf8) {
|
|
36
|
+
print(json)
|
|
37
|
+
} else {
|
|
38
|
+
fputs("{\"requested\":\(shouldRequest),\"screenCapture\":\(granted)}\n", stderr)
|
|
39
|
+
exit(1)
|
|
40
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import CoreGraphics
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
struct Bounds: Encodable {
|
|
6
|
+
let x: Int
|
|
7
|
+
let y: Int
|
|
8
|
+
let width: Int
|
|
9
|
+
let height: Int
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
struct WindowInfo: Encodable {
|
|
13
|
+
let id: Int
|
|
14
|
+
let owner: String
|
|
15
|
+
let name: String
|
|
16
|
+
let layer: Int
|
|
17
|
+
let bounds: Bounds
|
|
18
|
+
let area: Int
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
struct Response: Encodable {
|
|
22
|
+
let count: Int
|
|
23
|
+
let selected: WindowInfo?
|
|
24
|
+
let windows: [WindowInfo]?
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func value(for flag: String) -> String? {
|
|
28
|
+
guard let idx = CommandLine.arguments.firstIndex(of: flag) else {
|
|
29
|
+
return nil
|
|
30
|
+
}
|
|
31
|
+
let next = CommandLine.arguments.index(after: idx)
|
|
32
|
+
guard next < CommandLine.arguments.endIndex else {
|
|
33
|
+
return nil
|
|
34
|
+
}
|
|
35
|
+
return CommandLine.arguments[next]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let frontmostFlag = CommandLine.arguments.contains("--frontmost")
|
|
39
|
+
let explicitApp = value(for: "--app")
|
|
40
|
+
let frontmostName = frontmostFlag ? NSWorkspace.shared.frontmostApplication?.localizedName : nil
|
|
41
|
+
if frontmostFlag && frontmostName == nil {
|
|
42
|
+
fputs("{\"count\":0}\n", stderr)
|
|
43
|
+
exit(1)
|
|
44
|
+
}
|
|
45
|
+
let appFilter = (explicitApp ?? frontmostName)?.lowercased()
|
|
46
|
+
let nameFilter = value(for: "--window-name")?.lowercased()
|
|
47
|
+
let includeList = CommandLine.arguments.contains("--list")
|
|
48
|
+
|
|
49
|
+
let options: CGWindowListOption = [.optionOnScreenOnly, .excludeDesktopElements]
|
|
50
|
+
guard let raw = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] else {
|
|
51
|
+
fputs("{\"count\":0}\n", stderr)
|
|
52
|
+
exit(1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var exactMatches: [WindowInfo] = []
|
|
56
|
+
var partialMatches: [WindowInfo] = []
|
|
57
|
+
exactMatches.reserveCapacity(raw.count)
|
|
58
|
+
partialMatches.reserveCapacity(raw.count)
|
|
59
|
+
|
|
60
|
+
for entry in raw {
|
|
61
|
+
guard let owner = entry[kCGWindowOwnerName as String] as? String else { continue }
|
|
62
|
+
let ownerLower = owner.lowercased()
|
|
63
|
+
if let appFilter, !ownerLower.contains(appFilter) { continue }
|
|
64
|
+
|
|
65
|
+
let name = (entry[kCGWindowName as String] as? String) ?? ""
|
|
66
|
+
if let nameFilter, !name.lowercased().contains(nameFilter) { continue }
|
|
67
|
+
|
|
68
|
+
guard let number = entry[kCGWindowNumber as String] as? Int else { continue }
|
|
69
|
+
let layer = (entry[kCGWindowLayer as String] as? Int) ?? 0
|
|
70
|
+
|
|
71
|
+
guard let boundsDict = entry[kCGWindowBounds as String] as? [String: Any] else { continue }
|
|
72
|
+
let x = Int((boundsDict["X"] as? Double) ?? 0)
|
|
73
|
+
let y = Int((boundsDict["Y"] as? Double) ?? 0)
|
|
74
|
+
let width = Int((boundsDict["Width"] as? Double) ?? 0)
|
|
75
|
+
let height = Int((boundsDict["Height"] as? Double) ?? 0)
|
|
76
|
+
if width <= 0 || height <= 0 { continue }
|
|
77
|
+
|
|
78
|
+
let bounds = Bounds(x: x, y: y, width: width, height: height)
|
|
79
|
+
let area = width * height
|
|
80
|
+
let info = WindowInfo(id: number, owner: owner, name: name, layer: layer, bounds: bounds, area: area)
|
|
81
|
+
if let appFilter, ownerLower == appFilter {
|
|
82
|
+
exactMatches.append(info)
|
|
83
|
+
} else {
|
|
84
|
+
partialMatches.append(info)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let windows: [WindowInfo]
|
|
89
|
+
if appFilter != nil && !exactMatches.isEmpty {
|
|
90
|
+
windows = exactMatches
|
|
91
|
+
} else {
|
|
92
|
+
windows = partialMatches
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func rank(_ window: WindowInfo) -> (Int, Int) {
|
|
96
|
+
// Prefer normal-layer windows, then larger area.
|
|
97
|
+
let layerScore = window.layer == 0 ? 0 : 1
|
|
98
|
+
return (layerScore, -window.area)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let ordered: [WindowInfo]
|
|
102
|
+
if frontmostFlag {
|
|
103
|
+
ordered = windows
|
|
104
|
+
} else {
|
|
105
|
+
ordered = windows.sorted { rank($0) < rank($1) }
|
|
106
|
+
}
|
|
107
|
+
let selected = ordered.first
|
|
108
|
+
|
|
109
|
+
let list: [WindowInfo]?
|
|
110
|
+
if includeList {
|
|
111
|
+
list = ordered
|
|
112
|
+
} else {
|
|
113
|
+
list = nil
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let response = Response(count: windows.count, selected: selected, windows: list)
|
|
117
|
+
let encoder = JSONEncoder()
|
|
118
|
+
encoder.outputFormatting = [.sortedKeys]
|
|
119
|
+
|
|
120
|
+
if let data = try? encoder.encode(response),
|
|
121
|
+
let json = String(data: data, encoding: .utf8) {
|
|
122
|
+
print(json)
|
|
123
|
+
} else {
|
|
124
|
+
fputs("{\"count\":\(windows.count)}\n", stderr)
|
|
125
|
+
exit(1)
|
|
126
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$Path,
|
|
3
|
+
[ValidateSet("default", "temp")][string]$Mode = "default",
|
|
4
|
+
[string]$Format = "png",
|
|
5
|
+
[string]$Region,
|
|
6
|
+
[switch]$ActiveWindow,
|
|
7
|
+
[int]$WindowHandle
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
Set-StrictMode -Version Latest
|
|
11
|
+
$ErrorActionPreference = "Stop"
|
|
12
|
+
|
|
13
|
+
function Get-Timestamp {
|
|
14
|
+
Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function Get-DefaultDirectory {
|
|
18
|
+
$home = [Environment]::GetFolderPath("UserProfile")
|
|
19
|
+
$pictures = Join-Path $home "Pictures"
|
|
20
|
+
$screenshots = Join-Path $pictures "Screenshots"
|
|
21
|
+
if (Test-Path $screenshots) { return $screenshots }
|
|
22
|
+
if (Test-Path $pictures) { return $pictures }
|
|
23
|
+
return $home
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function New-DefaultFilename {
|
|
27
|
+
param([string]$Prefix)
|
|
28
|
+
if (-not $Prefix) { $Prefix = "screenshot" }
|
|
29
|
+
"$Prefix-$(Get-Timestamp).$Format"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function Resolve-OutputPath {
|
|
33
|
+
if ($Path) {
|
|
34
|
+
$expanded = [Environment]::ExpandEnvironmentVariables($Path)
|
|
35
|
+
$homeDir = [Environment]::GetFolderPath("UserProfile")
|
|
36
|
+
if ($expanded -eq "~") {
|
|
37
|
+
$expanded = $homeDir
|
|
38
|
+
} elseif ($expanded.StartsWith("~/") -or $expanded.StartsWith("~\\")) {
|
|
39
|
+
$expanded = Join-Path $homeDir $expanded.Substring(2)
|
|
40
|
+
}
|
|
41
|
+
$full = [System.IO.Path]::GetFullPath($expanded)
|
|
42
|
+
if ((Test-Path $full) -and (Get-Item $full).PSIsContainer) {
|
|
43
|
+
$full = Join-Path $full (New-DefaultFilename "")
|
|
44
|
+
} elseif (($expanded.EndsWith("\") -or $expanded.EndsWith("/")) -and -not (Test-Path $full)) {
|
|
45
|
+
New-Item -ItemType Directory -Path $full -Force | Out-Null
|
|
46
|
+
$full = Join-Path $full (New-DefaultFilename "")
|
|
47
|
+
} elseif ([System.IO.Path]::GetExtension($full) -eq "") {
|
|
48
|
+
$full = "$full.$Format"
|
|
49
|
+
}
|
|
50
|
+
$parent = Split-Path -Parent $full
|
|
51
|
+
if ($parent) {
|
|
52
|
+
New-Item -ItemType Directory -Path $parent -Force | Out-Null
|
|
53
|
+
}
|
|
54
|
+
return $full
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if ($Mode -eq "temp") {
|
|
58
|
+
$tmp = [System.IO.Path]::GetTempPath()
|
|
59
|
+
return Join-Path $tmp (New-DefaultFilename "codex-shot")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
$dest = Get-DefaultDirectory
|
|
63
|
+
return Join-Path $dest (New-DefaultFilename "")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Parse-Region {
|
|
67
|
+
if (-not $Region) { return $null }
|
|
68
|
+
$parts = $Region.Split(",") | ForEach-Object { $_.Trim() }
|
|
69
|
+
if ($parts.Length -ne 4) {
|
|
70
|
+
throw "Region must be x,y,w,h"
|
|
71
|
+
}
|
|
72
|
+
$values = $parts | ForEach-Object {
|
|
73
|
+
$out = 0
|
|
74
|
+
if (-not [int]::TryParse($_, [ref]$out)) {
|
|
75
|
+
throw "Region values must be integers"
|
|
76
|
+
}
|
|
77
|
+
$out
|
|
78
|
+
}
|
|
79
|
+
if ($values[2] -le 0 -or $values[3] -le 0) {
|
|
80
|
+
throw "Region width and height must be positive"
|
|
81
|
+
}
|
|
82
|
+
return $values
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if ($Region -and $ActiveWindow) {
|
|
86
|
+
throw "Choose either -Region or -ActiveWindow"
|
|
87
|
+
}
|
|
88
|
+
if ($Region -and $WindowHandle) {
|
|
89
|
+
throw "Choose either -Region or -WindowHandle"
|
|
90
|
+
}
|
|
91
|
+
if ($ActiveWindow -and $WindowHandle) {
|
|
92
|
+
throw "Choose either -ActiveWindow or -WindowHandle"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
$regionValues = Parse-Region
|
|
96
|
+
$outputPath = Resolve-OutputPath
|
|
97
|
+
|
|
98
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
99
|
+
Add-Type -AssemblyName System.Drawing
|
|
100
|
+
|
|
101
|
+
$imageFormat = switch ($Format.ToLowerInvariant()) {
|
|
102
|
+
"png" { [System.Drawing.Imaging.ImageFormat]::Png }
|
|
103
|
+
"jpg" { [System.Drawing.Imaging.ImageFormat]::Jpeg }
|
|
104
|
+
"jpeg" { [System.Drawing.Imaging.ImageFormat]::Jpeg }
|
|
105
|
+
"bmp" { [System.Drawing.Imaging.ImageFormat]::Bmp }
|
|
106
|
+
default { throw "Unsupported format: $Format" }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
Add-Type @"
|
|
110
|
+
using System;
|
|
111
|
+
using System.Runtime.InteropServices;
|
|
112
|
+
public static class NativeMethods {
|
|
113
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
114
|
+
public struct RECT {
|
|
115
|
+
public int Left;
|
|
116
|
+
public int Top;
|
|
117
|
+
public int Right;
|
|
118
|
+
public int Bottom;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
[DllImport("user32.dll")]
|
|
122
|
+
public static extern IntPtr GetForegroundWindow();
|
|
123
|
+
|
|
124
|
+
[DllImport("user32.dll")]
|
|
125
|
+
public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
|
|
126
|
+
}
|
|
127
|
+
"@
|
|
128
|
+
|
|
129
|
+
if ($regionValues) {
|
|
130
|
+
$x = $regionValues[0]
|
|
131
|
+
$y = $regionValues[1]
|
|
132
|
+
$w = $regionValues[2]
|
|
133
|
+
$h = $regionValues[3]
|
|
134
|
+
$bounds = New-Object System.Drawing.Rectangle($x, $y, $w, $h)
|
|
135
|
+
} elseif ($ActiveWindow -or $WindowHandle) {
|
|
136
|
+
$handle = if ($WindowHandle) { [IntPtr]$WindowHandle } else { [NativeMethods]::GetForegroundWindow() }
|
|
137
|
+
$rect = New-Object NativeMethods+RECT
|
|
138
|
+
if (-not [NativeMethods]::GetWindowRect($handle, [ref]$rect)) {
|
|
139
|
+
throw "Failed to get window bounds"
|
|
140
|
+
}
|
|
141
|
+
$width = $rect.Right - $rect.Left
|
|
142
|
+
$height = $rect.Bottom - $rect.Top
|
|
143
|
+
$bounds = New-Object System.Drawing.Rectangle($rect.Left, $rect.Top, $width, $height)
|
|
144
|
+
} else {
|
|
145
|
+
$vs = [System.Windows.Forms.SystemInformation]::VirtualScreen
|
|
146
|
+
$bounds = New-Object System.Drawing.Rectangle($vs.Left, $vs.Top, $vs.Width, $vs.Height)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
$bitmap = New-Object System.Drawing.Bitmap($bounds.Width, $bounds.Height)
|
|
150
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
$source = New-Object System.Drawing.Point($bounds.Left, $bounds.Top)
|
|
154
|
+
$target = [System.Drawing.Point]::Empty
|
|
155
|
+
$size = New-Object System.Drawing.Size($bounds.Width, $bounds.Height)
|
|
156
|
+
$graphics.CopyFromScreen($source, $target, $size)
|
|
157
|
+
$bitmap.Save($outputPath, $imageFormat)
|
|
158
|
+
} finally {
|
|
159
|
+
$graphics.Dispose()
|
|
160
|
+
$bitmap.Dispose()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Write-Output $outputPath
|