agentbundle 0.20.1__tar.gz → 0.22.0__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 (165) hide show
  1. {agentbundle-0.20.1 → agentbundle-0.22.0}/PKG-INFO +38 -1
  2. agentbundle-0.22.0/README-pypi.md +266 -0
  3. {agentbundle-0.20.1 → agentbundle-0.22.0}/README.md +35 -7
  4. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/catalogue.schema.json +16 -0
  5. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/install-marker.py +28 -35
  6. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/self_host.py +1 -1
  7. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_contract.py +4 -4
  8. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +3 -1
  9. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +2 -1
  10. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +3 -1
  11. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_self_host_check.py +5 -0
  12. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_shared_prefix_contract.py +5 -2
  13. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue.py +7 -9
  14. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/archive.py +41 -10
  15. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/build.py +9 -8
  16. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/config.py +76 -3
  17. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/defaults.py +17 -4
  18. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/diagnostics.py +3 -3
  19. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/lint.py +50 -49
  20. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/package.py +29 -19
  21. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/self_host.py +0 -1
  22. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/skill_spec_lint.py +2 -3
  23. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/verify.py +30 -12
  24. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/categories.py +3 -4
  25. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/cli.py +170 -28
  26. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/_common.py +7 -5
  27. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/_drop_warning.py +1 -2
  28. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/adapt.py +9 -4
  29. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/catalogue_build.py +1 -1
  30. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/catalogue_lint.py +1 -1
  31. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/catalogue_package.py +1 -1
  32. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/catalogue_self_host.py +4 -6
  33. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/catalogue_sync_defaults.py +2 -2
  34. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/catalogue_tooling_stub.py +1 -1
  35. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/catalogue_verify.py +1 -1
  36. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/docs.py +1 -1
  37. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/init_state.py +1 -1
  38. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/install.py +84 -65
  39. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/list_installed.py +35 -29
  40. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/list_packs.py +1 -1
  41. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/list_profiles.py +1 -1
  42. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/list_targets.py +0 -1
  43. agentbundle-0.22.0/agentbundle/commands/oplog_cmd.py +98 -0
  44. agentbundle-0.22.0/agentbundle/commands/pack_config_cmd.py +201 -0
  45. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/pack_evals.py +45 -17
  46. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/package_catalogue.py +1 -2
  47. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/reconcile.py +1 -1
  48. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/render.py +1 -2
  49. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/show.py +4 -4
  50. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/uninstall.py +5 -7
  51. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/upgrade.py +102 -103
  52. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/validate.py +1 -4
  53. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/config.py +165 -8
  54. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/https_catalogue.py +34 -38
  55. agentbundle-0.22.0/agentbundle/oplog.py +168 -0
  56. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/render.py +1 -2
  57. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/safety.py +85 -9
  58. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/scope.py +2 -3
  59. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/source_defaults.py +19 -22
  60. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/statelock.py +3 -3
  61. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/user_config.py +5 -11
  62. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/version.py +1 -1
  63. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle.egg-info/PKG-INFO +38 -1
  64. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle.egg-info/SOURCES.txt +4 -0
  65. {agentbundle-0.20.1 → agentbundle-0.22.0}/pyproject.toml +2 -2
  66. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/__init__.py +0 -0
  67. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/__main__.py +0 -0
  68. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/adapter.schema.json +0 -0
  69. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/adapter.toml +0 -0
  70. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/install-defaults.toml +0 -0
  71. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/pack.schema.json +0 -0
  72. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
  73. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/plugin-manifest.schema.json +0 -0
  74. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/_data/profile.schema.json +0 -0
  75. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/__init__.py +0 -0
  76. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/__main__.py +0 -0
  77. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapter_root_bins.py +0 -0
  78. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/__init__.py +0 -0
  79. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/claude_code.py +0 -0
  80. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/codex.py +0 -0
  81. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/copilot.py +0 -0
  82. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/cursor.py +0 -0
  83. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/gemini.py +0 -0
  84. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/kiro.py +0 -0
  85. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
  86. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
  87. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/contract.py +0 -0
  88. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/lint_packs.py +0 -0
  89. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/main.py +0 -0
  90. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/phase_order.py +0 -0
  91. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/__init__.py +0 -0
  92. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
  93. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
  94. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
  95. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/direct_directory.py +0 -0
  96. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
  97. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/hook_id.py +0 -0
  98. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
  99. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
  100. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/merge_json.py +0 -0
  101. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/projections/user_merge_json.py +0 -0
  102. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/recipes/composite-agents-md.toml +0 -0
  103. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/recipes/composite-marketplace.toml +0 -0
  104. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/recipes/marketplace.toml +0 -0
  105. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/recipes/per-pack-apm-package.toml +0 -0
  106. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/recipes/per-pack-claude-plugin.toml +0 -0
  107. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/recipes/per-pack-overlay.toml +0 -0
  108. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/recipes/self-host.toml +0 -0
  109. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/scope_rails.py +0 -0
  110. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/shared_libs.py +0 -0
  111. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/target_resolver.py +0 -0
  112. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/__init__.py +0 -0
  113. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
  114. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
  115. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
  116. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_cursor.py +0 -0
  117. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_gemini.py +0 -0
  118. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
  119. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
  120. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
  121. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +0 -0
  122. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
  123. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
  124. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
  125. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
  126. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
  127. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
  128. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
  129. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
  130. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
  131. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
  132. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
  133. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_pack_schema.py +0 -0
  134. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
  135. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
  136. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_pipeline.py +0 -0
  137. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
  138. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_projectable_subset.py +0 -0
  139. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
  140. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
  141. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_security.py +0 -0
  142. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_self_host_fixture_guard.py +0 -0
  143. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_self_host_recipe_config.py +0 -0
  144. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
  145. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
  146. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
  147. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
  148. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_validate.py +0 -0
  149. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/tests/test_writers_emit_lf.py +0 -0
  150. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/user_libs.py +0 -0
  151. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/build/validate.py +0 -0
  152. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/__init__.py +0 -0
  153. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/catalogue_tooling/results.py +0 -0
  154. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/__init__.py +0 -0
  155. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/config.py +0 -0
  156. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/diff.py +0 -0
  157. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/profile.py +0 -0
  158. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/commands/scaffold.py +0 -0
  159. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle/pack_inventory.py +0 -0
  160. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle.egg-info/dependency_links.txt +0 -0
  161. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle.egg-info/entry_points.txt +0 -0
  162. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle.egg-info/requires.txt +0 -0
  163. {agentbundle-0.20.1 → agentbundle-0.22.0}/agentbundle.egg-info/top_level.txt +0 -0
  164. {agentbundle-0.20.1 → agentbundle-0.22.0}/setup.cfg +0 -0
  165. {agentbundle-0.20.1 → agentbundle-0.22.0}/tests/test_linear_primitive.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentbundle
