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