@fprad0/skill-master-mcp 0.0.11 → 1.0.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.
Files changed (337) hide show
  1. package/CHANGELOG.md +96 -83
  2. package/README.md +472 -443
  3. package/VERSION.md +9 -9
  4. package/bin/lib/bootstrap-global-core.mjs +34 -0
  5. package/bin/lib/client-config.mjs +293 -268
  6. package/bin/lib/doctor-core.mjs +202 -0
  7. package/bin/lib/menu-core.mjs +1629 -1154
  8. package/bin/lib/operation-result.mjs +59 -0
  9. package/bin/lib/register-clients-core.mjs +247 -0
  10. package/bin/lib/skill-installation.mjs +215 -0
  11. package/bin/lib/update-cli-core.mjs +117 -0
  12. package/bin/skill-master-activation.mjs +163 -163
  13. package/bin/skill-master-bootstrap-global.mjs +61 -49
  14. package/bin/skill-master-configure-private-registry.mjs +3 -3
  15. package/bin/skill-master-doctor.mjs +239 -181
  16. package/bin/skill-master-eval-activation.mjs +32 -32
  17. package/bin/skill-master-install-global-skills.mjs +59 -97
  18. package/bin/skill-master-install-project-skills.mjs +97 -0
  19. package/bin/skill-master-menu.mjs +406 -320
  20. package/bin/skill-master-register-clients.mjs +232 -98
  21. package/bin/skill-master-success-skills.mjs +307 -307
  22. package/bin/skill-master-update.mjs +121 -72
  23. package/bin/skill-master.mjs +3 -3
  24. package/dist/activation.d.ts.map +1 -1
  25. package/dist/activation.js +12 -0
  26. package/dist/activation.js.map +1 -1
  27. package/dist/prompt-router.d.ts.map +1 -1
  28. package/dist/prompt-router.js +19 -0
  29. package/dist/prompt-router.js.map +1 -1
  30. package/dist/recommender.d.ts.map +1 -1
  31. package/dist/recommender.js +4 -1
  32. package/dist/recommender.js.map +1 -1
  33. package/docs/architecture/APRENDIZADO_DE_IMPLEMENTACOES_BEM_SUCEDIDAS.md +125 -125
  34. package/docs/architecture/ARQUITETURA_AUTO_UPDATE.md +9 -9
  35. package/docs/architecture/PLANO_MASTER_ACIONAMENTO_AUTOMATICO_E_APRENDIZADO.md +341 -341
  36. package/docs/architecture/REDE_SEGURA_DE_SKILLS.md +148 -148
  37. package/docs/operations/GUIA_MULTI_COMPUTADOR.md +262 -255
  38. package/docs/operations/GUIA_NPM_PRIVADO.md +294 -294
  39. package/docs/operations/GUIA_NPM_PUBLICO.md +147 -147
  40. package/docs/operations/MENU_VISUAL_EVIDENCE_2026-06-28.md +66 -0
  41. package/docs/operations/assets/menu-frame-compact.html +76 -0
  42. package/docs/operations/assets/menu-frame-compact.png +0 -0
  43. package/docs/operations/assets/menu-frame-large.html +84 -0
  44. package/docs/operations/assets/menu-frame-large.png +0 -0
  45. package/docs/operations/assets/menu-frame-running.html +80 -0
  46. package/docs/operations/assets/menu-frame-running.png +0 -0
  47. package/docs/operations/cross-platform-auth-transfer/ANALISE_COMPATIBILIDADE_MCP_2026-06-28.md +140 -0
  48. package/docs/operations/cross-platform-auth-transfer/README_TRANSFERENCIA.md +85 -0
  49. package/docs/operations/reborn-menu-cyberpunk-transfer/ANALISE_MENU_REBORN_CYBERPUNK_2026-06-28.md +174 -0
  50. package/docs/operations/reborn-menu-cyberpunk-transfer/HANDOFF_IMPLEMENTACAO_REBORN_CYBERPUNK_2026-06-28.md +119 -0
  51. package/docs/operations/reborn-menu-cyberpunk-transfer/ORDEM_DE_EXECUCAO_MENU_REBORN_CYBERPUNK.md +134 -0
  52. package/docs/operations/reborn-menu-cyberpunk-transfer/README_TRANSFERENCIA.md +84 -0
  53. package/docs/operations/reborn-menu-cyberpunk-transfer/README_TRANSFERENCIA_REBORN_PACKAGE.md +56 -0
  54. package/docs/operations/reborn-menu-cyberpunk-transfer/references/cyan-hud-frame-sheet.jpg +0 -0
  55. package/docs/operations/reborn-menu-cyberpunk-transfer/references/cyberpunk-pattern-sheet.jpg +0 -0
  56. package/docs/operations/reborn-menu-cyberpunk-transfer/references/fluid-workflow-windows.gif +0 -0
  57. package/docs/operations/token-economy-transfer/ANALISE_AVANCADA_ECONOMIA_TOKENS_2026-06-30.md +141 -0
  58. package/docs/operations/token-economy-transfer/PLANO_DEV_SENIOR_MASTER_TOKEN_ECONOMY_2026-06-30.md +171 -0
  59. package/docs/operations/token-economy-transfer/README_TRANSFERENCIA_TOKEN_ECONOMY.md +31 -0
  60. package/docs/planning/MENU_RUNTIME_CORRECTION_PLAN_2026-06-30.md +551 -0
  61. package/docs/planning/V0_0_9_APROVACAO_CRITICA_MENSAGENS_DE_VENDA.md +85 -85
  62. package/docs/planning/V0_0_9_FONTES_E_CRITERIOS_DE_AUTORIDADE.md +139 -139
  63. package/docs/planning/V0_0_9_MATRIZ_SKILLS_MULTIDISCIPLINARES.md +105 -105
  64. package/docs/planning/V0_0_9_POLITICA_MORAL_CATOLICA_PARA_IA.md +181 -181
  65. package/docs/planning/V0_0_9_PROMPTS_EXECUCAO.md +59 -59
  66. package/docs/planning/V0_0_9_ROADMAP_DISCERNIMENTO_E_CONHECIMENTO_AMPLO.md +181 -181
  67. package/docs/prompt-tasks/PROMPT_TASK_001_BOOTSTRAP_SKILL_MASTER_MCP.md +6 -0
  68. package/docs/prompt-tasks/PROMPT_TASK_002_AUTO_UPDATE_LAUNCHER.md +6 -0
  69. package/docs/prompt-tasks/PROMPT_TASK_003_REMOTE_MANIFEST_AND_RELEASES.md +6 -0
  70. package/docs/prompt-tasks/PROMPT_TASK_004_MULTI_USER_DISTRIBUTION.md +6 -0
  71. package/docs/prompt-tasks/PROMPT_TASK_005_SECURITY_AND_QUALITY_GATE.md +6 -0
  72. package/docs/prompt-tasks/PROMPT_TASK_006_MASTER_ACIONAMENTO_APRENDIZADO.md +83 -0
  73. package/docs/prompt-tasks/PROMPT_TASK_007_PERSONA_ORQUESTRADORA.md +88 -0
  74. package/docs/prompt-tasks/PROMPT_TASK_008_PROMPT_ROUTER_MODOS_ATIVACAO.md +156 -0
  75. package/docs/prompt-tasks/PROMPT_TASK_009_PIPELINE_APRENDIZADO_SUCESSO.md +105 -0
  76. package/docs/prompt-tasks/PROMPT_TASK_010_EVALS_GOVERNANCA_ATIVACAO.md +119 -0
  77. package/docs/prompt-tasks/PROMPT_TASK_011_MENU_NOTIFICACOES_NOTION.md +120 -0
  78. package/docs/prompt-tasks/PROMPT_TASK_012_MENU_CYBERPUNK_PIXEL_FRAME.md +123 -0
  79. package/docs/prompt-tasks/PROMPT_TASK_013_MENU_FLUID_DNA_ANIMATION.md +114 -0
  80. package/docs/prompt-tasks/PROMPT_TASK_014_MENU_FUNCTIONAL_PARITY_QA.md +157 -0
  81. package/docs/prompt-tasks/PROMPT_TASK_015_TRANSFER_RELEASE_HANDOFF.md +127 -0
  82. package/docs/prompt-tasks/PROMPT_TASK_016_CROSS_PLATFORM_MCP_AUTH_REGISTRATION.md +107 -0
  83. package/docs/prompt-tasks/PROMPT_TASK_018_NPM_PUBLISH_2FA_SETUP.md +80 -0
  84. package/docs/prompt-tasks/PROMPT_TASK_019_TOKEN_ECONOMY_GLOBAL_SKILLS.md +56 -0
  85. package/docs/prompt-tasks/PROMPT_TASK_MASTER_EXECUTOR.md +6 -0
  86. package/docs/skill-candidates/v0.0.10/cli-creator/LICENSE.txt +201 -201
  87. package/docs/skill-candidates/v0.0.10/cli-creator/SKILL.md +160 -160
  88. package/docs/skill-candidates/v0.0.10/cli-creator/agents/openai.yaml +4 -4
  89. package/docs/skill-candidates/v0.0.10/cli-creator/references/agent-cli-patterns.md +154 -154
  90. package/docs/skill-candidates/v0.0.10/developer-workstation-ops/SKILL.md +32 -32
  91. package/docs/skill-candidates/v0.0.10/figma/LICENSE.txt +1 -1
  92. package/docs/skill-candidates/v0.0.10/figma/SKILL.md +42 -42
  93. package/docs/skill-candidates/v0.0.10/figma/agents/openai.yaml +14 -14
  94. package/docs/skill-candidates/v0.0.10/figma/assets/figma-small.svg +3 -3
  95. package/docs/skill-candidates/v0.0.10/figma/assets/icon.svg +28 -28
  96. package/docs/skill-candidates/v0.0.10/figma/references/figma-mcp-config.md +35 -35
  97. package/docs/skill-candidates/v0.0.10/figma/references/figma-tools-and-prompts.md +34 -34
  98. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/LICENSE.TXT +1 -1
  99. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/SKILL.md +349 -349
  100. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/agents/openai.yaml +14 -14
  101. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/assets/figma-small.svg +3 -3
  102. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/assets/icon.svg +28 -28
  103. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/references/mapping-checklist.md +7 -7
  104. package/docs/skill-candidates/v0.0.10/figma-code-connect-components/scripts/normalize_node_id.py +25 -25
  105. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/LICENSE.TXT +1 -1
  106. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/SKILL.md +537 -537
  107. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/agents/openai.yaml +14 -14
  108. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/assets/figma-small.svg +3 -3
  109. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/assets/icon.svg +28 -28
  110. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/references/rule-template.md +15 -15
  111. package/docs/skill-candidates/v0.0.10/figma-create-design-system-rules/scripts/check_agents_md.sh +9 -9
  112. package/docs/skill-candidates/v0.0.10/figma-generate-design/LICENSE.TXT +1 -1
  113. package/docs/skill-candidates/v0.0.10/figma-generate-design/SKILL.md +341 -341
  114. package/docs/skill-candidates/v0.0.10/figma-generate-design/agents/openai.yaml +14 -14
  115. package/docs/skill-candidates/v0.0.10/figma-generate-design/assets/figma-small.svg +3 -3
  116. package/docs/skill-candidates/v0.0.10/figma-generate-design/assets/icon.svg +28 -28
  117. package/docs/skill-candidates/v0.0.10/figma-generate-design/maintainers.yml +1 -1
  118. package/docs/skill-candidates/v0.0.10/figma-generate-library/LICENSE.TXT +1 -1
  119. package/docs/skill-candidates/v0.0.10/figma-generate-library/SKILL.md +314 -314
  120. package/docs/skill-candidates/v0.0.10/figma-generate-library/agents/openai.yaml +14 -14
  121. package/docs/skill-candidates/v0.0.10/figma-generate-library/assets/figma-small.svg +3 -3
  122. package/docs/skill-candidates/v0.0.10/figma-generate-library/assets/icon.svg +28 -28
  123. package/docs/skill-candidates/v0.0.10/figma-generate-library/maintainers.yml +3 -3
  124. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/code-connect-setup.md +260 -260
  125. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/component-creation.md +1014 -1014
  126. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/discovery-phase.md +518 -518
  127. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/documentation-creation.md +834 -834
  128. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/error-recovery.md +540 -540
  129. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/naming-conventions.md +527 -527
  130. package/docs/skill-candidates/v0.0.10/figma-generate-library/references/token-creation.md +962 -962
  131. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/bindVariablesToComponent.js +110 -110
  132. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/cleanupOrphans.js +127 -127
  133. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createComponentWithVariants.js +148 -148
  134. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createDocumentationPage.js +139 -139
  135. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createSemanticTokens.js +108 -108
  136. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/createVariableCollection.js +49 -49
  137. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/inspectFileStructure.js +121 -121
  138. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/rehydrateState.js +92 -92
  139. package/docs/skill-candidates/v0.0.10/figma-generate-library/scripts/validateCreation.js +83 -83
  140. package/docs/skill-candidates/v0.0.10/figma-implement-design/LICENSE.txt +1 -1
  141. package/docs/skill-candidates/v0.0.10/figma-implement-design/SKILL.md +258 -258
  142. package/docs/skill-candidates/v0.0.10/figma-implement-design/agents/openai.yaml +14 -14
  143. package/docs/skill-candidates/v0.0.10/figma-implement-design/assets/figma-small.svg +3 -3
  144. package/docs/skill-candidates/v0.0.10/figma-implement-design/assets/icon.svg +28 -28
  145. package/docs/skill-candidates/v0.0.10/figma-use/LICENSE.TXT +1 -1
  146. package/docs/skill-candidates/v0.0.10/figma-use/SKILL.md +233 -233
  147. package/docs/skill-candidates/v0.0.10/figma-use/agents/openai.yaml +14 -14
  148. package/docs/skill-candidates/v0.0.10/figma-use/assets/figma-small.svg +3 -3
  149. package/docs/skill-candidates/v0.0.10/figma-use/assets/icon.svg +28 -28
  150. package/docs/skill-candidates/v0.0.10/figma-use/maintainers.yml +1 -1
  151. package/docs/skill-candidates/v0.0.10/figma-use/references/api-reference.md +301 -301
  152. package/docs/skill-candidates/v0.0.10/figma-use/references/common-patterns.md +512 -512
  153. package/docs/skill-candidates/v0.0.10/figma-use/references/component-patterns.md +488 -488
  154. package/docs/skill-candidates/v0.0.10/figma-use/references/effect-style-patterns.md +123 -123
  155. package/docs/skill-candidates/v0.0.10/figma-use/references/gotchas.md +599 -599
  156. package/docs/skill-candidates/v0.0.10/figma-use/references/maintainers.yml +12 -12
  157. package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-patterns.md +513 -513
  158. package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-standalone.d.ts +11293 -11293
  159. package/docs/skill-candidates/v0.0.10/figma-use/references/plugin-api-standalone.index.md +441 -441
  160. package/docs/skill-candidates/v0.0.10/figma-use/references/text-style-patterns.md +203 -203
  161. package/docs/skill-candidates/v0.0.10/figma-use/references/validation-and-recovery.md +109 -109
  162. package/docs/skill-candidates/v0.0.10/figma-use/references/variable-patterns.md +354 -354
  163. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/maintainers.yml +9 -9
  164. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components--creating.md +17 -17
  165. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components--using.md +17 -17
  166. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-components.md +50 -50
  167. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-effect-styles.md +52 -52
  168. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-text-styles.md +90 -90
  169. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables--creating.md +13 -13
  170. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables--using.md +13 -13
  171. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds-variables.md +64 -64
  172. package/docs/skill-candidates/v0.0.10/figma-use/references/working-with-design-systems/wwds.md +41 -41
  173. package/docs/skill-candidates/v0.0.10/frontend-design/LICENSE.txt +177 -177
  174. package/docs/skill-candidates/v0.0.10/frontend-design/SKILL.md +55 -55
  175. package/docs/skill-candidates/v0.0.10/frontend-ui-ux-systems/SKILL.md +32 -32
  176. package/docs/skill-candidates/v0.0.10/github/SKILL.md +74 -74
  177. package/docs/skill-candidates/v0.0.10/github/agents/openai.yaml +6 -6
  178. package/docs/skill-candidates/v0.0.10/github/assets/github-small.svg +3 -3
  179. package/docs/skill-candidates/v0.0.10/image-graphic-design-rendering/SKILL.md +28 -28
  180. package/docs/skill-candidates/v0.0.10/language-quality-pt-en-fr-it-ru/SKILL.md +28 -28
  181. package/docs/skill-candidates/v0.0.10/math-physics-reasoning/SKILL.md +28 -28
  182. package/docs/skill-candidates/v0.0.10/mcp-builder/LICENSE.txt +201 -201
  183. package/docs/skill-candidates/v0.0.10/mcp-builder/SKILL.md +236 -236
  184. package/docs/skill-candidates/v0.0.10/mcp-builder/reference/evaluation.md +601 -601
  185. package/docs/skill-candidates/v0.0.10/mcp-builder/reference/mcp_best_practices.md +249 -249
  186. package/docs/skill-candidates/v0.0.10/mcp-builder/reference/node_mcp_server.md +969 -969
  187. package/docs/skill-candidates/v0.0.10/mcp-builder/reference/python_mcp_server.md +718 -718
  188. package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/connections.py +151 -151
  189. package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/evaluation.py +373 -373
  190. package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/example_evaluation.xml +22 -22
  191. package/docs/skill-candidates/v0.0.10/mcp-builder/scripts/requirements.txt +2 -2
  192. package/docs/skill-candidates/v0.0.10/mcp-client-readiness/SKILL.md +31 -31
  193. package/docs/skill-candidates/v0.0.10/openai-docs/LICENSE.txt +201 -201
  194. package/docs/skill-candidates/v0.0.10/openai-docs/SKILL.md +161 -161
  195. package/docs/skill-candidates/v0.0.10/openai-docs/agents/openai.yaml +14 -14
  196. package/docs/skill-candidates/v0.0.10/openai-docs/assets/openai-small.svg +3 -3
  197. package/docs/skill-candidates/v0.0.10/openai-docs/references/latest-model.md +37 -37
  198. package/docs/skill-candidates/v0.0.10/openai-docs/references/prompting-guide.md +244 -244
  199. package/docs/skill-candidates/v0.0.10/openai-docs/references/upgrade-guide.md +181 -181
  200. package/docs/skill-candidates/v0.0.10/openai-docs/scripts/fetch-codex-manual.mjs +598 -598
  201. package/docs/skill-candidates/v0.0.10/openai-docs/scripts/resolve-latest-model-info.js +147 -147
  202. package/docs/skill-candidates/v0.0.10/playwright/NOTICE.txt +14 -14
  203. package/docs/skill-candidates/v0.0.10/playwright/SKILL.md +147 -147
  204. package/docs/skill-candidates/v0.0.10/playwright/agents/openai.yaml +6 -6
  205. package/docs/skill-candidates/v0.0.10/playwright/assets/playwright-small.svg +3 -3
  206. package/docs/skill-candidates/v0.0.10/playwright/references/cli.md +116 -116
  207. package/docs/skill-candidates/v0.0.10/playwright/references/workflows.md +95 -95
  208. package/docs/skill-candidates/v0.0.10/playwright/scripts/playwright_cli.sh +25 -25
  209. package/docs/skill-candidates/v0.0.10/polyglot-backend-engineering/SKILL.md +32 -32
  210. package/docs/skill-candidates/v0.0.10/screenshot/LICENSE.txt +201 -201
  211. package/docs/skill-candidates/v0.0.10/screenshot/SKILL.md +267 -267
  212. package/docs/skill-candidates/v0.0.10/screenshot/agents/openai.yaml +6 -6
  213. package/docs/skill-candidates/v0.0.10/screenshot/assets/screenshot-small.svg +5 -5
  214. package/docs/skill-candidates/v0.0.10/screenshot/scripts/ensure_macos_permissions.sh +54 -54
  215. package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_display_info.swift +22 -22
  216. package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_permissions.swift +40 -40
  217. package/docs/skill-candidates/v0.0.10/screenshot/scripts/macos_window_info.swift +126 -126
  218. package/docs/skill-candidates/v0.0.10/screenshot/scripts/take_screenshot.ps1 +163 -163
  219. package/docs/skill-candidates/v0.0.10/screenshot/scripts/take_screenshot.py +585 -585
  220. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/SKILL.md +62 -62
  221. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/agents/openai.yaml +4 -4
  222. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/activation-policy.md +77 -77
  223. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/human-approval-policy.md +83 -83
  224. package/docs/skill-candidates/v0.0.10/skill-master-orchestrator/references/persona-dev-senior-master.md +46 -46
  225. package/docs/skill-candidates/v0.0.10/terminal-menu-operations/SKILL.md +30 -30
  226. package/docs/skill-candidates/v0.0.10/terminal-pixel-art-tui/SKILL.md +43 -43
  227. package/docs/skill-candidates/v0.0.10/webapp-testing/LICENSE.txt +201 -201
  228. package/docs/skill-candidates/v0.0.10/webapp-testing/SKILL.md +95 -95
  229. package/docs/skill-candidates/v0.0.10/webapp-testing/examples/console_logging.py +34 -34
  230. package/docs/skill-candidates/v0.0.10/webapp-testing/examples/element_discovery.py +39 -39
  231. package/docs/skill-candidates/v0.0.10/webapp-testing/examples/static_html_automation.py +32 -32
  232. package/docs/skill-candidates/v0.0.10/webapp-testing/scripts/with_server.py +105 -105
  233. package/docs/skill-candidates/v0.0.10/winui-app/LICENSE.txt +201 -201
  234. package/docs/skill-candidates/v0.0.10/winui-app/SKILL.md +94 -94
  235. package/docs/skill-candidates/v0.0.10/winui-app/agents/openai.yaml +5 -5
  236. package/docs/skill-candidates/v0.0.10/winui-app/config.yaml +50 -50
  237. package/docs/skill-candidates/v0.0.10/winui-app/references/_sections.md +96 -96
  238. package/docs/skill-candidates/v0.0.10/winui-app/references/accessibility-input-and-localization.md +51 -51
  239. package/docs/skill-candidates/v0.0.10/winui-app/references/build-run-and-launch-verification.md +72 -72
  240. package/docs/skill-candidates/v0.0.10/winui-app/references/community-toolkit-controls-and-helpers.md +57 -57
  241. package/docs/skill-candidates/v0.0.10/winui-app/references/controls-layout-and-adaptive-ui.md +84 -84
  242. package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-environment-audit-and-remediation.md +82 -82
  243. package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-setup-and-project-selection.md +67 -67
  244. package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-template-first-recovery.md +62 -62
  245. package/docs/skill-candidates/v0.0.10/winui-app/references/foundation-winui-app-structure.md +62 -62
  246. package/docs/skill-candidates/v0.0.10/winui-app/references/motion-animations-and-polish.md +45 -45
  247. package/docs/skill-candidates/v0.0.10/winui-app/references/performance-diagnostics-and-responsiveness.md +46 -46
  248. package/docs/skill-candidates/v0.0.10/winui-app/references/sample-source-map.md +37 -37
  249. package/docs/skill-candidates/v0.0.10/winui-app/references/shell-navigation-and-windowing.md +67 -67
  250. package/docs/skill-candidates/v0.0.10/winui-app/references/styling-theming-materials-and-icons.md +71 -71
  251. package/docs/skill-candidates/v0.0.10/winui-app/references/testing-debugging-and-review-checklists.md +77 -77
  252. package/docs/skill-candidates/v0.0.10/winui-app/references/windows-app-sdk-lifecycle-notifications-and-deployment.md +52 -52
  253. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/SKILL.md +398 -398
  254. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/common-patterns.md +330 -330
  255. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/complete-examples.md +871 -871
  256. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/component-patterns.md +501 -501
  257. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/data-fetching.md +766 -766
  258. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/file-organization.md +501 -501
  259. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/loading-and-error-states.md +500 -500
  260. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/performance.md +405 -405
  261. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/routing-guide.md +363 -363
  262. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/styling-guide.md +427 -427
  263. package/docs/skill-candidates/v0.0.11/frontend-dev-guidelines/resources/typescript-standards.md +417 -417
  264. package/docs/skill-candidates/v0.0.11/git-version-control-ops/SKILL.md +34 -34
  265. package/docs/skill-candidates/v0.0.11/go-engineering/SKILL.md +34 -34
  266. package/docs/skill-candidates/v0.0.11/java-engineering/SKILL.md +34 -34
  267. package/docs/skill-candidates/v0.0.11/javascript-engineering/SKILL.md +34 -34
  268. package/docs/skill-candidates/v0.0.11/json-contract-design/SKILL.md +34 -34
  269. package/docs/skill-candidates/v0.0.11/multi-client-mcp-ops/SKILL.md +36 -36
  270. package/docs/skill-candidates/v0.0.11/nextjs/SKILL.md +745 -745
  271. package/docs/skill-candidates/v0.0.11/nextjs/agents/openai.yaml +3 -3
  272. package/docs/skill-candidates/v0.0.11/nextjs/references/app-router-files.md +94 -94
  273. package/docs/skill-candidates/v0.0.11/python-engineering/SKILL.md +34 -34
  274. package/docs/skill-candidates/v0.0.11/ruby-engineering/SKILL.md +34 -34
  275. package/docs/skill-candidates/v0.0.11/senior-fullstack/SKILL.md +209 -209
  276. package/docs/skill-candidates/v0.0.11/senior-fullstack/references/architecture_patterns.md +103 -103
  277. package/docs/skill-candidates/v0.0.11/senior-fullstack/references/development_workflows.md +103 -103
  278. package/docs/skill-candidates/v0.0.11/senior-fullstack/references/tech_stack_guide.md +103 -103
  279. package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/code_quality_analyzer.py +114 -114
  280. package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/fullstack_scaffolder.py +114 -114
  281. package/docs/skill-candidates/v0.0.11/senior-fullstack/scripts/project_scaffolder.py +114 -114
  282. package/docs/skill-candidates/v0.0.11/shadcn/SKILL.md +573 -573
  283. package/docs/skill-candidates/v0.0.11/shadcn/agents/openai.yaml +3 -3
  284. package/docs/skill-candidates/v0.0.11/sql-postgresql-engineering/SKILL.md +34 -34
  285. package/docs/skill-candidates/v0.0.11/terminal-shell-ops/SKILL.md +34 -34
  286. package/docs/skill-candidates/v0.0.11/typescript-expert/SKILL.md +429 -429
  287. package/docs/skill-candidates/v0.0.11/typescript-expert/references/tsconfig-strict.json +91 -91
  288. package/docs/skill-candidates/v0.0.11/typescript-expert/references/typescript-cheatsheet.md +383 -383
  289. package/docs/skill-candidates/v0.0.11/typescript-expert/references/utility-types.ts +335 -335
  290. package/docs/skill-candidates/v0.0.11/typescript-expert/scripts/ts_diagnostic.py +203 -203
  291. package/docs/skill-candidates/v0.0.11/ui-component-primitives/SKILL.md +34 -34
  292. package/docs/skill-candidates/v0.0.11/web-mobile-design-systems/SKILL.md +34 -34
  293. package/docs/skill-candidates/v0.0.11/windows-linux-platform-ops/SKILL.md +34 -34
  294. package/docs/skill-candidates/v0.0.12/context-compression-handoff/SKILL.md +47 -0
  295. package/docs/skill-candidates/v0.0.12/csharp-senior-master-engineering/SKILL.md +32 -0
  296. package/docs/skill-candidates/v0.0.12/css-senior-master-engineering/SKILL.md +32 -0
  297. package/docs/skill-candidates/v0.0.12/go-senior-master-engineering/SKILL.md +32 -0
  298. package/docs/skill-candidates/v0.0.12/html-senior-master-engineering/SKILL.md +32 -0
  299. package/docs/skill-candidates/v0.0.12/javascript-senior-master-engineering/SKILL.md +32 -0
  300. package/docs/skill-candidates/v0.0.12/json-senior-master-engineering/SKILL.md +32 -0
  301. package/docs/skill-candidates/v0.0.12/prompt-budget-gate/SKILL.md +46 -0
  302. package/docs/skill-candidates/v0.0.12/python-senior-master-engineering/SKILL.md +32 -0
  303. package/docs/skill-candidates/v0.0.12/react-senior-master-engineering/SKILL.md +32 -0
  304. package/docs/skill-candidates/v0.0.12/ruby-senior-master-engineering/SKILL.md +32 -0
  305. package/docs/skill-candidates/v0.0.12/senior-master-code-optimizer/SKILL.md +48 -0
  306. package/docs/skill-candidates/v0.0.12/sql-senior-master-engineering/SKILL.md +31 -0
  307. package/docs/skill-candidates/v0.0.12/token-economy-orchestrator/SKILL.md +38 -0
  308. package/docs/skill-candidates/v0.0.12/typescript-senior-master-engineering/SKILL.md +35 -0
  309. package/docs/skill-candidates/v0.0.9/ai-ethics-human-dignity/SKILL.md +32 -32
  310. package/docs/skill-candidates/v0.0.9/broad-domain-router/SKILL.md +41 -41
  311. package/docs/skill-candidates/v0.0.9/catholic-moral-discernment/SKILL.md +31 -31
  312. package/docs/skill-candidates/v0.0.9/engineering-systems-master/SKILL.md +31 -31
  313. package/docs/skill-candidates/v0.0.9/language-quality-pt-en-fr/SKILL.md +28 -28
  314. package/docs/skill-candidates/v0.0.9/math-science-reasoning/SKILL.md +29 -29
  315. package/docs/skill-candidates/v0.0.9/philosophy-sociology-discernment/SKILL.md +28 -28
  316. package/docs/skill-candidates/v0.0.9/professional-boundary-triage/SKILL.md +40 -40
  317. package/docs/skill-candidates/v0.0.9/release-ethics-gate/SKILL.md +32 -32
  318. package/docs/skill-candidates/v0.0.9/source-authority-reviewer/SKILL.md +31 -31
  319. package/examples/client-configs/claude-code.commands.md +21 -17
  320. package/examples/client-configs/claude-code.project.mcp.json +18 -18
  321. package/examples/client-configs/claude-desktop.macos.json +18 -18
  322. package/examples/client-configs/claude-desktop.windows.json +20 -20
  323. package/examples/client-configs/codex.windows.toml +11 -11
  324. package/examples/client-configs/gemini-code-assist.intellij.mcp.json +18 -18
  325. package/examples/client-configs/gemini.linux.settings.json +21 -21
  326. package/examples/client-configs/gemini.windows.settings.json +23 -23
  327. package/examples/client-configs/generic-stdio.json +16 -16
  328. package/manifests/channels/beta.json +26 -26
  329. package/manifests/channels/stable.json +27 -27
  330. package/network/approved-skills.json +54 -54
  331. package/network/unapproved-skill-candidates.json +110 -110
  332. package/package.json +87 -78
  333. package/scripts/configure-private-registry.mjs +208 -208
  334. package/scripts/lib/private-registry.mjs +97 -97
  335. package/scripts/render-menu-evidence.mjs +130 -0
  336. package/scripts/verify-menu-actions.mjs +117 -115
  337. package/sources.json +11 -11
@@ -1,585 +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()
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()