opencodekit 0.20.8 → 0.21.1

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.
Files changed (48) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.opencode/AGENTS.md +25 -1
  3. package/dist/template/.opencode/memory.db +0 -0
  4. package/dist/template/.opencode/memory.db-shm +0 -0
  5. package/dist/template/.opencode/memory.db-wal +0 -0
  6. package/dist/template/.opencode/opencode.json +83 -609
  7. package/dist/template/.opencode/opencodex-fast.jsonc +1 -1
  8. package/dist/template/.opencode/package.json +2 -2
  9. package/dist/template/.opencode/plugin/copilot-auth.ts +86 -12
  10. package/dist/template/.opencode/plugin/prompt-leverage.ts +191 -0
  11. package/dist/template/.opencode/plugin/sdk/copilot/copilot-provider.ts +14 -2
  12. package/dist/template/.opencode/plugin/sdk/copilot/index.ts +2 -2
  13. package/dist/template/.opencode/plugin/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  14. package/dist/template/.opencode/plugin/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  15. package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-config.ts +18 -0
  16. package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-error.ts +22 -0
  17. package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  18. package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-responses-language-model.ts +1770 -0
  19. package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  20. package/dist/template/.opencode/plugin/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  21. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  22. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/file-search.ts +127 -0
  23. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/image-generation.ts +114 -0
  24. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/local-shell.ts +64 -0
  25. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  26. package/dist/template/.opencode/plugin/sdk/copilot/responses/tool/web-search.ts +102 -0
  27. package/dist/template/.opencode/skill/gh-address-comments/SKILL.md +29 -0
  28. package/dist/template/.opencode/skill/gh-address-comments/scripts/fetch_comments.py +237 -0
  29. package/dist/template/.opencode/skill/gh-fix-ci/SKILL.md +38 -0
  30. package/dist/template/.opencode/skill/gh-fix-ci/scripts/inspect_pr_checks.py +509 -0
  31. package/dist/template/.opencode/skill/prompt-leverage/SKILL.md +90 -0
  32. package/dist/template/.opencode/skill/prompt-leverage/references/framework.md +91 -0
  33. package/dist/template/.opencode/skill/prompt-leverage/scripts/augment_prompt.py +157 -0
  34. package/dist/template/.opencode/skill/screenshot/SKILL.md +48 -0
  35. package/dist/template/.opencode/skill/screenshot/scripts/ensure_macos_permissions.sh +54 -0
  36. package/dist/template/.opencode/skill/screenshot/scripts/macos_display_info.swift +22 -0
  37. package/dist/template/.opencode/skill/screenshot/scripts/macos_permissions.swift +40 -0
  38. package/dist/template/.opencode/skill/screenshot/scripts/macos_window_info.swift +126 -0
  39. package/dist/template/.opencode/skill/screenshot/scripts/take_screenshot.ps1 +163 -0
  40. package/dist/template/.opencode/skill/screenshot/scripts/take_screenshot.py +585 -0
  41. package/dist/template/.opencode/skill/security-threat-model/SKILL.md +36 -0
  42. package/dist/template/.opencode/skill/security-threat-model/references/prompt-template.md +255 -0
  43. package/dist/template/.opencode/skill/security-threat-model/references/security-controls-and-assets.md +32 -0
  44. package/dist/template/.opencode/skill/skill-installer/SKILL.md +58 -0
  45. package/dist/template/.opencode/skill/skill-installer/scripts/github_utils.py +21 -0
  46. package/dist/template/.opencode/skill/skill-installer/scripts/install-skill-from-github.py +313 -0
  47. package/dist/template/.opencode/skill/skill-installer/scripts/list-skills.py +106 -0
  48. package/package.json +1 -1
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env python3
2
+ """Automated prompt upgrader using the Prompt Leverage Framework."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import re
8
+ from textwrap import dedent
9
+
10
+
11
+ TASK_KEYWORDS = {
12
+ "coding": [
13
+ "code",
14
+ "bug",
15
+ "repo",
16
+ "refactor",
17
+ "test",
18
+ "implement",
19
+ "fix",
20
+ "function",
21
+ "api",
22
+ ],
23
+ "research": [
24
+ "research",
25
+ "compare",
26
+ "find",
27
+ "latest",
28
+ "sources",
29
+ "analyze market",
30
+ "look up",
31
+ ],
32
+ "writing": ["write", "rewrite", "draft", "email", "memo", "blog", "copy", "tone"],
33
+ "review": ["review", "audit", "critique", "inspect", "evaluate", "assess"],
34
+ "planning": ["plan", "roadmap", "strategy", "framework", "outline"],
35
+ "analysis": ["analyze", "explain", "break down", "diagnose", "root cause"],
36
+ }
37
+
38
+
39
+ def detect_task(prompt: str) -> str:
40
+ """Detect task type by keyword matching."""
41
+ lowered = prompt.lower()
42
+ scores = {
43
+ task: sum(1 for keyword in keywords if keyword in lowered)
44
+ for task, keywords in TASK_KEYWORDS.items()
45
+ }
46
+ best_task, best_score = max(scores.items(), key=lambda item: item[1])
47
+ return best_task if best_score > 0 else "analysis"
48
+
49
+
50
+ def infer_intensity(prompt: str, task: str) -> str:
51
+ """Infer intensity level from prompt content."""
52
+ lowered = prompt.lower()
53
+ if any(
54
+ token in lowered
55
+ for token in [
56
+ "careful",
57
+ "deep",
58
+ "thorough",
59
+ "high stakes",
60
+ "production",
61
+ "critical",
62
+ ]
63
+ ):
64
+ return "Deep"
65
+ if task in {"coding", "research", "review"}:
66
+ return "Standard"
67
+ return "Light"
68
+
69
+
70
+ def build_tool_rules(task: str) -> str:
71
+ """Build task-specific tool rules."""
72
+ rules = {
73
+ "coding": "Inspect the relevant files and dependencies first. Validate the final change with the narrowest useful checks before broadening scope.",
74
+ "research": "Retrieve evidence from reliable sources before concluding. Do not guess facts that can be checked.",
75
+ "review": "Read enough surrounding context to understand intent before critiquing. Distinguish confirmed issues from plausible risks.",
76
+ }
77
+ return rules.get(
78
+ task,
79
+ "Use tools or extra context only when they materially improve correctness or completeness.",
80
+ )
81
+
82
+
83
+ def build_output_contract(task: str) -> str:
84
+ """Build task-specific output contract."""
85
+ contracts = {
86
+ "coding": "Return the result in a practical execution format: concise summary, concrete changes or code, validation notes, and any remaining risks.",
87
+ "research": "Return a structured synthesis with key findings, supporting evidence, uncertainty where relevant, and a concise bottom line.",
88
+ "writing": "Return polished final copy in the requested tone and format. If useful, include a short rationale for major editorial choices.",
89
+ "review": "Return findings grouped by severity or importance, explain why each matters, and suggest the smallest credible next step.",
90
+ }
91
+ return contracts.get(
92
+ task,
93
+ "Return a clear, well-structured response matched to the task, with no unnecessary verbosity.",
94
+ )
95
+
96
+
97
+ def upgrade_prompt(raw_prompt: str, task: str | None) -> str:
98
+ """Upgrade a raw prompt using the framework."""
99
+ normalized = re.sub(r"\s+", " ", raw_prompt).strip()
100
+ detected_task = task or detect_task(normalized)
101
+ intensity = infer_intensity(normalized, detected_task)
102
+ tool_rules = build_tool_rules(detected_task)
103
+ output_contract = build_output_contract(detected_task)
104
+
105
+ return dedent(
106
+ f"""
107
+ Objective:
108
+ - Complete this task: {normalized}
109
+ - Optimize for a correct, useful result rather than a merely plausible one.
110
+
111
+ Context:
112
+ - Preserve the user's original intent and constraints.
113
+ - Surface any key assumptions if required information is missing.
114
+
115
+ Work Style:
116
+ - Task type: {detected_task}
117
+ - Effort level: {intensity}
118
+ - Understand the problem broadly enough to avoid narrow mistakes, then go deep where the risk or complexity is highest.
119
+ - Use first-principles reasoning before proposing changes.
120
+ - For non-trivial work, review the result once with fresh eyes before finalizing.
121
+
122
+ Tool Rules:
123
+ - {tool_rules}
124
+
125
+ Output Contract:
126
+ - {output_contract}
127
+
128
+ Verification:
129
+ - Check correctness, completeness, and edge cases.
130
+ - Improve obvious weaknesses if a better approach is available within scope.
131
+
132
+ Done Criteria:
133
+ - Stop only when the response satisfies the task, matches the requested format, and passes the verification step.
134
+ """
135
+ ).strip()
136
+
137
+
138
+ def parse_args() -> argparse.Namespace:
139
+ parser = argparse.ArgumentParser(
140
+ description="Upgrade a raw prompt into a framework-backed execution prompt."
141
+ )
142
+ parser.add_argument("prompt", help="Raw prompt text to upgrade.")
143
+ parser.add_argument(
144
+ "--task",
145
+ choices=sorted(TASK_KEYWORDS.keys()),
146
+ help="Optional explicit task type (auto-detected if not provided).",
147
+ )
148
+ return parser.parse_args()
149
+
150
+
151
+ def main() -> None:
152
+ args = parse_args()
153
+ print(upgrade_prompt(args.prompt, args.task))
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: screenshot
3
+ description: Use when the user explicitly asks for desktop/system screenshots or when browser/tool-specific capture is unavailable.
4
+ version: 1.0.0
5
+ tags: [debugging, ui, automation]
6
+ dependencies: []
7
+ ---
8
+
9
+ # screenshot
10
+
11
+ Capture screenshots at OS level for desktop apps, windows, regions, or full screen.
12
+
13
+ ## When to Use
14
+
15
+ - User asks for screenshot of desktop/app/window/region
16
+ - You need non-browser captures (native app, OS UI, Electron shell)
17
+ - Browser capture tools are unavailable or insufficient
18
+
19
+ ## When NOT to Use
20
+
21
+ - Browser-only capture where Playwright/DevTools is enough
22
+ - Design-file capture where Figma skills are available
23
+
24
+ ## Save Location Rules
25
+
26
+ 1. If user gives a path, save there.
27
+ 2. If user asks generally for a screenshot, use OS default screenshot location.
28
+ 3. If screenshot is for agent inspection, save to temp location.
29
+
30
+ ## Scripts
31
+
32
+ - `scripts/take_screenshot.py` (macOS/Linux)
33
+ - `scripts/take_screenshot.ps1` (Windows)
34
+ - `scripts/ensure_macos_permissions.sh` (macOS preflight)
35
+
36
+ ## Quick Start
37
+
38
+ ```bash
39
+ # macOS/Linux default capture
40
+ python3 .opencode/skill/screenshot/scripts/take_screenshot.py
41
+
42
+ # capture app window(s) on macOS to temp
43
+ bash .opencode/skill/screenshot/scripts/ensure_macos_permissions.sh && \
44
+ python3 .opencode/skill/screenshot/scripts/take_screenshot.py --app "Codex" --mode temp
45
+
46
+ # region capture
47
+ python3 .opencode/skill/screenshot/scripts/take_screenshot.py --mode temp --region 100,200,800,600
48
+ ```
@@ -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