plugin-scanner 2.0.1__tar.gz → 2.0.2__tar.gz

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 (223) hide show
  1. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/PKG-INFO +62 -5
  2. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/README.md +60 -3
  3. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docs/guard/architecture.md +11 -2
  4. plugin_scanner-2.0.2/docs/guard/get-started.md +82 -0
  5. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docs/guard/testing-matrix.md +10 -0
  6. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/pyproject.toml +12 -2
  7. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/cli.py +5 -1
  8. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/adapters/base.py +7 -4
  9. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/adapters/claude_code.py +36 -5
  10. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/cli/commands.py +77 -10
  11. plugin_scanner-2.0.2/src/codex_plugin_scanner/guard/cli/product.py +189 -0
  12. plugin_scanner-2.0.2/src/codex_plugin_scanner/guard/cli/prompt.py +342 -0
  13. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/cli/render.py +74 -0
  14. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/consumer/service.py +8 -1
  15. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/models.py +2 -1
  16. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/runtime/runner.py +11 -1
  17. plugin_scanner-2.0.2/src/codex_plugin_scanner/guard/shims.py +95 -0
  18. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/store.py +56 -9
  19. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/version.py +1 -1
  20. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_guard_cli.py +38 -4
  21. plugin_scanner-2.0.2/tests/test_guard_launch_env.py +78 -0
  22. plugin_scanner-2.0.2/tests/test_guard_product_flow.py +272 -0
  23. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_guard_runtime.py +100 -0
  24. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.clusterfuzzlite/Dockerfile +0 -0
  25. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.clusterfuzzlite/build.sh +0 -0
  26. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.clusterfuzzlite/project.yaml +0 -0
  27. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  28. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.dockerignore +0 -0
  29. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/CODEOWNERS +0 -0
  30. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/dependabot.yml +0 -0
  31. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/workflows/ci.yml +0 -0
  32. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/workflows/codeql.yml +0 -0
  33. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/workflows/e2e-test.yml +0 -0
  34. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/workflows/fuzz.yml +0 -0
  35. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/workflows/publish-action-repo.yml +0 -0
  36. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/workflows/publish.yml +0 -0
  37. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.github/workflows/scorecard.yml +0 -0
  38. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.gitignore +0 -0
  39. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/.pre-commit-hooks.yaml +0 -0
  40. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/CONTRIBUTING.md +0 -0
  41. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/Dockerfile +0 -0
  42. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/LICENSE +0 -0
  43. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/SECURITY.md +0 -0
  44. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/action/README.legacy.md +0 -0
  45. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/action/README.md +0 -0
  46. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/action/action.yml +0 -0
  47. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/action/cisco-version.txt +0 -0
  48. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/action/pypi-attestations-version.txt +0 -0
  49. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/action/scanner-version.txt +0 -0
  50. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docker-requirements.txt +0 -0
  51. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docs/guard/harness-support.md +0 -0
  52. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docs/guard/local-vs-cloud.md +0 -0
  53. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docs/guard/repo-boundaries.md +0 -0
  54. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docs/trust/mcp-trust-draft.md +0 -0
  55. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docs/trust/plugin-trust-draft.md +0 -0
  56. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/docs/trust/skill-trust-local.md +0 -0
  57. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/fuzzers/manifest_fuzzer.py +0 -0
  58. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/schemas/plugin-quality.v1.json +0 -0
  59. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/schemas/scan-result.v1.json +0 -0
  60. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/schemas/verify-result.v1.json +0 -0
  61. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/__init__.py +0 -0
  62. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/action_runner.py +0 -0
  63. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  64. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  65. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/claude.py +0 -0
  66. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  67. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  68. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  69. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  70. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  71. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  72. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  73. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  74. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/security.py +0 -0
  75. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  76. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/config.py +0 -0
  77. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  78. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  79. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  80. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  81. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  82. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  83. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  84. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  85. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  86. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  87. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  88. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  89. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  90. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  91. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  92. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  93. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/config.py +0 -0
  94. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  95. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  96. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  97. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  98. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  99. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  100. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  101. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  102. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  103. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  104. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  105. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  106. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  107. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  108. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  109. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  110. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  111. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/models.py +0 -0
  112. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/path_support.py +0 -0
  113. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/policy.py +0 -0
  114. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  115. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/repo_detect.py +0 -0
  116. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/reporting.py +0 -0
  117. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  118. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/rules/registry.py +0 -0
  119. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/rules/specs.py +0 -0
  120. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/scanner.py +0 -0
  121. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/submission.py +0 -0
  122. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/suppressions.py +0 -0
  123. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  124. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  125. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  126. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/trust_models.py +0 -0
  127. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  128. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  129. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  130. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/trust_specs.py +0 -0
  131. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/src/codex_plugin_scanner/verification.py +0 -0
  132. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/__init__.py +0 -0
  133. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/__init__.py +0 -0
  134. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  135. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  136. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/bad-plugin/secrets.js +0 -0
  137. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  138. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  139. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/claude-plugin-good/README.md +0 -0
  140. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  141. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  142. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  143. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/code-quality-bad/evil.js +0 -0
  144. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/code-quality-bad/inject.js +0 -0
  145. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  146. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  147. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/gemini-extension-good/README.md +0 -0
  148. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  149. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  150. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  151. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  152. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/.codexignore +0 -0
  153. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/LICENSE +0 -0
  154. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/README.md +0 -0
  155. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  156. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  157. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  158. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  159. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  160. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  161. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  162. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  163. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/mit-license/LICENSE +0 -0
  164. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  165. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  166. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  167. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  168. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  169. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  170. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  171. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  172. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  173. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  174. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  175. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  176. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  177. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  178. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  179. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  180. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  181. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  182. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/opencode-good/LICENSE +0 -0
  183. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/opencode-good/README.md +0 -0
  184. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  185. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  186. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  187. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  188. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  189. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  190. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  191. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  192. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test-trust-scoring.py +0 -0
  193. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test-trust-specs.py +0 -0
  194. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_action_bundle.py +0 -0
  195. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_action_runner.py +0 -0
  196. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_best_practices.py +0 -0
  197. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_cli.py +0 -0
  198. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_code_quality.py +0 -0
  199. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_config.py +0 -0
  200. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_coverage_remaining.py +0 -0
  201. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_ecosystems.py +0 -0
  202. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_edge_cases.py +0 -0
  203. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_final_coverage.py +0 -0
  204. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_integration.py +0 -0
  205. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_lint_fixes.py +0 -0
  206. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_live_cisco_smoke.py +0 -0
  207. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_manifest.py +0 -0
  208. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_marketplace.py +0 -0
  209. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_operational_security.py +0 -0
  210. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_policy.py +0 -0
  211. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_quality_artifact.py +0 -0
  212. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_rule_registry.py +0 -0
  213. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_scanner.py +0 -0
  214. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_schema_contracts.py +0 -0
  215. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_security.py +0 -0
  216. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_security_ops.py +0 -0
  217. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_skill_security.py +0 -0
  218. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_submission.py +0 -0
  219. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_trust_scoring.py +0 -0
  220. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_trust_specs.py +0 -0
  221. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_verification.py +0 -0
  222. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/tests/test_versioning.py +0 -0
  223. {plugin_scanner-2.0.1 → plugin_scanner-2.0.2}/uv.lock +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.1