3
- Version: 0.20.1
3
+ Version: 0.22.0
4
4
  Summary: npm for your coding agent. Install packs of skills, subagents, and hooks into any repo, for every major agent.
5
5
  Author-email: eugenelim <eugenelim@users.noreply.github.com>
6
6
  License: Apache-2.0 OR MIT
@@ -175,6 +175,11 @@ Developers installing from your fork get the internal channel without a manual
175
175
  A malformed `enabled = true` config fails closed — no silent fallback to the public
176
176
  source.
177
177
 
178
+ **Offline and air-gapped hosts:** set `AGENTBUNDLE_NO_REMOTE=1` to skip the org
179
+ Artifactory bootstrap and editable-install detection entirely. `agentbundle` falls
180
+ straight through to the packaged default, so hosts without network access to Artifactory
181
+ still resolve a source without errors.
182
+
178
183
  See the full enterprise adoption guide at
179
184
  `docs/guides/_shared/how-to/use-an-artifactory-catalogue.md` for all six flows
180
185
  (org bootstrap, repo-scope CI upgrade, user-scope MDM, source-conflict remediation,
@@ -242,6 +247,38 @@ agentbundle pack evals run --pack <pack-name> --catalogue-root .
242
247
 
243
248
  See the [pack layout reference](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/architecture/pack-layout.md) and [authoring a skill](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/guides/_shared/how-to/author-a-skill.md).
244
249
 
250
+ ## Catalogue defaults and pack config API
251
+
252
+ **Catalogue defaults** let operators ship sensible starting values for every pack they distribute. Add a `[pack-defaults.<pack-name>]` table to `catalogue.toml` and the defaults are baked in at publish time. They slot into the three-layer cascade: pack-source defaults → operator defaults → user config. A custom install root is also supported via `[catalogue] user-dir = "~/custom/path"`.
253
+
254
+ **Pack scripts** can resolve their user-scope directory and read the merged config at runtime using the `agentbundle.config` API:
255
+
256
+ ```python
257
+ from agentbundle.config import pack_dir, load_pack_config
258
+
259
+ directory = pack_dir("my-pack") # ~/.agentbundle/my-pack/
260
+ config = load_pack_config("my-pack") # merged dict: pack defaults + operator + user
261
+ ```
262
+
263
+ **Operation log** — scripts can append structured JSONL records to `<pack_dir>/ops.jsonl` for lightweight audit trails:
264
+
265
+ ```python
266
+ from agentbundle.oplog import write_entry
267
+
268
+ write_entry("my-pack", "install", src="git+https://example.com/my-pack")
269
+ ```
270
+
271
+ **CLI commands** to read, write, and inspect pack config and operation logs:
272
+
273
+ ```bash
274
+ agentbundle pack-config show my-pack # all config values for a pack
275
+ agentbundle pack-config get my-pack api-key # single value
276
+ agentbundle pack-config set my-pack api-key v # write to user config.toml
277
+
278
+ agentbundle oplog show my-pack # JSONL operation history
279
+ agentbundle oplog clear my-pack # wipe history (asks first)
280
+ ```
281
+
245
282
  ## Credentials
246
283
 
247
284
  `agentbundle` doesn't resolve secrets. Credentialed skills use [`credbroker`](https://pypi.org/project/credbroker/), a standalone resolver that keeps cleartext out of the model's reach.
@@ -0,0 +1,266 @@
1
+ # agentbundle
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/agentbundle)](https://pypi.org/project/agentbundle/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/agentbundle)](https://pypi.org/project/agentbundle/)
5
+ [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](https://github.com/eugenelim/agent-ready-repo#license)
6
+
7
+ **The installer for [agent-ready-repo](https://github.com/eugenelim/agent-ready-repo).** Think npm, but for the skills, subagents, and hooks your coding agent runs on. One pack, one command, every major agent — Claude Code, Codex, Cursor, Copilot, Gemini, and Kiro (both the CLI and the IDE).
8
+
9
+ ## Quick start
10
+
11
+ ```bash
12
+ pip install agentbundle
13
+ ```
14
+
15
+ Requires Python 3.11+. Runs on macOS, Linux, and Windows.
16
+
17
+ **Install into a repo** — so everyone who clones it gets the pack. `core` is the flagship pack, the loop itself:
18
+
19
+ ```bash
20
+ agentbundle install --pack core
21
+ ```
22
+
23
+ No catalogue argument needed: it defaults to the agent-ready-repo catalogue. It lands in the repo's agent config — subagents and skills included — and you commit it like any other project file. This is the default scope: the pack belongs to the project and the whole team.
24
+
25
+ **Install for yourself, everywhere** — so a pack follows you across every project, with no per-repo setup:
26
+
27
+ ```bash
28
+ agentbundle install --pack desk-research --scope user
29
+ ```
30
+
31
+ User-scope packs land in your home directory, not the repo — they're yours, not the team's, and they're there in every project you open.
32
+
33
+ The install auto-detects your agent (`--adapter` overrides). Multi-IDE? Install the same pack for each agent at the same scope — they coexist, and the agents that read `.agents/skills/` (codex, cursor, gemini, copilot) share one skill copy instead of fighting over it. To install from a **different** catalogue, pass it as a trailing argument — a git URL or a local path (`agentbundle install --pack core <catalogue>`); a `config set source <catalogue>` makes that the default, and an editable clone (`pip install -e`) defaults to itself.
34
+
35
+ ## More commands
36
+
37
+ ```bash
38
+ # See what the catalogue offers (bare uses the default; or name one explicitly)
39
+ agentbundle list-packs
40
+ agentbundle list-profiles
41
+
42
+ # See what a single pack contains — skills and agents, derived live from its tree
43
+ agentbundle show core
44
+ agentbundle show core --format json # stable object for scripts/agents
45
+
46
+ # See what YOU have installed — pack, adapter, scope, version, and whether
47
+ # an upgrade is available (both scopes by default)
48
+ agentbundle list-installed
49
+ agentbundle list-installed --no-check # skip the catalogue check (offline, fast)
50
+ agentbundle list-installed --check-drift # also count locally edited files
51
+ agentbundle list-installed --format json # machine-readable JSON (schema_version 1)
52
+ agentbundle list-installed --updates-only # show only rows needing attention
53
+
54
+ # Install a whole curated profile — a single-scope set of packs — in one command
55
+ agentbundle install --profile inception
56
+
57
+ # Preview any install without writing a file
58
+ agentbundle install --pack core --dry-run
59
+
60
+ # Upgrade to the version the catalogue ships — shows installed → target, asks first
61
+ agentbundle upgrade --pack core
62
+ agentbundle upgrade --pack core --yes # skip the prompt (CI)
63
+
64
+ # Uninstall — previews remove (Tier-1) vs keep (your edits), asks first
65
+ agentbundle uninstall --pack core --dry-run
66
+ agentbundle uninstall --pack core --yes
67
+ ```
68
+
69
+ **`list-installed`** reads your state files (not the catalogue) and reports every installed `(pack, adapter)` at each scope with its version and a four-value status — `up-to-date`, `upgrade-available`, `ahead` (installed version is newer than catalogue), or `unknown`; it degrades to `unknown` (never an error) when the catalogue can't be resolved, and `--no-check` skips the check entirely. `--format json` emits a stable JSON contract (`schema_version: 1`) to stdout — useful for CI automation of upgrade decisions. `--updates-only` hides `up-to-date` rows.
70
+
71
+ **`show <pack>`** answers "what skills and agents does this pack contain?" by walking the pack's source tree live on each call — so the answer can't drift, and nothing is persisted. `--format json` emits a stable object (`name`, `version`, `description`, `skills`, `agents`, `source`) for scripts and agents. When the catalogue can't be resolved, an *installed* pack still reports its inventory from your state files (marked `source: installed-state`); a not-installed pack errors.
72
+
73
+ A **profile** is a catalogue-curated, single-scope set of packs you install in one command — it declares its own scope, so `--scope` doesn't apply. **Upgrade takes no version** — the target is whatever the catalogue you point at declares; to pin a past version, point the catalogue at that git ref. Install a pack that's **already there** and `agentbundle` offers to `upgrade` it instead (`--yes` runs it straight away).
74
+
75
+ **Mutating commands ask first.** `uninstall`, the `--force` cleanup, and the upgrade offer all preview what they'll do and confirm before touching anything; `--dry-run` previews without writing, and `--yes` skips the prompt for non-interactive / CI use (where, without it, they refuse rather than hang).
76
+
77
+ ## Enterprise distribution
78
+
79
+ For organizations running an internal Artifactory mirror or any static HTTPS server,
80
+ agentbundle's enterprise distribution capabilities handle the full adoption loop —
81
+ from org-wide channel configuration to CI-driven bulk upgrades.
82
+
83
+ **Install from an internal Artifactory channel:**
84
+
85
+ ```bash
86
+ # Point agentbundle at your org's channel descriptor (one-time per machine,
87
+ # or pre-configured in your org fork — see Org bootstrap below)
88
+ agentbundle config set source catalogue+https://artifactory.example.test/agentbundle/catalogues/core/channels/stable.json
89
+
90
+ agentbundle install --pack core
91
+ ```
92
+
93
+ The channel descriptor points to an immutable versioned archive; agentbundle
94
+ fetches, verifies its SHA-256 digest, and installs. Pass a bearer token via
95
+ `AGENTBUNDLE_HTTP_BEARER_TOKEN` — it is never stored in state, never printed, and
96
+ never forwarded to a different host.
97
+
98
+ **JSON output for CI pipelines:**
99
+
100
+ ```bash
101
+ # See what's installed and what needs upgrading — machine-readable
102
+ agentbundle list-installed --format json
103
+ agentbundle list-installed --format json --updates-only
104
+ ```
105
+
106
+ Returns a stable JSON contract (`schema_version` 1) with per-row status
107
+ (`up-to-date` / `upgrade-available` / `ahead` / `unknown`) and machine-readable
108
+ reason codes for unknown rows. Pipe into `jq` or your CI annotation step.
109
+
110
+ **Bulk upgrade in one scoped command:**
111
+
112
+ ```bash
113
+ # Upgrade all installed packs in a scope — preflights before any write
114
+ agentbundle upgrade --all --scope repo --yes
115
+ agentbundle upgrade --all --scope user --format json --yes
116
+ ```
117
+
118
+ Preflights all rows before writing anything; a blocked row stops the run before the
119
+ filesystem is touched. Partial failure is reported honestly — not described as a
120
+ rollback. Never silently downgrades an `ahead` row.
121
+
122
+ **Package your catalogue for Artifactory:**
123
+
124
+ ```bash
125
+ agentbundle package-catalogue \
126
+ --root /path/to/catalogue \
127
+ --bundle my-packs \
128
+ --release 1.0.0 \
129
+ --channel stable \
130
+ --output dist/
131
+ ```
132
+
133
+ Produces a deterministic, reproducible gzip archive (versioned) and a mutable channel
134
+ descriptor JSON (`stable.json`), ready to upload to Artifactory. Identical inputs
135
+ produce byte-identical archives (honors `SOURCE_DATE_EPOCH`).
136
+
137
+ **Org bootstrap — ship the default channel in your fork:**
138
+
139
+ Add an `[organization.artifactory]` block to
140
+ `agentbundle/_data/install-defaults.toml` in your org's agentbundle fork:
141
+
142
+ ```toml
143
+ [organization.artifactory]
144
+ enabled = true
145
+ base-url = "https://artifactory.example.test"
146
+ repository = "agentbundle"
147
+ bundle = "core"
148
+ channel = "stable"
149
+ ```
150
+
151
+ Developers installing from your fork get the internal channel without a manual
152
+ `config set source` step. The block ships `enabled = false` in the public package.
153
+ A malformed `enabled = true` config fails closed — no silent fallback to the public
154
+ source.
155
+
156
+ **Offline and air-gapped hosts:** set `AGENTBUNDLE_NO_REMOTE=1` to skip the org
157
+ Artifactory bootstrap and editable-install detection entirely. `agentbundle` falls
158
+ straight through to the packaged default, so hosts without network access to Artifactory
159
+ still resolve a source without errors.
160
+
161
+ See the full enterprise adoption guide at
162
+ `docs/guides/_shared/how-to/use-an-artifactory-catalogue.md` for all six flows
163
+ (org bootstrap, repo-scope CI upgrade, user-scope MDM, source-conflict remediation,
164
+ disconnected hosts, and security controls).
165
+
166
+ ## Build your own catalogue
167
+
168
+ `agentbundle` isn't tied to the agent-ready-repo catalogue. Any repo that lays its packs out the same way can use it. A pack is a directory:
169
+
170
+ ```text
171
+ my-pack/
172
+ pack.toml # name, version, adapter-contract, install scope,
173
+ # plus rich metadata (license, maintainers, links,
174
+ # categories, keywords) and a README pointer
175
+ .claude-plugin/
176
+ plugin.json # Claude Code plugin manifest (hand-authored)
177
+ README.md # the pack's portable doc — projected with the pack
178
+ .apm/ # primitives — projected by the build pipeline
179
+ skills/<name>/
180
+ SKILL.md # the skill body; one folder per skill
181
+ references/ # progressive-disclosure docs, loaded on demand
182
+ assets/ # templates the skill copies into the repo
183
+ agents/<name>.md # subagents
184
+ hooks/<name>.py # lifecycle hooks
185
+ seeds/ # files scaffolded into the adopter repo
186
+ ```
187
+
188
+ `pack.toml` is the **single source of truth** for a pack's metadata. Declare
189
+ `license`, `[[pack.maintainers]]`, `[pack.links]`, `categories`, and
190
+ `keywords` once; the build projects the cleanly-mappable subset — plus the
191
+ pack's `README.md` — into each distribution route's manifest (the `plugin.json`
192
+ / `marketplace.json` entry), so the catalogue describes each pack richly rather
193
+ than with a single sentence. Extra fields stay in `pack.toml`; the projection
194
+ is deliberately lossy per tool.
195
+
196
+ Point a catalogue URI (a git URL or a local path) at the repo that holds your packs. Then `validate` a pack against the adapter contract, `render` it to preview the projection, and `install` it into a target repo. `scaffold` drops a pack's seeds into a fresh directory to start from. The build pipeline (`agentbundle.build`) is the same engine `make build` runs.
197
+
198
+ **Org adapter default:** If your org ships a private `agentbundle` wheel (or a fork pinned to your internal catalogue), you can set a default adapter for all developers without requiring them to run `agentbundle config set` or pass `--adapter` on every install. Add an `[organization]` table to `_data/install-defaults.toml` in your fork:
199
+
200
+ ```toml
201
+ [organization]
202
+ preferred_adapter = "cursor"
203
+ ```
204
+
205
+ The org hint fires after the user-config but before the on-disk IDE probe — so `--adapter`, user-config, and upgrade state-hints all take priority. An invalid value exits 1 before writing anything. See the [`agentbundle` reference](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/guides/_shared/reference/agentbundle.md#org-adapter-default) for the full cascade.
206
+
207
+ **Lint your catalogue** — shallow structural checks run without extra dependencies:
208
+
209
+ ```bash
210
+ agentbundle catalogue lint --root .
211
+ ```
212
+
213
+ For full [agentskills.io spec](https://agentskills.io/specification) compliance (frontmatter key set, description policy, encoding, evals schema), install the `lint` extra and run with `--deep`:
214
+
215
+ ```bash
216
+ pip install 'agentbundle[lint]'
217
+ agentbundle catalogue lint --root . --deep
218
+ ```
219
+
220
+ **Run Tier-A activation evals** to measure whether each covered skill fires on the prompts it should:
221
+
222
+ ```bash
223
+ agentbundle pack evals run --pack <pack-name> --catalogue-root .
224
+ ```
225
+
226
+ See the [pack layout reference](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/architecture/pack-layout.md) and [authoring a skill](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/guides/_shared/how-to/author-a-skill.md).
227
+
228
+ ## Catalogue defaults and pack config API
229
+
230
+ **Catalogue defaults** let operators ship sensible starting values for every pack they distribute. Add a `[pack-defaults.<pack-name>]` table to `catalogue.toml` and the defaults are baked in at publish time. They slot into the three-layer cascade: pack-source defaults → operator defaults → user config. A custom install root is also supported via `[catalogue] user-dir = "~/custom/path"`.
231
+
232
+ **Pack scripts** can resolve their user-scope directory and read the merged config at runtime using the `agentbundle.config` API:
233
+
234
+ ```python
235
+ from agentbundle.config import pack_dir, load_pack_config
236
+
237
+ directory = pack_dir("my-pack") # ~/.agentbundle/my-pack/
238
+ config = load_pack_config("my-pack") # merged dict: pack defaults + operator + user
239
+ ```
240
+
241
+ **Operation log** — scripts can append structured JSONL records to `<pack_dir>/ops.jsonl` for lightweight audit trails:
242
+
243
+ ```python
244
+ from agentbundle.oplog import write_entry
245
+
246
+ write_entry("my-pack", "install", src="git+https://example.com/my-pack")
247
+ ```
248
+
249
+ **CLI commands** to read, write, and inspect pack config and operation logs:
250
+
251
+ ```bash
252
+ agentbundle pack-config show my-pack # all config values for a pack
253
+ agentbundle pack-config get my-pack api-key # single value
254
+ agentbundle pack-config set my-pack api-key v # write to user config.toml
255
+
256
+ agentbundle oplog show my-pack # JSONL operation history
257
+ agentbundle oplog clear my-pack # wipe history (asks first)
258
+ ```
259
+
260
+ ## Credentials
261
+
262
+ `agentbundle` doesn't resolve secrets. Credentialed skills use [`credbroker`](https://pypi.org/project/credbroker/), a standalone resolver that keeps cleartext out of the model's reach.
263
+
264
+ ## Learn more
265
+
266
+ The full story — the loop, the reviewers, the pack catalogue — is in the [agent-ready-repo README](https://github.com/eugenelim/agent-ready-repo#readme).
@@ -1,15 +1,11 @@
1
1
  # agentbundle
2
2
 
3
- [![PyPI](https://img.shields.io/pypi/v/agentbundle)](https://pypi.org/project/agentbundle/)
4
- [![Python](https://img.shields.io/pypi/pyversions/agentbundle)](https://pypi.org/project/agentbundle/)
5
- [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](https://github.com/eugenelim/agent-ready-repo#license)
6
-
7
3
  **The installer for [agent-ready-repo](https://github.com/eugenelim/agent-ready-repo).** Think npm, but for the skills, subagents, and hooks your coding agent runs on. One pack, one command, every major agent — Claude Code, Codex, Cursor, Copilot, Gemini, and Kiro (both the CLI and the IDE).
8
4
 
9
5
  ## Quick start
10
6
 
11
7
  ```bash
12
- pip install agentbundle
8
+ pip install -e packages/agentbundle
13
9
  ```
14
10
 
15
11
  Requires Python 3.11+. Runs on macOS, Linux, and Windows.
@@ -208,7 +204,7 @@ agentbundle catalogue lint --root .
208
204
  For full [agentskills.io spec](https://agentskills.io/specification) compliance (frontmatter key set, description policy, encoding, evals schema), install the `lint` extra and run with `--deep`:
209
205
 
210
206
  ```bash
211
- pip install 'agentbundle[lint]'
207
+ pip install -e 'packages/agentbundle[lint]'
212
208
  agentbundle catalogue lint --root . --deep
213
209
  ```
214
210
 
@@ -220,9 +216,41 @@ agentbundle pack evals run --pack <pack-name> --catalogue-root .
220
216
 
221
217
  See the [pack layout reference](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/architecture/pack-layout.md) and [authoring a skill](https://github.com/eugenelim/agent-ready-repo/blob/main/docs/guides/_shared/how-to/author-a-skill.md).
222
218
 
219
+ ## Catalogue defaults and pack config API
220
+
221
+ **Catalogue defaults** let operators ship sensible starting values for every pack they distribute. Add a `[pack-defaults.<pack-name>]` table to `catalogue.toml` and the defaults are baked in at publish time. They slot into the three-layer cascade: pack-source defaults → operator defaults → user config. A custom install root is also supported via `[catalogue] user-dir = "~/custom/path"`.
222
+
223
+ **Pack scripts** can resolve their user-scope directory and read the merged config at runtime using the `agentbundle.config` API:
224
+
225
+ ```python
226
+ from agentbundle.config import pack_dir, load_pack_config
227
+
228
+ directory = pack_dir("my-pack") # ~/.agentbundle/my-pack/
229
+ config = load_pack_config("my-pack") # merged dict: pack defaults + operator + user
230
+ ```
231
+
232
+ **Operation log** — scripts can append structured JSONL records to `<pack_dir>/ops.jsonl` for lightweight audit trails:
233
+
234
+ ```python
235
+ from agentbundle.oplog import write_entry
236
+
237
+ write_entry("my-pack", "install", src="git+https://example.com/my-pack")
238
+ ```
239
+
240
+ **CLI commands** to read, write, and inspect pack config and operation logs:
241
+
242
+ ```bash
243
+ agentbundle pack-config show my-pack # all config values for a pack
244
+ agentbundle pack-config get my-pack api-key # single value
245
+ agentbundle pack-config set my-pack api-key v # write to user config.toml
246
+
247
+ agentbundle oplog show my-pack # JSONL operation history
248
+ agentbundle oplog clear my-pack # wipe history (asks first)
249
+ ```
250
+
223
251
  ## Credentials
224
252
 
225
- `agentbundle` doesn't resolve secrets. Credentialed skills use [`credbroker`](https://pypi.org/project/credbroker/), a standalone resolver that keeps cleartext out of the model's reach.
253
+ `agentbundle` doesn't resolve secrets. Credentialed skills use [`credbroker`](../credbroker/README.md), a standalone resolver that keeps cleartext out of the model's reach.
226
254
 
227
255
  ## Learn more
228
256
 
@@ -12,6 +12,14 @@
12
12
  "description": "Schema version — must be 1.",
13
13
  "enum": [1]
14
14
  },
15
+ "pack-defaults": {
16
+ "type": "object",
17
+ "description": "Per-pack configuration defaults baked into install-defaults.toml at build time.",
18
+ "additionalProperties": {
19
+ "type": "object",
20
+ "additionalProperties": { "type": "string" }
21
+ }
22
+ },
15
23
  "catalogue": {
16
24
  "type": "object",
17
25
  "additionalProperties": false,
@@ -25,6 +33,10 @@
25
33
  "package"
26
34
  ],
27
35
  "properties": {
36
+ "user-dir": {
37
+ "type": "string",
38
+ "description": "User-scope pack directory root (must start with ~/). Defaults to ~/.agentbundle when absent."
39
+ },
28
40
  "name": {
29
41
  "type": "string",
30
42
  "description": "Safe identifier — [A-Za-z0-9][A-Za-z0-9_-]*.",
@@ -159,6 +171,10 @@
159
171
  "bundle": {
160
172
  "type": "string",
161
173
  "description": "Catalogue bundle name (required when enabled)."
174
+ },
175
+ "channel": {
176
+ "type": "string",
177
+ "description": "Artifactory channel name (required when enabled = true)."
162
178
  }
163
179
  }
164
180
  }
@@ -48,16 +48,12 @@ Exit codes:
48
48
  """
49
49
 
50
50
  import argparse
51
+ import contextlib
51
52
  import datetime
52
53
  import hashlib
53
54
  import json
54
55
  import os
55
56
  import pathlib
56
- import sys
57
- import tempfile
58
- import tomllib
59
- from datetime import timezone
60
-
61
57
 
62
58
  # ---------------------------------------------------------------------------
63
59
  # Vendored pack-name / pack-version shape rules — copied from
@@ -69,6 +65,9 @@ from datetime import timezone
69
65
  # pack.toml would pass unchecked and land phantom TOML lines in the marker.
70
66
  # ---------------------------------------------------------------------------
71
67
  import re as _re
68
+ import sys
69
+ import tempfile
70
+ import tomllib
72
71
 
73
72
  _PACK_NAME_RE = _re.compile(r"^[a-z0-9][a-z0-9-]*$")
74
73
  _PACK_VERSION_RE = _re.compile(
@@ -164,11 +163,11 @@ def _assert_under(target: pathlib.Path, jail: pathlib.Path) -> None:
164
163
  # jail is already resolved; do not call os.path.realpath on it again.
165
164
  try:
166
165
  resolved_target.relative_to(jail)
167
- except ValueError:
166
+ except ValueError as exc:
168
167
  raise ValueError(
169
168
  f"install-marker: marker path {resolved_target} escapes the per-scope jail "
170
169
  f"{jail}; refusing write"
171
- )
170
+ ) from exc
172
171
 
173
172
 
174
173
  def _assert_portable_name(component: str) -> None:
@@ -196,7 +195,7 @@ def _assert_portable_name(component: str) -> None:
196
195
  f"install-marker: refusing path component with control character "
197
196
  f"U+{ord(ch):04X}: {component!r}"
198
197
  )
199
- if component.endswith(".") or component.endswith(" "):
198
+ if component.endswith((".", " ")):
200
199
  raise ValueError(
201
200
  f"install-marker: refusing path component with trailing dot or space: "
202
201
  f"{component!r}"
@@ -294,7 +293,7 @@ def _marker_scope(origin: str) -> str:
294
293
  def _pack_toml(plugin_root: pathlib.Path) -> dict:
295
294
  """Load and return the pack manifest dict from ``pack.toml``."""
296
295
  toml_path = plugin_root / "pack.toml"
297
- with open(toml_path, "rb") as fh:
296
+ with toml_path.open("rb") as fh:
298
297
  return tomllib.load(fh)
299
298
 
300
299
 
@@ -343,12 +342,11 @@ def _marker_path(
343
342
  agentbundle.mkdir(mode=0o700, parents=True, exist_ok=True)
344
343
  resolved_jail = pathlib.Path(os.path.realpath(agentbundle))
345
344
  return agentbundle / ".adapt-install-marker.toml", resolved_jail
346
- else:
347
- # repo scope — project_dir must be set; callers ensure this.
348
- if project_dir is None:
349
- raise ValueError("project_dir required for repo-scope marker")
350
- resolved_jail = pathlib.Path(os.path.realpath(project_dir))
351
- return project_dir / ".adapt-install-marker.toml", resolved_jail
345
+ # repo scope — project_dir must be set; callers ensure this.
346
+ if project_dir is None:
347
+ raise ValueError("project_dir required for repo-scope marker")
348
+ resolved_jail = pathlib.Path(os.path.realpath(project_dir))
349
+ return project_dir / ".adapt-install-marker.toml", resolved_jail
352
350
 
353
351
 
354
352
  def _should_fire(
@@ -606,18 +604,14 @@ def _write_marker(
606
604
  os.write(tmp_fd, content_bytes)
607
605
  os.close(tmp_fd)
608
606
  tmp_fd = -1
609
- os.replace(tmp_name, marker_path)
607
+ pathlib.Path(tmp_name).replace(marker_path)
610
608
  except Exception:
611
609
  # Clean up tempfile on error (best effort).
612
610
  if tmp_fd >= 0:
613
- try:
611
+ with contextlib.suppress(OSError):
614
612
  os.close(tmp_fd)
615
- except OSError:
616
- pass
617
- try:
618
- os.unlink(tmp_name)
619
- except OSError:
620
- pass
613
+ with contextlib.suppress(OSError):
614
+ pathlib.Path(tmp_name).unlink()
621
615
  raise
622
616
 
623
617
 
@@ -640,17 +634,13 @@ def _write_hash(plugin_data: pathlib.Path, current_hash: str) -> None:
640
634
  os.write(tmp_fd, content_bytes)
641
635
  os.close(tmp_fd)
642
636
  tmp_fd = -1
643
- os.replace(tmp_name, hash_path)
637
+ pathlib.Path(tmp_name).replace(hash_path)
644
638
  except Exception:
645
639
  if tmp_fd >= 0:
646
- try:
640
+ with contextlib.suppress(OSError):
647
641
  os.close(tmp_fd)
648
- except OSError:
649
- pass
650
- try:
651
- os.unlink(tmp_name)
652
- except OSError:
653
- pass
642
+ with contextlib.suppress(OSError):
643
+ pathlib.Path(tmp_name).unlink()
654
644
  raise
655
645
 
656
646
 
@@ -892,7 +882,7 @@ def _main_claude_plugins(args: argparse.Namespace) -> int:
892
882
  new_entry: dict = {
893
883
  "name": pack_name,
894
884
  "version": pack_version,
895
- "installed-at": datetime.datetime.now(timezone.utc),
885
+ "installed-at": datetime.datetime.now(datetime.UTC),
896
886
  "install-route": args.install_route,
897
887
  }
898
888
 
@@ -912,7 +902,10 @@ def _main_claude_plugins(args: argparse.Namespace) -> int:
912
902
  except Exception as exc:
913
903
  # Hash write failure is non-fatal for the adopter (the marker was written),
914
904
  # but log it so the next session retries detection rather than silently skipping.
915
- print(f"install-marker: hash write failed (next session will retry): {exc}", file=sys.stderr)
905
+ print(
906
+ f"install-marker: hash write failed (next session will retry): {exc}",
907
+ file=sys.stderr,
908
+ )
916
909
 
917
910
  return 0
918
911
 
@@ -935,7 +928,7 @@ def _main_apm(args: argparse.Namespace) -> int:
935
928
  home = pathlib.Path(home_str)
936
929
 
937
930
  # --- Resolve data directory per AC3 precedence ---
938
- plugin_data = _resolve_data_dir(env)
931
+ plugin_data = _resolve_data_dir(env) # type: ignore[arg-type]
939
932
  if plugin_data is None:
940
933
  # No-match fall-through (no APM data-directory token set); exit 0
941
934
  # without writing marker or hash file (the no-partial-state rail).
@@ -1072,7 +1065,7 @@ def _main_apm(args: argparse.Namespace) -> int:
1072
1065
  new_entry: dict = {
1073
1066
  "name": pack_name,
1074
1067
  "version": pack_version,
1075
- "installed-at": datetime.datetime.now(timezone.utc),
1068
+ "installed-at": datetime.datetime.now(datetime.UTC),
1076
1069
  "install-route": args.install_route,
1077
1070
  }
1078
1071
 
@@ -1482,7 +1482,7 @@ def run_build_check_drift_gates(
1482
1482
  except _PARITY_EXPECTED_EXC as exc:
1483
1483
  source_out = f"<{type(exc).__name__}: {exc}>"
1484
1484
  try:
1485
- template_out: object = template_emit(test_input)
1485
+ template_out: object = template_emit(test_input) # type: ignore[operator]
1486
1486
  except _PARITY_EXPECTED_EXC as exc:
1487
1487
  template_out = f"<{type(exc).__name__}: {exc}>"
1488
1488
  if source_out != template_out:
@@ -602,10 +602,10 @@ class TestCodexSkillDirectDirectory(unittest.TestCase):
602
602
  self.assertNotIn("managed-block-delimiter-end", entry)
603
603
 
604
604
  def test_contract_files_byte_identical(self) -> None:
605
- self.assertEqual(
606
- CONTRACT_PATH.read_bytes(),
607
- DATA_CONTRACT_PATH.read_bytes(),
608
- )
605
+ def _norm(p: Path) -> bytes:
606
+ return p.read_bytes().replace(b"\r\n", b"\n")
607
+
608
+ self.assertEqual(_norm(CONTRACT_PATH), _norm(DATA_CONTRACT_PATH))
609
609
 
610
610
  def test_seed_agents_md_has_no_legacy_delimiters(self) -> None:
611
611
  text = SEED_AGENTS_MD_PATH.read_text(encoding="utf-8")
@@ -47,12 +47,14 @@ def _make_quadrants(base: Path) -> None:
47
47
 
48
48
 
49
49
  def _run_linter(cwd: Path) -> subprocess.CompletedProcess[str]:
50
+ env = {**os.environ, "PYTHONIOENCODING": "utf-8", "PYTHONUTF8": "1"}
50
51
  return subprocess.run(
51
52
  [sys.executable, str(LINTER)],
52
53
  cwd=cwd,
53
- env=dict(os.environ),
54
+ env=env,
54
55
  capture_output=True,
55
56
  text=True,
57
+ encoding="utf-8",
56
58
  check=False,
57
59
  )
58
60
 
@@ -63,13 +63,14 @@ def _seed_tree(
63
63
 
64
64
 
65
65
  def _run_linter(cwd: Path) -> subprocess.CompletedProcess[str]:
66
- env = dict(os.environ)
66
+ env = {**os.environ, "PYTHONIOENCODING": "utf-8", "PYTHONUTF8": "1"}
67
67
  return subprocess.run(
68
68
  [sys.executable, str(LINTER)],
69
69
  cwd=cwd,
70
70
  env=env,
71
71
  capture_output=True,
72
72
  text=True,
73
+ encoding="utf-8",
73
74
  check=False,
74
75
  )
75
76
 
@@ -52,12 +52,14 @@ def _seed(root: Path, canonical_block: str, agents_block: str) -> None:
52
52
 
53
53
 
54
54
  def _run_linter(cwd: Path) -> subprocess.CompletedProcess[str]:
55
+ env = {**os.environ, "PYTHONIOENCODING": "utf-8", "PYTHONUTF8": "1"}
55
56
  return subprocess.run(
56
57
  [sys.executable, str(LINTER)],
57
58
  cwd=cwd,
58
- env=dict(os.environ),
59
+ env=env,
59
60
  capture_output=True,
60
61
  text=True,
62
+ encoding="utf-8",
61
63
  check=False,
62
64
  )
63
65