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.
- package/dist/index.js +1 -1
- package/dist/template/.opencode/AGENTS.md +25 -1
- 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 -609
- package/dist/template/.opencode/opencodex-fast.jsonc +1 -1
- package/dist/template/.opencode/package.json +2 -2
- package/dist/template/.opencode/plugin/copilot-auth.ts +86 -12
- package/dist/template/.opencode/plugin/prompt-leverage.ts +191 -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/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/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/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-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/package.json +1 -1
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cross-platform screenshot helper for Codex skills."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import datetime as dt
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import platform
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import tempfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
17
|
+
MAC_PERM_SCRIPT = SCRIPT_DIR / "macos_permissions.swift"
|
|
18
|
+
MAC_PERM_HELPER = SCRIPT_DIR / "ensure_macos_permissions.sh"
|
|
19
|
+
MAC_WINDOW_SCRIPT = SCRIPT_DIR / "macos_window_info.swift"
|
|
20
|
+
MAC_DISPLAY_SCRIPT = SCRIPT_DIR / "macos_display_info.swift"
|
|
21
|
+
TEST_MODE_ENV = "CODEX_SCREENSHOT_TEST_MODE"
|
|
22
|
+
TEST_PLATFORM_ENV = "CODEX_SCREENSHOT_TEST_PLATFORM"
|
|
23
|
+
TEST_WINDOWS_ENV = "CODEX_SCREENSHOT_TEST_WINDOWS"
|
|
24
|
+
TEST_DISPLAYS_ENV = "CODEX_SCREENSHOT_TEST_DISPLAYS"
|
|
25
|
+
TEST_PNG = (
|
|
26
|
+
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
|
|
27
|
+
b"\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDAT\x08\xd7c"
|
|
28
|
+
b"\xf8\xff\xff?\x00\x05\xfe\x02\xfeA\xad\x1c\x1c\x00\x00\x00\x00IEND"
|
|
29
|
+
b"\xaeB`\x82"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_region(value: str) -> tuple[int, int, int, int]:
|
|
34
|
+
parts = [p.strip() for p in value.split(",")]
|
|
35
|
+
if len(parts) != 4:
|
|
36
|
+
raise argparse.ArgumentTypeError("region must be x,y,w,h")
|
|
37
|
+
try:
|
|
38
|
+
x, y, w, h = (int(p) for p in parts)
|
|
39
|
+
except ValueError as exc:
|
|
40
|
+
raise argparse.ArgumentTypeError("region values must be integers") from exc
|
|
41
|
+
if w <= 0 or h <= 0:
|
|
42
|
+
raise argparse.ArgumentTypeError("region width and height must be positive")
|
|
43
|
+
return x, y, w, h
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_mode_enabled() -> bool:
|
|
47
|
+
value = os.environ.get(TEST_MODE_ENV, "")
|
|
48
|
+
return value.lower() in {"1", "true", "yes", "on"}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def normalize_platform(value: str) -> str:
|
|
52
|
+
lowered = value.strip().lower()
|
|
53
|
+
if lowered in {"darwin", "mac", "macos", "osx"}:
|
|
54
|
+
return "Darwin"
|
|
55
|
+
if lowered in {"linux", "ubuntu"}:
|
|
56
|
+
return "Linux"
|
|
57
|
+
if lowered in {"windows", "win"}:
|
|
58
|
+
return "Windows"
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_platform_override() -> str | None:
|
|
63
|
+
value = os.environ.get(TEST_PLATFORM_ENV)
|
|
64
|
+
if value:
|
|
65
|
+
return normalize_platform(value)
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_int_list(value: str) -> list[int]:
|
|
70
|
+
results: list[int] = []
|
|
71
|
+
for part in value.split(","):
|
|
72
|
+
part = part.strip()
|
|
73
|
+
if not part:
|
|
74
|
+
continue
|
|
75
|
+
try:
|
|
76
|
+
results.append(int(part))
|
|
77
|
+
except ValueError:
|
|
78
|
+
continue
|
|
79
|
+
return results
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_window_ids() -> list[int]:
|
|
83
|
+
value = os.environ.get(TEST_WINDOWS_ENV, "101,102")
|
|
84
|
+
ids = parse_int_list(value)
|
|
85
|
+
return ids or [101]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_display_ids() -> list[int]:
|
|
89
|
+
value = os.environ.get(TEST_DISPLAYS_ENV, "1,2")
|
|
90
|
+
ids = parse_int_list(value)
|
|
91
|
+
return ids or [1]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def write_test_png(path: Path) -> None:
|
|
95
|
+
ensure_parent(path)
|
|
96
|
+
path.write_bytes(TEST_PNG)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def timestamp() -> str:
|
|
100
|
+
return dt.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def default_filename(fmt: str, prefix: str = "screenshot") -> str:
|
|
104
|
+
return f"{prefix}-{timestamp()}.{fmt}"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def mac_default_dir() -> Path:
|
|
108
|
+
desktop = Path.home() / "Desktop"
|
|
109
|
+
try:
|
|
110
|
+
proc = subprocess.run(
|
|
111
|
+
["defaults", "read", "com.apple.screencapture", "location"],
|
|
112
|
+
check=False,
|
|
113
|
+
capture_output=True,
|
|
114
|
+
text=True,
|
|
115
|
+
)
|
|
116
|
+
location = proc.stdout.strip()
|
|
117
|
+
if location:
|
|
118
|
+
return Path(location).expanduser()
|
|
119
|
+
except OSError:
|
|
120
|
+
pass
|
|
121
|
+
return desktop
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def default_dir(system: str) -> Path:
|
|
125
|
+
home = Path.home()
|
|
126
|
+
if system == "Darwin":
|
|
127
|
+
return mac_default_dir()
|
|
128
|
+
if system == "Windows":
|
|
129
|
+
pictures = home / "Pictures"
|
|
130
|
+
screenshots = pictures / "Screenshots"
|
|
131
|
+
if screenshots.exists():
|
|
132
|
+
return screenshots
|
|
133
|
+
if pictures.exists():
|
|
134
|
+
return pictures
|
|
135
|
+
return home
|
|
136
|
+
pictures = home / "Pictures"
|
|
137
|
+
screenshots = pictures / "Screenshots"
|
|
138
|
+
if screenshots.exists():
|
|
139
|
+
return screenshots
|
|
140
|
+
if pictures.exists():
|
|
141
|
+
return pictures
|
|
142
|
+
return home
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def ensure_parent(path: Path) -> None:
|
|
146
|
+
try:
|
|
147
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
148
|
+
except OSError:
|
|
149
|
+
# Fall back to letting the capture command report a clearer error.
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def resolve_output_path(
|
|
154
|
+
requested_path: str | None, mode: str, fmt: str, system: str
|
|
155
|
+
) -> Path:
|
|
156
|
+
if requested_path:
|
|
157
|
+
path = Path(requested_path).expanduser()
|
|
158
|
+
if path.exists() and path.is_dir():
|
|
159
|
+
path = path / default_filename(fmt)
|
|
160
|
+
elif requested_path.endswith(("/", "\\")) and not path.exists():
|
|
161
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
path = path / default_filename(fmt)
|
|
163
|
+
elif path.suffix == "":
|
|
164
|
+
path = path.with_suffix(f".{fmt}")
|
|
165
|
+
ensure_parent(path)
|
|
166
|
+
return path
|
|
167
|
+
|
|
168
|
+
if mode == "temp":
|
|
169
|
+
tmp_dir = Path(tempfile.gettempdir())
|
|
170
|
+
tmp_path = tmp_dir / default_filename(fmt, prefix="codex-shot")
|
|
171
|
+
ensure_parent(tmp_path)
|
|
172
|
+
return tmp_path
|
|
173
|
+
|
|
174
|
+
dest_dir = default_dir(system)
|
|
175
|
+
dest_path = dest_dir / default_filename(fmt)
|
|
176
|
+
ensure_parent(dest_path)
|
|
177
|
+
return dest_path
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def multi_output_paths(base: Path, suffixes: list[str]) -> list[Path]:
|
|
181
|
+
if len(suffixes) <= 1:
|
|
182
|
+
return [base]
|
|
183
|
+
paths: list[Path] = []
|
|
184
|
+
for suffix in suffixes:
|
|
185
|
+
candidate = base.with_name(f"{base.stem}-{suffix}{base.suffix}")
|
|
186
|
+
ensure_parent(candidate)
|
|
187
|
+
paths.append(candidate)
|
|
188
|
+
return paths
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def run(cmd: list[str]) -> None:
|
|
192
|
+
try:
|
|
193
|
+
subprocess.run(cmd, check=True)
|
|
194
|
+
except FileNotFoundError as exc:
|
|
195
|
+
raise SystemExit(f"required command not found: {cmd[0]}") from exc
|
|
196
|
+
except subprocess.CalledProcessError as exc:
|
|
197
|
+
raise SystemExit(f"command failed ({exc.returncode}): {' '.join(cmd)}") from exc
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def swift_json(script: Path, extra_args: list[str] | None = None) -> dict:
|
|
201
|
+
module_cache = Path(tempfile.gettempdir()) / "codex-swift-module-cache"
|
|
202
|
+
module_cache.mkdir(parents=True, exist_ok=True)
|
|
203
|
+
cmd = ["swift", "-module-cache-path", str(module_cache), str(script)]
|
|
204
|
+
if extra_args:
|
|
205
|
+
cmd.extend(extra_args)
|
|
206
|
+
try:
|
|
207
|
+
proc = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
208
|
+
except FileNotFoundError as exc:
|
|
209
|
+
raise SystemExit("swift not found; install Xcode command line tools") from exc
|
|
210
|
+
except subprocess.CalledProcessError as exc:
|
|
211
|
+
stderr = (exc.stderr or "").strip()
|
|
212
|
+
if "ModuleCache" in stderr and "Operation not permitted" in stderr:
|
|
213
|
+
raise SystemExit(
|
|
214
|
+
"swift needs module-cache access; rerun with escalated permissions"
|
|
215
|
+
) from exc
|
|
216
|
+
msg = stderr or (exc.stdout or "").strip() or "swift helper failed"
|
|
217
|
+
raise SystemExit(msg) from exc
|
|
218
|
+
try:
|
|
219
|
+
return json.loads(proc.stdout)
|
|
220
|
+
except json.JSONDecodeError as exc:
|
|
221
|
+
raise SystemExit(f"swift helper returned invalid JSON: {proc.stdout.strip()}") from exc
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def macos_screen_capture_granted(request: bool = False) -> bool:
|
|
225
|
+
args = ["--request"] if request else []
|
|
226
|
+
payload = swift_json(MAC_PERM_SCRIPT, args)
|
|
227
|
+
return bool(payload.get("screenCapture"))
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def ensure_macos_permissions() -> None:
|
|
231
|
+
if os.environ.get("CODEX_SANDBOX"):
|
|
232
|
+
raise SystemExit(
|
|
233
|
+
"screen capture checks are blocked in the sandbox; rerun with escalated permissions"
|
|
234
|
+
)
|
|
235
|
+
if macos_screen_capture_granted():
|
|
236
|
+
return
|
|
237
|
+
subprocess.run(["bash", str(MAC_PERM_HELPER)], check=False)
|
|
238
|
+
if not macos_screen_capture_granted():
|
|
239
|
+
raise SystemExit(
|
|
240
|
+
"Screen Recording permission is required; enable it in System Settings and retry"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def activate_app(app: str) -> None:
|
|
245
|
+
safe_app = app.replace('"', '\\"')
|
|
246
|
+
script = f'tell application "{safe_app}" to activate'
|
|
247
|
+
subprocess.run(["osascript", "-e", script], check=False, capture_output=True, text=True)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def macos_window_payload(args: argparse.Namespace, frontmost: bool, include_list: bool) -> dict:
|
|
251
|
+
flags: list[str] = []
|
|
252
|
+
if frontmost:
|
|
253
|
+
flags.append("--frontmost")
|
|
254
|
+
if args.app:
|
|
255
|
+
flags.extend(["--app", args.app])
|
|
256
|
+
if args.window_name:
|
|
257
|
+
flags.extend(["--window-name", args.window_name])
|
|
258
|
+
if include_list:
|
|
259
|
+
flags.append("--list")
|
|
260
|
+
return swift_json(MAC_WINDOW_SCRIPT, flags)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def macos_display_indexes() -> list[int]:
|
|
264
|
+
payload = swift_json(MAC_DISPLAY_SCRIPT)
|
|
265
|
+
displays = payload.get("displays") or []
|
|
266
|
+
indexes: list[int] = []
|
|
267
|
+
for item in displays:
|
|
268
|
+
try:
|
|
269
|
+
value = int(item)
|
|
270
|
+
except (TypeError, ValueError):
|
|
271
|
+
continue
|
|
272
|
+
if value > 0:
|
|
273
|
+
indexes.append(value)
|
|
274
|
+
return indexes or [1]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def macos_window_ids(args: argparse.Namespace, capture_all: bool) -> list[int]:
|
|
278
|
+
payload = macos_window_payload(
|
|
279
|
+
args,
|
|
280
|
+
frontmost=args.active_window,
|
|
281
|
+
include_list=capture_all,
|
|
282
|
+
)
|
|
283
|
+
if capture_all:
|
|
284
|
+
windows = payload.get("windows") or []
|
|
285
|
+
ids: list[int] = []
|
|
286
|
+
for item in windows:
|
|
287
|
+
win_id = item.get("id")
|
|
288
|
+
if win_id is None:
|
|
289
|
+
continue
|
|
290
|
+
try:
|
|
291
|
+
ids.append(int(win_id))
|
|
292
|
+
except (TypeError, ValueError):
|
|
293
|
+
continue
|
|
294
|
+
if ids:
|
|
295
|
+
return ids
|
|
296
|
+
selected = payload.get("selected") or {}
|
|
297
|
+
win_id = selected.get("id")
|
|
298
|
+
if win_id is not None:
|
|
299
|
+
try:
|
|
300
|
+
return [int(win_id)]
|
|
301
|
+
except (TypeError, ValueError):
|
|
302
|
+
pass
|
|
303
|
+
raise SystemExit("no matching macOS window found; try --list-windows to inspect ids")
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def list_macos_windows(args: argparse.Namespace) -> None:
|
|
307
|
+
payload = macos_window_payload(args, frontmost=args.active_window, include_list=True)
|
|
308
|
+
windows = payload.get("windows") or []
|
|
309
|
+
if not windows:
|
|
310
|
+
print("no matching windows found")
|
|
311
|
+
return
|
|
312
|
+
for item in windows:
|
|
313
|
+
bounds = item.get("bounds") or {}
|
|
314
|
+
name = item.get("name") or ""
|
|
315
|
+
width = bounds.get("width", 0)
|
|
316
|
+
height = bounds.get("height", 0)
|
|
317
|
+
x = bounds.get("x", 0)
|
|
318
|
+
y = bounds.get("y", 0)
|
|
319
|
+
print(f"{item.get('id')}\t{item.get('owner')}\t{name}\t{width}x{height}+{x}+{y}")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def list_test_macos_windows(args: argparse.Namespace) -> None:
|
|
323
|
+
owner = args.app or "TestApp"
|
|
324
|
+
name = args.window_name or ""
|
|
325
|
+
ids = test_window_ids()
|
|
326
|
+
if args.active_window and ids:
|
|
327
|
+
ids = [ids[0]]
|
|
328
|
+
for idx, win_id in enumerate(ids, start=1):
|
|
329
|
+
window_name = name or f"Window {idx}"
|
|
330
|
+
print(f"{win_id}\t{owner}\t{window_name}\t800x600+0+0")
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def resolve_macos_windows(args: argparse.Namespace) -> list[int]:
|
|
334
|
+
if args.app:
|
|
335
|
+
activate_app(args.app)
|
|
336
|
+
capture_all = not args.active_window
|
|
337
|
+
return macos_window_ids(args, capture_all=capture_all)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def resolve_test_macos_windows(args: argparse.Namespace) -> list[int]:
|
|
341
|
+
ids = test_window_ids()
|
|
342
|
+
if args.active_window and ids:
|
|
343
|
+
return [ids[0]]
|
|
344
|
+
return ids
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def capture_macos(
|
|
348
|
+
args: argparse.Namespace,
|
|
349
|
+
output: Path,
|
|
350
|
+
*,
|
|
351
|
+
window_id: int | None = None,
|
|
352
|
+
display: int | None = None,
|
|
353
|
+
) -> None:
|
|
354
|
+
cmd = ["screencapture", "-x", f"-t{args.format}"]
|
|
355
|
+
if args.interactive:
|
|
356
|
+
cmd.append("-i")
|
|
357
|
+
if display is not None:
|
|
358
|
+
cmd.append(f"-D{display}")
|
|
359
|
+
effective_window_id = window_id if window_id is not None else args.window_id
|
|
360
|
+
if effective_window_id is not None:
|
|
361
|
+
cmd.append(f"-l{effective_window_id}")
|
|
362
|
+
elif args.region is not None:
|
|
363
|
+
x, y, w, h = args.region
|
|
364
|
+
cmd.append(f"-R{x},{y},{w},{h}")
|
|
365
|
+
cmd.append(str(output))
|
|
366
|
+
run(cmd)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def capture_linux(args: argparse.Namespace, output: Path) -> None:
|
|
370
|
+
scrot = shutil.which("scrot")
|
|
371
|
+
gnome = shutil.which("gnome-screenshot")
|
|
372
|
+
imagemagick = shutil.which("import")
|
|
373
|
+
xdotool = shutil.which("xdotool")
|
|
374
|
+
|
|
375
|
+
if args.region is not None:
|
|
376
|
+
x, y, w, h = args.region
|
|
377
|
+
if scrot:
|
|
378
|
+
run(["scrot", "-a", f"{x},{y},{w},{h}", str(output)])
|
|
379
|
+
return
|
|
380
|
+
if imagemagick:
|
|
381
|
+
geometry = f"{w}x{h}+{x}+{y}"
|
|
382
|
+
run(["import", "-window", "root", "-crop", geometry, str(output)])
|
|
383
|
+
return
|
|
384
|
+
raise SystemExit("region capture requires scrot or ImageMagick (import)")
|
|
385
|
+
|
|
386
|
+
if args.window_id is not None:
|
|
387
|
+
if imagemagick:
|
|
388
|
+
run(["import", "-window", str(args.window_id), str(output)])
|
|
389
|
+
return
|
|
390
|
+
raise SystemExit("window-id capture requires ImageMagick (import)")
|
|
391
|
+
|
|
392
|
+
if args.active_window:
|
|
393
|
+
if scrot:
|
|
394
|
+
run(["scrot", "-u", str(output)])
|
|
395
|
+
return
|
|
396
|
+
if gnome:
|
|
397
|
+
run(["gnome-screenshot", "-w", "-f", str(output)])
|
|
398
|
+
return
|
|
399
|
+
if imagemagick and xdotool:
|
|
400
|
+
win_id = (
|
|
401
|
+
subprocess.check_output(["xdotool", "getactivewindow"], text=True)
|
|
402
|
+
.strip()
|
|
403
|
+
)
|
|
404
|
+
run(["import", "-window", win_id, str(output)])
|
|
405
|
+
return
|
|
406
|
+
raise SystemExit("active-window capture requires scrot, gnome-screenshot, or import+xdotool")
|
|
407
|
+
|
|
408
|
+
if scrot:
|
|
409
|
+
run(["scrot", str(output)])
|
|
410
|
+
return
|
|
411
|
+
if gnome:
|
|
412
|
+
run(["gnome-screenshot", "-f", str(output)])
|
|
413
|
+
return
|
|
414
|
+
if imagemagick:
|
|
415
|
+
run(["import", "-window", "root", str(output)])
|
|
416
|
+
return
|
|
417
|
+
raise SystemExit("no supported screenshot tool found (scrot, gnome-screenshot, or import)")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def main() -> None:
|
|
421
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
422
|
+
parser.add_argument(
|
|
423
|
+
"--path",
|
|
424
|
+
help="output file path or directory; overrides --mode",
|
|
425
|
+
)
|
|
426
|
+
parser.add_argument(
|
|
427
|
+
"--mode",
|
|
428
|
+
choices=("default", "temp"),
|
|
429
|
+
default="default",
|
|
430
|
+
help="default saves to the OS screenshot location; temp saves to the temp dir",
|
|
431
|
+
)
|
|
432
|
+
parser.add_argument(
|
|
433
|
+
"--format",
|
|
434
|
+
default="png",
|
|
435
|
+
help="image format/extension (default: png)",
|
|
436
|
+
)
|
|
437
|
+
parser.add_argument(
|
|
438
|
+
"--app",
|
|
439
|
+
help="macOS only: capture all matching on-screen windows for this app name",
|
|
440
|
+
)
|
|
441
|
+
parser.add_argument(
|
|
442
|
+
"--window-name",
|
|
443
|
+
help="macOS only: substring match for a window title (optionally scoped by --app)",
|
|
444
|
+
)
|
|
445
|
+
parser.add_argument(
|
|
446
|
+
"--list-windows",
|
|
447
|
+
action="store_true",
|
|
448
|
+
help="macOS only: list matching window ids instead of capturing",
|
|
449
|
+
)
|
|
450
|
+
parser.add_argument(
|
|
451
|
+
"--region",
|
|
452
|
+
type=parse_region,
|
|
453
|
+
help="capture region as x,y,w,h (pixel coordinates)",
|
|
454
|
+
)
|
|
455
|
+
parser.add_argument(
|
|
456
|
+
"--window-id",
|
|
457
|
+
type=int,
|
|
458
|
+
help="capture a specific window id when supported",
|
|
459
|
+
)
|
|
460
|
+
parser.add_argument(
|
|
461
|
+
"--active-window",
|
|
462
|
+
action="store_true",
|
|
463
|
+
help="capture the focused/active window only when supported",
|
|
464
|
+
)
|
|
465
|
+
parser.add_argument(
|
|
466
|
+
"--interactive",
|
|
467
|
+
action="store_true",
|
|
468
|
+
help="use interactive selection where the OS tool supports it",
|
|
469
|
+
)
|
|
470
|
+
args = parser.parse_args()
|
|
471
|
+
|
|
472
|
+
if args.region and args.window_id is not None:
|
|
473
|
+
raise SystemExit("choose either --region or --window-id, not both")
|
|
474
|
+
if args.region and args.active_window:
|
|
475
|
+
raise SystemExit("choose either --region or --active-window, not both")
|
|
476
|
+
if args.window_id is not None and args.active_window:
|
|
477
|
+
raise SystemExit("choose either --window-id or --active-window, not both")
|
|
478
|
+
if args.app and args.window_id is not None:
|
|
479
|
+
raise SystemExit("choose either --app or --window-id, not both")
|
|
480
|
+
if args.region and args.app:
|
|
481
|
+
raise SystemExit("choose either --region or --app, not both")
|
|
482
|
+
if args.region and args.window_name:
|
|
483
|
+
raise SystemExit("choose either --region or --window-name, not both")
|
|
484
|
+
if args.interactive and args.app:
|
|
485
|
+
raise SystemExit("choose either --interactive or --app, not both")
|
|
486
|
+
if args.interactive and args.window_name:
|
|
487
|
+
raise SystemExit("choose either --interactive or --window-name, not both")
|
|
488
|
+
if args.interactive and args.window_id is not None:
|
|
489
|
+
raise SystemExit("choose either --interactive or --window-id, not both")
|
|
490
|
+
if args.interactive and args.active_window:
|
|
491
|
+
raise SystemExit("choose either --interactive or --active-window, not both")
|
|
492
|
+
if args.list_windows and (args.region or args.window_id is not None or args.interactive):
|
|
493
|
+
raise SystemExit("--list-windows only supports --app, --window-name, and --active-window")
|
|
494
|
+
|
|
495
|
+
test_mode = test_mode_enabled()
|
|
496
|
+
system = platform.system()
|
|
497
|
+
if test_mode:
|
|
498
|
+
override = test_platform_override()
|
|
499
|
+
if override:
|
|
500
|
+
system = override
|
|
501
|
+
window_ids: list[int] = []
|
|
502
|
+
display_ids: list[int] = []
|
|
503
|
+
|
|
504
|
+
if system != "Darwin" and (args.app or args.window_name or args.list_windows):
|
|
505
|
+
raise SystemExit("--app/--window-name/--list-windows are supported on macOS only")
|
|
506
|
+
|
|
507
|
+
if system == "Darwin":
|
|
508
|
+
if test_mode:
|
|
509
|
+
if args.list_windows:
|
|
510
|
+
list_test_macos_windows(args)
|
|
511
|
+
return
|
|
512
|
+
if args.window_id is not None:
|
|
513
|
+
window_ids = [args.window_id]
|
|
514
|
+
elif args.app or args.window_name or args.active_window:
|
|
515
|
+
window_ids = resolve_test_macos_windows(args)
|
|
516
|
+
elif args.region is None and not args.interactive:
|
|
517
|
+
display_ids = test_display_ids()
|
|
518
|
+
else:
|
|
519
|
+
ensure_macos_permissions()
|
|
520
|
+
if args.list_windows:
|
|
521
|
+
list_macos_windows(args)
|
|
522
|
+
return
|
|
523
|
+
if args.window_id is not None:
|
|
524
|
+
window_ids = [args.window_id]
|
|
525
|
+
elif args.app or args.window_name or args.active_window:
|
|
526
|
+
window_ids = resolve_macos_windows(args)
|
|
527
|
+
elif args.region is None and not args.interactive:
|
|
528
|
+
display_ids = macos_display_indexes()
|
|
529
|
+
|
|
530
|
+
output = resolve_output_path(args.path, args.mode, args.format, system)
|
|
531
|
+
|
|
532
|
+
if test_mode:
|
|
533
|
+
if system == "Darwin":
|
|
534
|
+
if window_ids:
|
|
535
|
+
suffixes = [f"w{wid}" for wid in window_ids]
|
|
536
|
+
paths = multi_output_paths(output, suffixes)
|
|
537
|
+
for path in paths:
|
|
538
|
+
write_test_png(path)
|
|
539
|
+
for path in paths:
|
|
540
|
+
print(path)
|
|
541
|
+
return
|
|
542
|
+
if len(display_ids) > 1:
|
|
543
|
+
suffixes = [f"d{did}" for did in display_ids]
|
|
544
|
+
paths = multi_output_paths(output, suffixes)
|
|
545
|
+
for path in paths:
|
|
546
|
+
write_test_png(path)
|
|
547
|
+
for path in paths:
|
|
548
|
+
print(path)
|
|
549
|
+
return
|
|
550
|
+
write_test_png(output)
|
|
551
|
+
print(output)
|
|
552
|
+
return
|
|
553
|
+
|
|
554
|
+
if system == "Darwin":
|
|
555
|
+
if window_ids:
|
|
556
|
+
suffixes = [f"w{wid}" for wid in window_ids]
|
|
557
|
+
paths = multi_output_paths(output, suffixes)
|
|
558
|
+
for wid, path in zip(window_ids, paths):
|
|
559
|
+
capture_macos(args, path, window_id=wid)
|
|
560
|
+
for path in paths:
|
|
561
|
+
print(path)
|
|
562
|
+
return
|
|
563
|
+
if len(display_ids) > 1:
|
|
564
|
+
suffixes = [f"d{did}" for did in display_ids]
|
|
565
|
+
paths = multi_output_paths(output, suffixes)
|
|
566
|
+
for did, path in zip(display_ids, paths):
|
|
567
|
+
capture_macos(args, path, display=did)
|
|
568
|
+
for path in paths:
|
|
569
|
+
print(path)
|
|
570
|
+
return
|
|
571
|
+
capture_macos(args, output)
|
|
572
|
+
elif system == "Linux":
|
|
573
|
+
capture_linux(args, output)
|
|
574
|
+
elif system == "Windows":
|
|
575
|
+
raise SystemExit(
|
|
576
|
+
"Windows support lives in scripts/take_screenshot.ps1; run it with PowerShell"
|
|
577
|
+
)
|
|
578
|
+
else:
|
|
579
|
+
raise SystemExit(f"unsupported platform: {system}")
|
|
580
|
+
|
|
581
|
+
print(output)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
if __name__ == "__main__":
|
|
585
|
+
main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-threat-model
|
|
3
|
+
description: Use when the user explicitly requests threat modeling for a repo or path, with concrete abuse paths, trust boundaries, and mitigations.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
tags: [planning, code-quality, research]
|
|
6
|
+
dependencies: []
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# security-threat-model
|
|
10
|
+
|
|
11
|
+
Repository-grounded threat modeling for real attack paths and mitigations.
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- User explicitly asks for threat modeling
|
|
16
|
+
- User asks for abuse-path analysis, attacker goals, or trust-boundary review
|
|
17
|
+
|
|
18
|
+
## When NOT to Use
|
|
19
|
+
|
|
20
|
+
- General code review without threat-model request
|
|
21
|
+
- Pure style/refactor requests
|
|
22
|
+
|
|
23
|
+
## Workflow
|
|
24
|
+
|
|
25
|
+
1. Scope system/runtime components from repository evidence
|
|
26
|
+
2. Enumerate trust boundaries, assets, and entry points
|
|
27
|
+
3. Define realistic attacker capabilities
|
|
28
|
+
4. Model concrete abuse paths and rank by impact/likelihood
|
|
29
|
+
5. Validate assumptions with user (1–3 targeted questions)
|
|
30
|
+
6. Recommend mitigations tied to concrete files/components
|
|
31
|
+
7. Write final report to `<repo-name>-threat-model.md`
|
|
32
|
+
|
|
33
|
+
## References
|
|
34
|
+
|
|
35
|
+
- `references/prompt-template.md`
|
|
36
|
+
- `references/security-controls-and-assets.md`
|