4
- Summary: Security, operational-security, and publishability scanner for Codex, Claude, Gemini, and OpenCode plugin ecosystems.
3
+ Version: 2.0.2
4
+ Summary: Local Guard runtime plus security and publishability scanning for Codex, Claude, Cursor, Gemini, and OpenCode.
5
5
  Project-URL: Homepage, https://github.com/hashgraph-online/ai-plugin-scanner
6
6
  Project-URL: Repository, https://github.com/hashgraph-online/ai-plugin-scanner
7
7
  Project-URL: Issues, https://github.com/hashgraph-online/ai-plugin-scanner/issues
@@ -47,10 +47,47 @@ Description-Content-Type: text/markdown
47
47
  [![GitHub Stars](https://img.shields.io/github/stars/hashgraph-online/ai-plugin-scanner?style=social)](https://github.com/hashgraph-online/ai-plugin-scanner/stargazers)
48
48
  [![Lint: ruff](https://img.shields.io/badge/lint-ruff-D7FF64.svg)](https://github.com/astral-sh/ruff)
49
49
 
50
- | ![Hashgraph Online Logo](https://hol.org/brand/Logo_Whole_Dark.png) | **The default CI gate for AI agent plugin ecosystems**. Lint locally, verify in CI, and ship publish-ready bundles for manifests, skills, MCP, and marketplace metadata across Codex, Claude, Gemini, and OpenCode.<br><br>Use this after scaffolding and before publishing, review, or distribution.<br><br>[PyPI Package (`plugin-scanner`)](https://pypi.org/project/plugin-scanner/)<br>[Legacy Namespace (`codex-plugin-scanner`)](https://pypi.org/project/codex-plugin-scanner/)<br>[HOL Plugin Registry](https://hol.org/registry/plugins)<br>[HOL GitHub Organization](https://github.com/hashgraph-online)<br>[Report an Issue](https://github.com/hashgraph-online/ai-plugin-scanner/issues) |
50
+ | ![Hashgraph Online Logo](https://hol.org/brand/Logo_Whole_Dark.png) | **HOL Guard for local harness protection, plus the scanner CI gate for plugin ecosystems**. Protect Codex, Claude Code, Cursor, Gemini, and OpenCode before local tools run, then lint locally, verify in CI, and ship publish-ready bundles for manifests, skills, MCP, and marketplace metadata.<br><br>Use Guard when you want a local safety loop. Use the scanner when you want publishing and CI confidence.<br><br>[PyPI Package (`plugin-scanner`)](https://pypi.org/project/plugin-scanner/)<br>[Legacy Namespace (`codex-plugin-scanner`)](https://pypi.org/project/codex-plugin-scanner/)<br>[HOL Plugin Registry](https://hol.org/registry/plugins)<br>[HOL GitHub Organization](https://github.com/hashgraph-online)<br>[Report an Issue](https://github.com/hashgraph-online/ai-plugin-scanner/issues) |
51
51
  | :--- | :--- |
52
52
 
53
- ## Start In 30 Seconds
53
+ ## Guard Start In 60 Seconds
54
+
55
+ ```bash
56
+ # See what Guard can protect on this machine
57
+ pipx run plugin-guard guard start
58
+
59
+ # Install Guard in front of Codex
60
+ pipx run plugin-guard guard install codex
61
+
62
+ # Review the current tool state before launch
63
+ pipx run plugin-guard guard run codex
64
+
65
+ # Inspect local receipts later
66
+ pipx run plugin-guard guard receipts
67
+ ```
68
+
69
+ Guard is local-first:
70
+
71
+ 1. detect your harnesses
72
+ 2. install a Guard launcher
73
+ 3. run the harness through Guard
74
+ 4. approve or block changes
75
+ 5. inspect receipts locally
76
+ 6. connect sync only if you want shared history later
77
+
78
+ Guard commands that matter most:
79
+
80
+ - `plugin-scanner guard start` for the first-run path
81
+ - `plugin-scanner guard status` for the current protection state
82
+ - `plugin-scanner guard install <harness>` to create a local Guard launcher
83
+ - `plugin-scanner guard run <harness> --dry-run` to record the current state before launch
84
+ - `plugin-scanner guard run <harness>` to review and approve changed tools before launch
85
+ - `plugin-scanner guard diff <harness>` when Guard says something changed
86
+ - `plugin-scanner guard receipts` for local history
87
+
88
+ See [docs/guard/get-started.md](docs/guard/get-started.md) for the full local flow.
89
+
90
+ ## Scanner Start In 30 Seconds
54
91
 
55
92
  ```bash
56
93
  # Local preflight
@@ -70,7 +107,27 @@ pipx run plugin-scanner verify .
70
107
 
71
108
  If your repository uses a Codex marketplace root like `.agents/plugins/marketplace.json`, keep `plugin_dir: "."`. The scanner will discover local `./plugins/...` entries automatically, scan each local plugin manifest, and skip remote marketplace entries instead of treating the repo root as a single plugin.
72
109
 
73
- ## Use After `$plugin-creator`
110
+ ## Two Product Modes
111
+
112
+ ### HOL Guard
113
+
114
+ Use Guard when the problem is local runtime safety inside a harness:
115
+
116
+ - a new MCP server showed up in local config
117
+ - an existing tool changed after you trusted it
118
+ - you want receipts for what was approved or blocked
119
+ - you want to review changes before Codex, Claude Code, Cursor, Gemini, or OpenCode launches
120
+
121
+ ### Scanner CI Gate
122
+
123
+ Use the scanner when the problem is authoring, CI, and publish readiness:
124
+
125
+ - lint manifests and metadata
126
+ - verify runtime and install surfaces
127
+ - block PRs with policy gates
128
+ - emit artifacts before submission or publishing
129
+
130
+ ## Use Scanner After `$plugin-creator`
74
131
 
75
132
  `plugin-scanner` is designed as the quality gate between plugin creation and distribution:
76
133
 
@@ -12,10 +12,47 @@
12
12
  [![GitHub Stars](https://img.shields.io/github/stars/hashgraph-online/ai-plugin-scanner?style=social)](https://github.com/hashgraph-online/ai-plugin-scanner/stargazers)
13
13
  [![Lint: ruff](https://img.shields.io/badge/lint-ruff-D7FF64.svg)](https://github.com/astral-sh/ruff)
14
14
 
15
- | ![Hashgraph Online Logo](https://hol.org/brand/Logo_Whole_Dark.png) | **The default CI gate for AI agent plugin ecosystems**. Lint locally, verify in CI, and ship publish-ready bundles for manifests, skills, MCP, and marketplace metadata across Codex, Claude, Gemini, and OpenCode.<br><br>Use this after scaffolding and before publishing, review, or distribution.<br><br>[PyPI Package (`plugin-scanner`)](https://pypi.org/project/plugin-scanner/)<br>[Legacy Namespace (`codex-plugin-scanner`)](https://pypi.org/project/codex-plugin-scanner/)<br>[HOL Plugin Registry](https://hol.org/registry/plugins)<br>[HOL GitHub Organization](https://github.com/hashgraph-online)<br>[Report an Issue](https://github.com/hashgraph-online/ai-plugin-scanner/issues) |
15
+ | ![Hashgraph Online Logo](https://hol.org/brand/Logo_Whole_Dark.png) | **HOL Guard for local harness protection, plus the scanner CI gate for plugin ecosystems**. Protect Codex, Claude Code, Cursor, Gemini, and OpenCode before local tools run, then lint locally, verify in CI, and ship publish-ready bundles for manifests, skills, MCP, and marketplace metadata.<br><br>Use Guard when you want a local safety loop. Use the scanner when you want publishing and CI confidence.<br><br>[PyPI Package (`plugin-scanner`)](https://pypi.org/project/plugin-scanner/)<br>[Legacy Namespace (`codex-plugin-scanner`)](https://pypi.org/project/codex-plugin-scanner/)<br>[HOL Plugin Registry](https://hol.org/registry/plugins)<br>[HOL GitHub Organization](https://github.com/hashgraph-online)<br>[Report an Issue](https://github.com/hashgraph-online/ai-plugin-scanner/issues) |
16
16
  | :--- | :--- |
17
17
 
18
- ## Start In 30 Seconds
18
+ ## Guard Start In 60 Seconds
19
+
20
+ ```bash
21
+ # See what Guard can protect on this machine
22
+ pipx run plugin-guard guard start
23
+
24
+ # Install Guard in front of Codex
25
+ pipx run plugin-guard guard install codex
26
+
27
+ # Review the current tool state before launch
28
+ pipx run plugin-guard guard run codex
29
+
30
+ # Inspect local receipts later
31
+ pipx run plugin-guard guard receipts
32
+ ```
33
+
34
+ Guard is local-first:
35
+
36
+ 1. detect your harnesses
37
+ 2. install a Guard launcher
38
+ 3. run the harness through Guard
39
+ 4. approve or block changes
40
+ 5. inspect receipts locally
41
+ 6. connect sync only if you want shared history later
42
+
43
+ Guard commands that matter most:
44
+
45
+ - `plugin-scanner guard start` for the first-run path
46
+ - `plugin-scanner guard status` for the current protection state
47
+ - `plugin-scanner guard install <harness>` to create a local Guard launcher
48
+ - `plugin-scanner guard run <harness> --dry-run` to record the current state before launch
49
+ - `plugin-scanner guard run <harness>` to review and approve changed tools before launch
50
+ - `plugin-scanner guard diff <harness>` when Guard says something changed
51
+ - `plugin-scanner guard receipts` for local history
52
+
53
+ See [docs/guard/get-started.md](docs/guard/get-started.md) for the full local flow.
54
+
55
+ ## Scanner Start In 30 Seconds
19
56
 
20
57
  ```bash
21
58
  # Local preflight
@@ -35,7 +72,27 @@ pipx run plugin-scanner verify .
35
72
 
36
73
  If your repository uses a Codex marketplace root like `.agents/plugins/marketplace.json`, keep `plugin_dir: "."`. The scanner will discover local `./plugins/...` entries automatically, scan each local plugin manifest, and skip remote marketplace entries instead of treating the repo root as a single plugin.
37
74
 
38
- ## Use After `$plugin-creator`
75
+ ## Two Product Modes
76
+
77
+ ### HOL Guard
78
+
79
+ Use Guard when the problem is local runtime safety inside a harness:
80
+
81
+ - a new MCP server showed up in local config
82
+ - an existing tool changed after you trusted it
83
+ - you want receipts for what was approved or blocked
84
+ - you want to review changes before Codex, Claude Code, Cursor, Gemini, or OpenCode launches
85
+
86
+ ### Scanner CI Gate
87
+
88
+ Use the scanner when the problem is authoring, CI, and publish readiness:
89
+
90
+ - lint manifests and metadata
91
+ - verify runtime and install surfaces
92
+ - block PRs with policy gates
93
+ - emit artifacts before submission or publishing
94
+
95
+ ## Use Scanner After `$plugin-creator`
39
96
 
40
97
  `plugin-scanner` is designed as the quality gate between plugin creation and distribution:
41
98
 
@@ -1,10 +1,11 @@
1
1
  # Guard Architecture
2
2
 
3
- Guard lives inside `codex_plugin_scanner` and uses the existing scan engine as its trust and evidence core.
3
+ Guard lives inside `codex_plugin_scanner` and is the local product surface for harness protection. The existing scan engine remains the trust and evidence core, but the user workflow starts with local harness installs and launch interception rather than CI.
4
4
 
5
5
  The runtime is split into:
6
6
 
7
7
  - `guard/adapters`: harness discovery for Codex, Claude Code, Cursor, Gemini, and OpenCode
8
+ - `guard/shims`: local launcher shims that route harness launches through Guard
8
9
  - `guard/consumer`: orchestration for detection, policy evaluation, and consumer-mode scan output
9
10
  - `guard/policy`: local action resolution for allow, review, warn, and block decisions
10
11
  - `guard/receipts`: receipt creation for first use and changed-artifact events
@@ -21,4 +22,12 @@ Guard evaluates local artifacts in this order:
21
22
  5. Record a receipt and optional diff
22
23
  6. Launch the harness only if the effective action is not `block`
23
24
 
24
- Wrapper mode is the default implementation strategy in this phase. Config mutation is limited to the Claude Code hook helper, where Guard can add and remove its own hook entry in workspace-local settings.
25
+ The local product loop is:
26
+
27
+ 1. `guard start` detects supported harnesses and suggests the next step
28
+ 2. `guard install <harness>` creates a local launcher shim
29
+ 3. `guard run <harness>` evaluates changes before the harness launches
30
+ 4. `guard receipts` and `guard status` let users inspect local decisions
31
+ 5. `guard login` and `guard sync` stay optional
32
+
33
+ Wrapper mode is still the core execution strategy in this phase. Config mutation is limited to the Claude Code hook helper, where Guard can add and remove its own hook entry in workspace-local settings.
@@ -0,0 +1,82 @@
1
+ # Guard Get Started
2
+
3
+ Guard is the local product inside `plugin-scanner`.
4
+ If you want the shortest entrypoint, install and run the dedicated `plugin-guard` console script.
5
+
6
+ Use it when you want to protect a harness before local MCP servers, skills, hooks, or plugin surfaces run.
7
+
8
+ ## The local loop
9
+
10
+ 1. Detect your harnesses:
11
+
12
+ ```bash
13
+ plugin-guard guard start
14
+ ```
15
+
16
+ 2. Install Guard in front of the harness you use most:
17
+
18
+ ```bash
19
+ plugin-guard guard install codex
20
+ ```
21
+
22
+ 3. Run one dry pass so Guard records the current state:
23
+
24
+ ```bash
25
+ plugin-guard guard run codex --dry-run
26
+ ```
27
+
28
+ 4. Launch through Guard after that. Guard will prompt you if a tool is new or changed:
29
+
30
+ ```bash
31
+ plugin-guard guard run codex
32
+ ```
33
+
34
+ 5. Review changes when Guard blocks or asks for another look:
35
+
36
+ ```bash
37
+ plugin-scanner guard diff codex
38
+ plugin-scanner guard allow codex --scope artifact --artifact-id codex:project:workspace_skill
39
+ plugin-scanner guard deny codex --scope artifact --artifact-id codex:project:workspace_skill
40
+ ```
41
+
42
+ 6. Inspect receipts:
43
+
44
+ ```bash
45
+ plugin-guard guard receipts
46
+ plugin-guard guard status
47
+ ```
48
+
49
+ 7. Connect sync only if you want shared history later:
50
+
51
+ ```bash
52
+ plugin-scanner guard login --sync-url <url> --token <token>
53
+ plugin-scanner guard sync
54
+ ```
55
+
56
+ ## What `install` does
57
+
58
+ `guard install <harness>` creates a local launcher shim under Guard’s home directory:
59
+
60
+ - macOS/Linux: `~/.config/.ai-plugin-scanner-guard/bin/guard-<harness>`
61
+ - Windows: `~/.config/.ai-plugin-scanner-guard/bin/guard-<harness>.cmd`
62
+
63
+ Claude Code also gets Guard hook entries in `.claude/settings.local.json` when you install from a workspace.
64
+
65
+ ## First-party canaries
66
+
67
+ Use these local repos to prove Guard against real first-party surfaces:
68
+
69
+ - `hashnet-mcp-js` for a real MCP server harness target
70
+ - `registry-broker-skills` for a real skills registry fixture during scan and trust checks
71
+
72
+ Suggested local validation:
73
+
74
+ ```bash
75
+ plugin-scanner guard detect codex --json
76
+ plugin-scanner guard install codex
77
+ plugin-scanner guard status
78
+ plugin-scanner guard run codex --dry-run
79
+ plugin-scanner guard receipts
80
+ ```
81
+
82
+ For a real Codex canary, point `~/.codex/config.toml` or `<workspace>/.codex/config.toml` at a local `hashnet-mcp` command, then repeat the Guard loop above.
@@ -3,20 +3,30 @@
3
3
  Automated coverage in this phase includes:
4
4
 
5
5
  - Guard CLI behavior tests for detect, scan, run, diff, receipts, install, uninstall, login, and sync
6
+ - Guard product-flow tests for `guard start`, `guard status`, and launcher shim creation
6
7
  - SQLite persistence through real command execution in temporary homes and workspaces
7
8
  - consumer-mode JSON contract generation against scanner fixtures
8
9
  - local HTTP sync against a live in-process server instead of mocked transport
9
10
 
10
11
  Manual verification should include:
11
12
 
13
+ - `guard start`
14
+ - `guard status`
12
15
  - `guard detect codex --json`
13
16
  - `guard detect cursor --json`
14
17
  - `guard detect gemini --json`
15
18
  - `guard detect opencode --json`
19
+ - `guard install codex`
16
20
  - `guard run codex --dry-run --default-action allow --json`
21
+ - `guard receipts`
17
22
  - `codex mcp list`
18
23
  - `cursor-agent mcp list`
19
24
  - `gemini --help`
20
25
  - `opencode --help`
21
26
 
27
+ First-party canaries for local manual validation:
28
+
29
+ - a local `hashnet-mcp-js` checkout wired into Codex, Cursor, or Claude Code config
30
+ - a local `registry-broker-skills` checkout for scanner fixtures and trust review
31
+
22
32
  Claude Code smoke tests remain conditional on the local `claude` binary being available.
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "plugin-scanner"
7
- version = "2.0.1"
8
- description = "Security, operational-security, and publishability scanner for Codex, Claude, Gemini, and OpenCode plugin ecosystems."
7
+ version = "2.0.2"
8
+ description = "Local Guard runtime plus security and publishability scanning for Codex, Claude, Cursor, Gemini, and OpenCode."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
11
11
  requires-python = ">=3.10"
@@ -50,6 +50,7 @@ publish = [
50
50
 
51
51
  [project.scripts]
52
52
  plugin-scanner = "codex_plugin_scanner.cli:main"
53
+ plugin-guard = "codex_plugin_scanner.cli:main"
53
54
  codex-plugin-scanner = "codex_plugin_scanner.cli:main"
54
55
  plugin-ecosystem-scanner = "codex_plugin_scanner.cli:main"
55
56
 
@@ -66,6 +67,15 @@ extend-exclude = ["tests/test-trust-scoring.py", "tests/test-trust-specs.py"]
66
67
  [tool.hatch.build.targets.wheel]
67
68
  packages = ["src/codex_plugin_scanner"]
68
69
 
70
+ [tool.hatch.build]
71
+ exclude = [
72
+ ".guard-e2e/**",
73
+ ".guard-e2e-prod/**",
74
+ ".guard-e2e-prod2/**",
75
+ ".guard-prod-venv/**",
76
+ ".guard-prod-venv-fresh/**",
77
+ ]
78
+
69
79
  [tool.ruff.lint]
70
80
  select = ["E", "F", "W", "I", "N", "UP", "B", "A", "SIM", "RUF"]
71
81
 
@@ -137,7 +137,11 @@ def _add_common_policy_args(parser: argparse.ArgumentParser) -> None:
137
137
 
138
138
 
139
139
  def _build_parser() -> argparse.ArgumentParser:
140
- parser = argparse.ArgumentParser(prog="codex-plugin-scanner", description="Scan and lint Codex plugin directories")
140
+ program_name = Path(sys.argv[0]).name or "plugin-scanner"
141
+ parser = argparse.ArgumentParser(
142
+ prog=program_name,
143
+ description="Run HOL Guard locally or scan plugin ecosystems for CI and publish readiness.",
144
+ )
141
145
  parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
142
146
  parser.add_argument("--list-ecosystems", action="store_true", help="List supported plugin ecosystems and exit")
143
147
  subparsers = parser.add_subparsers(dest="command")
@@ -9,6 +9,7 @@ from dataclasses import dataclass
9
9
  from pathlib import Path
10
10
 
11
11
  from ..models import GuardArtifact, HarnessDetection
12
+ from ..shims import install_guard_shim, remove_guard_shim
12
13
 
13
14
 
14
15
  @dataclass(frozen=True, slots=True)
@@ -77,19 +78,21 @@ class HarnessAdapter:
77
78
  raise NotImplementedError
78
79
 
79
80
  def install(self, context: HarnessContext) -> dict[str, object]:
81
+ shim_manifest = install_guard_shim(self.harness, context)
80
82
  return {
81
83
  "harness": self.harness,
82
84
  "active": True,
83
- "config_path": None,
84
- "notes": ["No harness config was changed. Guard will evaluate this harness in wrapper mode."],
85
+ "config_path": shim_manifest["shim_path"],
86
+ **shim_manifest,
85
87
  }
86
88
 
87
89
  def uninstall(self, context: HarnessContext) -> dict[str, object]:
90
+ shim_manifest = remove_guard_shim(self.harness, context)
88
91
  return {
89
92
  "harness": self.harness,
90
93
  "active": False,
91
- "config_path": None,
92
- "notes": ["No harness config was changed. Guard wrapper mode is now disabled."],
94
+ "config_path": shim_manifest["shim_path"],
95
+ **shim_manifest,
93
96
  }
94
97
 
95
98
  def launch_command(self, context: HarnessContext, passthrough_args: list[str]) -> list[str]:
@@ -8,6 +8,7 @@ import sys
8
8
  from pathlib import Path
9
9
 
10
10
  from ..models import GuardArtifact, HarnessDetection
11
+ from ..shims import install_guard_shim, remove_guard_shim
11
12
  from .base import HarnessAdapter, HarnessContext, _command_available, _json_payload
12
13
 
13
14
 
@@ -119,14 +120,30 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
119
120
 
120
121
  @staticmethod
121
122
  def _hook_command(context: HarnessContext) -> str:
122
- command = [sys.executable, "-m", "codex_plugin_scanner.cli", "guard", "hook"]
123
+ command = [
124
+ sys.executable,
125
+ "-m",
126
+ "codex_plugin_scanner.cli",
127
+ "guard",
128
+ "hook",
129
+ "--guard-home",
130
+ str(context.guard_home),
131
+ ]
132
+ if context.home_dir.resolve() != Path.home().resolve():
133
+ command.extend(["--home", str(context.home_dir)])
123
134
  if context.workspace_dir is not None:
124
135
  command.extend(["--workspace", str(context.workspace_dir)])
125
136
  return subprocess.list2cmdline(command)
126
137
 
127
138
  def install(self, context: HarnessContext) -> dict[str, object]:
139
+ shim_manifest = install_guard_shim(self.harness, context)
128
140
  if context.workspace_dir is None:
129
- return super().install(context)
141
+ return {
142
+ "harness": self.harness,
143
+ "active": True,
144
+ "config_path": shim_manifest["shim_path"],
145
+ **shim_manifest,
146
+ }
130
147
  settings_path = context.workspace_dir / ".claude" / "settings.local.json"
131
148
  payload = _json_payload(settings_path)
132
149
  hook_command = self._hook_command(context)
@@ -144,12 +161,22 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
144
161
  "harness": self.harness,
145
162
  "active": True,
146
163
  "config_path": str(settings_path),
147
- "notes": ["Guard hook entries added to .claude/settings.local.json"],
164
+ **shim_manifest,
165
+ "notes": [
166
+ "Guard hook entries added to .claude/settings.local.json",
167
+ *[str(note) for note in shim_manifest.get("notes", [])],
168
+ ],
148
169
  }
149
170
 
150
171
  def uninstall(self, context: HarnessContext) -> dict[str, object]:
172
+ shim_manifest = remove_guard_shim(self.harness, context)
151
173
  if context.workspace_dir is None:
152
- return super().uninstall(context)
174
+ return {
175
+ "harness": self.harness,
176
+ "active": False,
177
+ "config_path": shim_manifest["shim_path"],
178
+ **shim_manifest,
179
+ }
153
180
  settings_path = context.workspace_dir / ".claude" / "settings.local.json"
154
181
  payload = _json_payload(settings_path)
155
182
  hook_command = self._hook_command(context)
@@ -164,5 +191,9 @@ class ClaudeCodeHarnessAdapter(HarnessAdapter):
164
191
  "harness": self.harness,
165
192
  "active": False,
166
193
  "config_path": str(settings_path),
167
- "notes": ["Guard hook entries removed from .claude/settings.local.json"],
194
+ **shim_manifest,
195
+ "notes": [
196
+ "Guard hook entries removed from .claude/settings.local.json",
197
+ *[str(note) for note in shim_manifest.get("notes", [])],
198
+ ],
168
199
  }
@@ -16,6 +16,8 @@ from ..policy.engine import SAFE_CHANGED_HASH_ACTION, VALID_GUARD_ACTIONS
16
16
  from ..receipts import build_receipt
17
17
  from ..runtime import guard_run, sync_receipts
18
18
  from ..store import GuardStore
19
+ from .product import build_guard_start_payload, build_guard_status_payload
20
+ from .prompt import build_prompt_artifacts, resolve_interactive_decisions
19
21
  from .render import emit_guard_payload
20
22
 
21
23
 
@@ -26,6 +28,7 @@ def _now() -> str:
26
28
  def add_guard_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
27
29
  """Register the Guard command family."""
28
30
 
31
+ program_name = Path(sys.argv[0]).name or "plugin-scanner"
29
32
  guard_parser = subparsers.add_parser(
30
33
  "guard",
31
34
  help="Run local harness protection workflows",
@@ -35,19 +38,27 @@ def add_guard_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentPar
35
38
  ),
36
39
  epilog=(
37
40
  "Examples:\n"
38
- " codex-plugin-scanner guard detect\n"
39
- " codex-plugin-scanner guard doctor cursor\n"
40
- " codex-plugin-scanner guard run codex --dry-run\n"
41
- " codex-plugin-scanner guard install claude-code --workspace ."
41
+ f" {program_name} guard detect\n"
42
+ f" {program_name} guard doctor cursor\n"
43
+ f" {program_name} guard run codex --dry-run\n"
44
+ f" {program_name} guard install claude-code --workspace ."
42
45
  ),
43
46
  formatter_class=argparse.RawDescriptionHelpFormatter,
44
47
  )
45
48
  guard_subparsers = guard_parser.add_subparsers(
46
49
  dest="guard_command",
47
- metavar="{detect,install,uninstall,run,scan,diff,receipts,explain,allow,deny,doctor,login,sync}",
50
+ metavar="{start,status,detect,install,uninstall,run,scan,diff,receipts,explain,allow,deny,doctor,login,sync}",
48
51
  )
49
52
  guard_subparsers.required = True
50
53
 
54
+ start_parser = guard_subparsers.add_parser("start", help="Show the first Guard steps for a local harness")
55
+ _add_guard_common_args(start_parser)
56
+ start_parser.add_argument("--json", action="store_true")
57
+
58
+ status_parser = guard_subparsers.add_parser("status", help="Show current Guard protection status")
59
+ _add_guard_common_args(status_parser)
60
+ status_parser.add_argument("--json", action="store_true")
61
+
51
62
  detect_parser = guard_subparsers.add_parser("detect", help="Discover supported harnesses and local artifacts")
52
63
  detect_parser.add_argument("harness", nargs="?")
53
64
  _add_guard_common_args(detect_parser)
@@ -95,10 +106,11 @@ def add_guard_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentPar
95
106
  policy_parser.add_argument("--artifact-id")
96
107
  policy_parser.add_argument(
97
108
  "--scope",
98
- choices=("global", "harness", "workspace", "artifact"),
109
+ choices=("global", "harness", "workspace", "artifact", "publisher"),
99
110
  default="harness",
100
111
  )
101
112
  policy_parser.add_argument("--reason")
113
+ policy_parser.add_argument("--publisher")
102
114
  _add_guard_common_args(policy_parser)
103
115
  policy_parser.add_argument("--json", action="store_true")
104
116
  policy_parser.set_defaults(policy_action=action)
@@ -112,10 +124,12 @@ def add_guard_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentPar
112
124
  login_parser.add_argument("--sync-url", required=True)
113
125
  login_parser.add_argument("--token", required=True)
114
126
  login_parser.add_argument("--home")
127
+ login_parser.add_argument("--guard-home")
115
128
  login_parser.add_argument("--json", action="store_true")
116
129
 
117
130
  sync_parser = guard_subparsers.add_parser("sync", help="Sync receipts to the configured Guard endpoint")
118
131
  sync_parser.add_argument("--home")
132
+ sync_parser.add_argument("--guard-home")
119
133
  sync_parser.add_argument("--json", action="store_true")
120
134
 
121
135
  hook_parser = guard_subparsers.add_parser("hook", help=argparse.SUPPRESS)
@@ -133,6 +147,7 @@ def add_guard_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentPar
133
147
 
134
148
  def _add_guard_common_args(parser: argparse.ArgumentParser) -> None:
135
149
  parser.add_argument("--home")
150
+ parser.add_argument("--guard-home")
136
151
  parser.add_argument("--workspace")
137
152
 
138
153
 
@@ -144,16 +159,27 @@ def run_guard_command(args: argparse.Namespace) -> int:
144
159
  _emit("scan", payload, args.json or args.consumer_mode)
145
160
  return 0
146
161
 
147
- guard_home = resolve_guard_home(getattr(args, "home", None))
162
+ home_override = getattr(args, "home", None)
163
+ guard_home = resolve_guard_home(getattr(args, "guard_home", None) or home_override)
148
164
  workspace = Path(args.workspace).resolve() if getattr(args, "workspace", None) else None
149
165
  context = HarnessContext(
150
- home_dir=Path(getattr(args, "home", None)).resolve() if getattr(args, "home", None) else Path.home(),
166
+ home_dir=Path(home_override).resolve() if home_override else Path.home().resolve(),
151
167
  workspace_dir=workspace,
152
168
  guard_home=guard_home,
153
169
  )
154
170
  config = load_guard_config(guard_home, workspace=workspace)
155
171
  store = GuardStore(guard_home)
156
172
 
173
+ if args.guard_command == "start":
174
+ payload = build_guard_start_payload(context, store, config)
175
+ _emit("start", payload, getattr(args, "json", False))
176
+ return 0
177
+
178
+ if args.guard_command == "status":
179
+ payload = build_guard_status_payload(context, store, config)
180
+ _emit("status", payload, getattr(args, "json", False))
181
+ return 0
182
+
157
183
  if args.guard_command == "detect":
158
184
  detections = [detect_harness(args.harness, context)] if args.harness else detect_all(context)
159
185
  payload = {
@@ -182,6 +208,27 @@ def run_guard_command(args: argparse.Namespace) -> int:
182
208
  return 0
183
209
 
184
210
  if args.guard_command == "run":
211
+ interactive_resolver = None
212
+ if (
213
+ not getattr(args, "json", False)
214
+ and not bool(args.dry_run)
215
+ and config.mode == "prompt"
216
+ and sys.stdin.isatty()
217
+ ):
218
+
219
+ def interactive_resolver(detection, payload):
220
+ return resolve_interactive_decisions(
221
+ store=store,
222
+ evaluation=payload,
223
+ prompt_artifacts=build_prompt_artifacts(
224
+ harness=detection.harness,
225
+ artifacts=list(detection.artifacts),
226
+ evaluation_artifacts=[item for item in payload.get("artifacts", []) if isinstance(item, dict)],
227
+ ),
228
+ workspace=str(workspace) if workspace else None,
229
+ now=_now(),
230
+ )
231
+
185
232
  payload = guard_run(
186
233
  args.harness,
187
234
  context=context,
@@ -190,7 +237,18 @@ def run_guard_command(args: argparse.Namespace) -> int:
190
237
  dry_run=bool(args.dry_run),
191
238
  passthrough_args=list(args.passthrough_args),
192
239
  default_action=args.default_action,
240
+ interactive_resolver=interactive_resolver,
193
241
  )
242
+ if (
243
+ payload.get("blocked")
244
+ and config.mode == "prompt"
245
+ and not getattr(args, "json", False)
246
+ and not sys.stdin.isatty()
247
+ ):
248
+ payload["review_hint"] = (
249
+ f"Guard blocked {args.harness} because this shell is non-interactive. "
250
+ f"Run `plugin-guard guard diff {args.harness}` and allow or deny the changed artifacts first."
251
+ )
194
252
  _emit("run", payload, getattr(args, "json", False))
195
253
  if payload.get("blocked"):
196
254
  return 1
@@ -216,7 +274,7 @@ def run_guard_command(args: argparse.Namespace) -> int:
216
274
  return 0
217
275
 
218
276
  if args.guard_command in {"allow", "deny"}:
219
- _validate_policy_scope(args.scope, args.artifact_id, workspace)
277
+ _validate_policy_scope(args.scope, args.artifact_id, workspace, getattr(args, "publisher", None))
220
278
  payload = record_policy(
221
279
  store=store,
222
280
  harness=args.harness,
@@ -224,6 +282,7 @@ def run_guard_command(args: argparse.Namespace) -> int:
224
282
  scope=args.scope,
225
283
  artifact_id=args.artifact_id,
226
284
  workspace=str(workspace) if workspace else None,
285
+ publisher=getattr(args, "publisher", None),
227
286
  reason=args.reason,
228
287
  )
229
288
  _emit(args.guard_command, {"decision": payload}, getattr(args, "json", False))
@@ -349,10 +408,18 @@ def _string_list(value: object | None) -> list[str]:
349
408
  return [str(item) for item in value if isinstance(item, str) and item.strip()]
350
409
 
351
410
 
352
- def _validate_policy_scope(scope: str, artifact_id: str | None, workspace: Path | None) -> None:
411
+ def _validate_policy_scope(
412
+ scope: str,
413
+ artifact_id: str | None,
414
+ workspace: Path | None,
415
+ publisher: str | None,
416
+ ) -> None:
353
417
  if scope == "artifact" and not artifact_id:
354
418
  print("--artifact-id is required when --scope artifact", file=sys.stderr)
355
419
  raise SystemExit(2)
356
420
  if scope == "workspace" and workspace is None:
357
421
  print("--workspace is required when --scope workspace", file=sys.stderr)
358
422
  raise SystemExit(2)
423
+ if scope == "publisher" and not publisher:
424
+ print("--publisher is required when --scope publisher", file=sys.stderr)
425
+ raise SystemExit(2)