agentbundle 0.20.0__tar.gz → 0.21.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.
- {agentbundle-0.20.0 → agentbundle-0.21.0}/PKG-INFO +35 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/README.md +34 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/catalogue.schema.json +12 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/install-marker.py +28 -35
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/self_host.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue.py +7 -9
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/archive.py +41 -10
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/build.py +6 -6
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/config.py +63 -3
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/defaults.py +16 -2
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/diagnostics.py +3 -3
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/lint.py +50 -49
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/package.py +29 -19
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/self_host.py +0 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/skill_spec_lint.py +2 -3
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/verify.py +30 -12
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/categories.py +3 -4
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/cli.py +177 -28
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/_common.py +17 -7
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/_drop_warning.py +1 -2
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/adapt.py +9 -4
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/catalogue_build.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/catalogue_lint.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/catalogue_package.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/catalogue_self_host.py +4 -6
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/catalogue_sync_defaults.py +2 -2
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/catalogue_tooling_stub.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/catalogue_verify.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/docs.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/init_state.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/install.py +92 -65
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/list_installed.py +35 -29
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/list_packs.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/list_profiles.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/list_targets.py +0 -1
- agentbundle-0.21.0/agentbundle/commands/oplog_cmd.py +98 -0
- agentbundle-0.21.0/agentbundle/commands/pack_config_cmd.py +201 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/pack_evals.py +45 -17
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/package_catalogue.py +1 -2
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/reconcile.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/render.py +1 -2
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/show.py +4 -4
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/uninstall.py +5 -7
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/upgrade.py +102 -103
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/validate.py +1 -4
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/config.py +165 -8
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/https_catalogue.py +34 -38
- agentbundle-0.21.0/agentbundle/oplog.py +168 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/render.py +1 -2
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/safety.py +85 -9
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/scope.py +1 -2
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/source_defaults.py +15 -11
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/statelock.py +3 -3
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/user_config.py +5 -11
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/version.py +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle.egg-info/PKG-INFO +35 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle.egg-info/SOURCES.txt +3 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/pyproject.toml +1 -1
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/__init__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/__main__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/adapter.schema.json +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/adapter.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/install-defaults.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/pack.schema.json +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/plugin-manifest.schema.json +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/_data/profile.schema.json +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/__init__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/__main__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapter_root_bins.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/__init__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/claude_code.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/codex.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/copilot.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/cursor.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/gemini.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/kiro.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/contract.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/lint_packs.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/main.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/phase_order.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/__init__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/direct_directory.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/hook_id.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/merge_json.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/projections/user_merge_json.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/recipes/composite-agents-md.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/recipes/composite-marketplace.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/recipes/marketplace.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/recipes/per-pack-apm-package.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/recipes/per-pack-claude-plugin.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/recipes/per-pack-overlay.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/recipes/self-host.toml +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/scope_rails.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/shared_libs.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/target_resolver.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/__init__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_cursor.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_gemini.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_contract.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_pack_schema.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_pipeline.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_projectable_subset.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_security.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_self_host_check.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_self_host_fixture_guard.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_self_host_recipe_config.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_shared_prefix_contract.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_validate.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/tests/test_writers_emit_lf.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/user_libs.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/build/validate.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/__init__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/catalogue_tooling/results.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/__init__.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/config.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/diff.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/profile.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/commands/scaffold.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle/pack_inventory.py +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle.egg-info/dependency_links.txt +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle.egg-info/entry_points.txt +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle.egg-info/requires.txt +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/agentbundle.egg-info/top_level.txt +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.0}/setup.cfg +0 -0
- {agentbundle-0.20.0 → agentbundle-0.21.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.
|
|
3
|
+
Version: 0.21.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
|
|
@@ -34,6 +34,8 @@ Requires-Dist: pyyaml>=6.0; extra == "lint"
|
|
|
34
34
|
pip install agentbundle
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
Requires Python 3.11+. Runs on macOS, Linux, and Windows.
|
|
38
|
+
|
|
37
39
|
**Install into a repo** — so everyone who clones it gets the pack. `core` is the flagship pack, the loop itself:
|
|
38
40
|
|
|
39
41
|
```bash
|
|
@@ -240,6 +242,38 @@ agentbundle pack evals run --pack <pack-name> --catalogue-root .
|
|
|
240
242
|
|
|
241
243
|
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).
|
|
242
244
|
|
|
245
|
+
## Catalogue defaults and pack config API
|
|
246
|
+
|
|
247
|
+
**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"`.
|
|
248
|
+
|
|
249
|
+
**Pack scripts** can resolve their user-scope directory and read the merged config at runtime using the `agentbundle.config` API:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from agentbundle.config import pack_dir, load_pack_config
|
|
253
|
+
|
|
254
|
+
directory = pack_dir("my-pack") # ~/.agentbundle/my-pack/
|
|
255
|
+
config = load_pack_config("my-pack") # merged dict: pack defaults + operator + user
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Operation log** — scripts can append structured JSONL records to `<pack_dir>/ops.jsonl` for lightweight audit trails:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from agentbundle.oplog import write_entry
|
|
262
|
+
|
|
263
|
+
write_entry("my-pack", "install", src="git+https://example.com/my-pack")
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**CLI commands** to read, write, and inspect pack config and operation logs:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
agentbundle pack-config show my-pack # all config values for a pack
|
|
270
|
+
agentbundle pack-config get my-pack api-key # single value
|
|
271
|
+
agentbundle pack-config set my-pack api-key v # write to user config.toml
|
|
272
|
+
|
|
273
|
+
agentbundle oplog show my-pack # JSONL operation history
|
|
274
|
+
agentbundle oplog clear my-pack # wipe history (asks first)
|
|
275
|
+
```
|
|
276
|
+
|
|
243
277
|
## Credentials
|
|
244
278
|
|
|
245
279
|
`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.
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
pip install agentbundle
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
Requires Python 3.11+. Runs on macOS, Linux, and Windows.
|
|
16
|
+
|
|
15
17
|
**Install into a repo** — so everyone who clones it gets the pack. `core` is the flagship pack, the loop itself:
|
|
16
18
|
|
|
17
19
|
```bash
|
|
@@ -218,6 +220,38 @@ agentbundle pack evals run --pack <pack-name> --catalogue-root .
|
|
|
218
220
|
|
|
219
221
|
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).
|
|
220
222
|
|
|
223
|
+
## Catalogue defaults and pack config API
|
|
224
|
+
|
|
225
|
+
**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"`.
|
|
226
|
+
|
|
227
|
+
**Pack scripts** can resolve their user-scope directory and read the merged config at runtime using the `agentbundle.config` API:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from agentbundle.config import pack_dir, load_pack_config
|
|
231
|
+
|
|
232
|
+
directory = pack_dir("my-pack") # ~/.agentbundle/my-pack/
|
|
233
|
+
config = load_pack_config("my-pack") # merged dict: pack defaults + operator + user
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Operation log** — scripts can append structured JSONL records to `<pack_dir>/ops.jsonl` for lightweight audit trails:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from agentbundle.oplog import write_entry
|
|
240
|
+
|
|
241
|
+
write_entry("my-pack", "install", src="git+https://example.com/my-pack")
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**CLI commands** to read, write, and inspect pack config and operation logs:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
agentbundle pack-config show my-pack # all config values for a pack
|
|
248
|
+
agentbundle pack-config get my-pack api-key # single value
|
|
249
|
+
agentbundle pack-config set my-pack api-key v # write to user config.toml
|
|
250
|
+
|
|
251
|
+
agentbundle oplog show my-pack # JSONL operation history
|
|
252
|
+
agentbundle oplog clear my-pack # wipe history (asks first)
|
|
253
|
+
```
|
|
254
|
+
|
|
221
255
|
## Credentials
|
|
222
256
|
|
|
223
257
|
`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.
|
|
@@ -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_-]*.",
|
|
@@ -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("."
|
|
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(
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
611
|
+
with contextlib.suppress(OSError):
|
|
614
612
|
os.close(tmp_fd)
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
637
|
+
pathlib.Path(tmp_name).replace(hash_path)
|
|
644
638
|
except Exception:
|
|
645
639
|
if tmp_fd >= 0:
|
|
646
|
-
|
|
640
|
+
with contextlib.suppress(OSError):
|
|
647
641
|
os.close(tmp_fd)
|
|
648
|
-
|
|
649
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
@@ -103,8 +103,7 @@ def _resolve_https(uri: str) -> Path:
|
|
|
103
103
|
atexit.register(shutil.rmtree, str(tmpdir), True)
|
|
104
104
|
_fetch_and_extract(tarball_url, tmpdir)
|
|
105
105
|
# The GitHub archive extracts to <repo>-<ref>/ (with '/' → '-' in SHAs).
|
|
106
|
-
|
|
107
|
-
return inner
|
|
106
|
+
return _find_inner_dir(tmpdir)
|
|
108
107
|
|
|
109
108
|
|
|
110
109
|
def _ref_type(ref: str) -> str:
|
|
@@ -148,13 +147,12 @@ def _github_archive_url(owner: str, repo: str, ref: str) -> str:
|
|
|
148
147
|
def _fetch_and_extract(url: str, dest: Path) -> None:
|
|
149
148
|
try:
|
|
150
149
|
# B310: constant github.com archive base assembled from parsed owner/repo/ref.
|
|
151
|
-
with urllib.request.urlopen(url) as resp: # nosec B310
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
tf.extractall(path=dest, filter="data")
|
|
150
|
+
with urllib.request.urlopen(url) as resp, tarfile.open(fileobj=resp, mode="r|gz") as tf: # nosec B310
|
|
151
|
+
# filter="data" rejects unsafe members (absolute paths, ..
|
|
152
|
+
# links, devices, setuid bits) — Python 3.12+ default but
|
|
153
|
+
# explicit for 3.11 compatibility and to silence the 3.14
|
|
154
|
+
# DeprecationWarning. Path-jail is belt; this is braces.
|
|
155
|
+
tf.extractall(path=dest, filter="data")
|
|
158
156
|
except urllib.error.URLError as exc:
|
|
159
157
|
raise CatalogueError(
|
|
160
158
|
f"Failed to fetch catalogue archive: {url} — {exc.reason}"
|
|
@@ -58,7 +58,10 @@ def _check_sha256_sidecar(archive: Path, sha256_file: Path | None) -> list[Diagn
|
|
|
58
58
|
return [_err("CAT-V-ARC-001", f"cannot read sha256 sidecar: {exc}")]
|
|
59
59
|
actual_hex = hashlib.sha256(archive.read_bytes()).hexdigest()
|
|
60
60
|
if expected_hex != actual_hex:
|
|
61
|
-
return [_err(
|
|
61
|
+
return [_err(
|
|
62
|
+
"CAT-V-ARC-001",
|
|
63
|
+
f"sha256 sidecar mismatch: expected {expected_hex}, got {actual_hex}",
|
|
64
|
+
)]
|
|
62
65
|
return []
|
|
63
66
|
|
|
64
67
|
|
|
@@ -74,7 +77,10 @@ def _check_gzip_parseable(archive: Path) -> list[Diagnostic]:
|
|
|
74
77
|
def _check_compressed_size(archive: Path) -> list[Diagnostic]:
|
|
75
78
|
size = archive.stat().st_size
|
|
76
79
|
if size > _MAX_COMPRESSED_BYTES:
|
|
77
|
-
return [_err(
|
|
80
|
+
return [_err(
|
|
81
|
+
"CAT-V-ARC-003",
|
|
82
|
+
f"compressed size {size} exceeds limit {_MAX_COMPRESSED_BYTES}",
|
|
83
|
+
)]
|
|
78
84
|
return []
|
|
79
85
|
|
|
80
86
|
|
|
@@ -102,7 +108,11 @@ def check_members(members: list[tarfile.TarInfo]) -> list[Diagnostic]:
|
|
|
102
108
|
# no device/special files or FIFOs
|
|
103
109
|
for m in members:
|
|
104
110
|
if m.isdev() or m.isfifo():
|
|
105
|
-
diags.append(_err(
|
|
111
|
+
diags.append(_err(
|
|
112
|
+
"CAT-V-ARC-007",
|
|
113
|
+
f"device/special/FIFO in archive: {m.name!r}",
|
|
114
|
+
path=m.name,
|
|
115
|
+
))
|
|
106
116
|
|
|
107
117
|
# AC13: no duplicate members
|
|
108
118
|
seen: set[str] = set()
|
|
@@ -125,7 +135,10 @@ def check_members(members: list[tarfile.TarInfo]) -> list[Diagnostic]:
|
|
|
125
135
|
|
|
126
136
|
# member count limit
|
|
127
137
|
if len(members) > _MAX_MEMBERS:
|
|
128
|
-
diags.append(_err(
|
|
138
|
+
diags.append(_err(
|
|
139
|
+
"CAT-V-ARC-003",
|
|
140
|
+
f"member count {len(members)} exceeds limit {_MAX_MEMBERS}",
|
|
141
|
+
))
|
|
129
142
|
|
|
130
143
|
return diags
|
|
131
144
|
|
|
@@ -133,7 +146,10 @@ def check_members(members: list[tarfile.TarInfo]) -> list[Diagnostic]:
|
|
|
133
146
|
def _check_expanded_size(members: list[tarfile.TarInfo]) -> list[Diagnostic]:
|
|
134
147
|
total = sum(m.size for m in members if m.isreg())
|
|
135
148
|
if total > _MAX_EXPANDED_BYTES:
|
|
136
|
-
return [_err(
|
|
149
|
+
return [_err(
|
|
150
|
+
"CAT-V-ARC-003",
|
|
151
|
+
f"expanded size {total} exceeds limit {_MAX_EXPANDED_BYTES}",
|
|
152
|
+
)]
|
|
137
153
|
return []
|
|
138
154
|
|
|
139
155
|
|
|
@@ -180,7 +196,10 @@ def _check_manifest_schema(manifest: dict) -> list[Diagnostic]:
|
|
|
180
196
|
diags.append(_err("CAT-V-ARC-011", f"manifest schema must be 1 or 2, got {schema!r}"))
|
|
181
197
|
for field in ("bundle", "release", "generated_at"):
|
|
182
198
|
if not isinstance(manifest.get(field), str):
|
|
183
|
-
diags.append(_err(
|
|
199
|
+
diags.append(_err(
|
|
200
|
+
"CAT-V-ARC-011",
|
|
201
|
+
f"manifest missing required string field {field!r}",
|
|
202
|
+
))
|
|
184
203
|
if not isinstance(manifest.get("files"), list):
|
|
185
204
|
diags.append(_err("CAT-V-ARC-011", "manifest 'files' must be a list"))
|
|
186
205
|
if not isinstance(manifest.get("packs"), list):
|
|
@@ -201,11 +220,19 @@ def _check_all_manifest_digests(
|
|
|
201
220
|
member_name = f"{prefix}{rel_path}" if prefix else rel_path
|
|
202
221
|
member = member_map.get(member_name)
|
|
203
222
|
if member is None:
|
|
204
|
-
diags.append(_err(
|
|
223
|
+
diags.append(_err(
|
|
224
|
+
"CAT-V-ARC-013",
|
|
225
|
+
f"manifest declares {rel_path!r} but not in archive",
|
|
226
|
+
path=rel_path,
|
|
227
|
+
))
|
|
205
228
|
continue
|
|
206
229
|
fobj = tf.extractfile(member)
|
|
207
230
|
if fobj is None:
|
|
208
|
-
diags.append(_err(
|
|
231
|
+
diags.append(_err(
|
|
232
|
+
"CAT-V-ARC-012",
|
|
233
|
+
f"cannot extract {rel_path!r} for digest check",
|
|
234
|
+
path=rel_path,
|
|
235
|
+
))
|
|
209
236
|
continue
|
|
210
237
|
actual_sha = hashlib.sha256(fobj.read()).hexdigest()
|
|
211
238
|
if actual_sha != expected_sha:
|
|
@@ -268,7 +295,10 @@ def _check_min_agentbundle_compat(manifest: dict) -> list[Diagnostic]:
|
|
|
268
295
|
except ValueError:
|
|
269
296
|
return []
|
|
270
297
|
if running < required:
|
|
271
|
-
return [_err(
|
|
298
|
+
return [_err(
|
|
299
|
+
"CAT-V-ARC-015",
|
|
300
|
+
f"archive requires agentbundle >= {min_ver}, running {CLI_VERSION}",
|
|
301
|
+
)]
|
|
272
302
|
return []
|
|
273
303
|
|
|
274
304
|
|
|
@@ -335,7 +365,8 @@ def verify_archive(archive: Path, sha256_file: Path | None = None) -> VerifyResu
|
|
|
335
365
|
try:
|
|
336
366
|
tf.extractall(tmpdir, filter="data")
|
|
337
367
|
except TypeError:
|
|
338
|
-
# Python < 3.12: extract member-by-member;
|
|
368
|
+
# Python < 3.12: extract member-by-member;
|
|
369
|
+
# paths pre-validated by check_members()
|
|
339
370
|
for _m in tf.getmembers():
|
|
340
371
|
_dest = tmpdir / _m.name
|
|
341
372
|
_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -101,18 +101,18 @@ def build_catalogue(
|
|
|
101
101
|
|
|
102
102
|
# Temporarily override module-level constants when config is present.
|
|
103
103
|
# Restored in finally to avoid cross-test pollution.
|
|
104
|
-
saved_branch = _build_main._DIST_BRANCH
|
|
105
|
-
saved_desc = _build_main._MARKETPLACE_DESCRIPTION
|
|
104
|
+
saved_branch = _build_main._DIST_BRANCH # type: ignore[attr-defined]
|
|
105
|
+
saved_desc = _build_main._MARKETPLACE_DESCRIPTION # type: ignore[attr-defined]
|
|
106
106
|
try:
|
|
107
107
|
if config and config.build.claude_plugin_branch:
|
|
108
|
-
_build_main._DIST_BRANCH = config.build.claude_plugin_branch
|
|
108
|
+
_build_main._DIST_BRANCH = config.build.claude_plugin_branch # type: ignore[attr-defined]
|
|
109
109
|
if config and config.build.marketplace_description:
|
|
110
|
-
_build_main._MARKETPLACE_DESCRIPTION = config.build.marketplace_description
|
|
110
|
+
_build_main._MARKETPLACE_DESCRIPTION = config.build.marketplace_description # type: ignore[attr-defined]
|
|
111
111
|
|
|
112
112
|
rc = cmd_build(args)
|
|
113
113
|
finally:
|
|
114
|
-
_build_main._DIST_BRANCH = saved_branch
|
|
115
|
-
_build_main._MARKETPLACE_DESCRIPTION = saved_desc
|
|
114
|
+
_build_main._DIST_BRANCH = saved_branch # type: ignore[attr-defined]
|
|
115
|
+
_build_main._MARKETPLACE_DESCRIPTION = saved_desc # type: ignore[attr-defined]
|
|
116
116
|
|
|
117
117
|
return BuildResult(
|
|
118
118
|
ok=(rc == 0),
|
|
@@ -16,13 +16,22 @@ from __future__ import annotations
|
|
|
16
16
|
import json
|
|
17
17
|
import re
|
|
18
18
|
import tomllib
|
|
19
|
-
from dataclasses import dataclass
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from urllib.parse import urlsplit
|
|
22
22
|
|
|
23
23
|
# Bundled recipe IDs that are always valid without a path on disk.
|
|
24
24
|
_BUNDLED_RECIPES: frozenset[str] = frozenset({"default"})
|
|
25
25
|
|
|
26
|
+
# Pack slug pattern (must match agentbundle.config._PACK_NAME_RE).
|
|
27
|
+
_SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$")
|
|
28
|
+
|
|
29
|
+
# Reserved slugs that collide with well-known user-scope paths.
|
|
30
|
+
_RESERVED_SLUGS: frozenset[str] = frozenset({"bin", "state", "credentials", "state.toml"})
|
|
31
|
+
|
|
32
|
+
# Default user-scope pack directory.
|
|
33
|
+
_DEFAULT_USER_DIR = "~/.agentbundle"
|
|
34
|
+
|
|
26
35
|
# Safe identifier pattern for catalogue names.
|
|
27
36
|
_SAFE_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_\-]*$")
|
|
28
37
|
|
|
@@ -95,6 +104,9 @@ class CatalogueConfig:
|
|
|
95
104
|
build: CatalogueBuild
|
|
96
105
|
package: CataloguePackage
|
|
97
106
|
distribution: DistributionConfig
|
|
107
|
+
# RFC-0074: per-pack config defaults and user-scope directory.
|
|
108
|
+
user_dir: str = _DEFAULT_USER_DIR
|
|
109
|
+
pack_defaults: dict[str, dict[str, str]] = field(default_factory=dict)
|
|
98
110
|
|
|
99
111
|
|
|
100
112
|
def _load_schema() -> dict:
|
|
@@ -224,10 +236,10 @@ def _validate_path(root: Path, p: str, field_name: str) -> None:
|
|
|
224
236
|
resolved_root = root.resolve()
|
|
225
237
|
try:
|
|
226
238
|
resolved_path = (root / p).resolve()
|
|
227
|
-
except OSError:
|
|
239
|
+
except OSError as exc:
|
|
228
240
|
raise CatalogueConfigError(
|
|
229
241
|
f"catalogue.toml: {field_name!r} path cannot be resolved: {p!r}"
|
|
230
|
-
)
|
|
242
|
+
) from exc
|
|
231
243
|
if not resolved_path.is_relative_to(resolved_root):
|
|
232
244
|
raise CatalogueConfigError(
|
|
233
245
|
f"catalogue.toml: {field_name!r} path escapes catalogue root "
|
|
@@ -446,6 +458,52 @@ def load_catalogue_config(root: Path) -> CatalogueConfig | None:
|
|
|
446
458
|
)
|
|
447
459
|
distribution = DistributionConfig(agentbundle=agentbundle_dist)
|
|
448
460
|
|
|
461
|
+
# --- RFC-0074: user-dir and pack-defaults ---
|
|
462
|
+
|
|
463
|
+
user_dir_raw = cat.get("user-dir", _DEFAULT_USER_DIR)
|
|
464
|
+
if not isinstance(user_dir_raw, str):
|
|
465
|
+
raise CatalogueConfigError(
|
|
466
|
+
f"catalogue.toml: catalogue.user-dir must be a string, "
|
|
467
|
+
f"got {type(user_dir_raw).__name__}"
|
|
468
|
+
)
|
|
469
|
+
# Must start with ~/ (home-relative). Absolute paths outside $HOME are rejected.
|
|
470
|
+
if not user_dir_raw.startswith("~/"):
|
|
471
|
+
raise CatalogueConfigError(
|
|
472
|
+
f"catalogue.toml: catalogue.user-dir must start with ~/ (home-relative); "
|
|
473
|
+
f"got {user_dir_raw!r}. Absolute paths outside $HOME are not allowed."
|
|
474
|
+
)
|
|
475
|
+
# Reject path traversal sequences (e.g. ~/../../etc).
|
|
476
|
+
from pathlib import PurePosixPath
|
|
477
|
+
|
|
478
|
+
if ".." in PurePosixPath(user_dir_raw[2:]).parts:
|
|
479
|
+
raise CatalogueConfigError(
|
|
480
|
+
f"catalogue.toml: catalogue.user-dir must not contain '..' path components; "
|
|
481
|
+
f"got {user_dir_raw!r}"
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
raw_pack_defaults = raw.get("pack-defaults", {}) or {}
|
|
485
|
+
if not isinstance(raw_pack_defaults, dict):
|
|
486
|
+
raise CatalogueConfigError(
|
|
487
|
+
"catalogue.toml: pack-defaults must be a table"
|
|
488
|
+
)
|
|
489
|
+
pack_defaults: dict[str, dict[str, str]] = {}
|
|
490
|
+
for slug, kv in raw_pack_defaults.items():
|
|
491
|
+
if not _SLUG_RE.match(slug):
|
|
492
|
+
raise CatalogueConfigError(
|
|
493
|
+
f"catalogue.toml: pack-defaults key {slug!r} must match "
|
|
494
|
+
f"^[a-z0-9][a-z0-9-]*$"
|
|
495
|
+
)
|
|
496
|
+
if slug in _RESERVED_SLUGS:
|
|
497
|
+
raise CatalogueConfigError(
|
|
498
|
+
f"catalogue.toml: pack-defaults key {slug!r} is reserved "
|
|
499
|
+
f"({sorted(_RESERVED_SLUGS)})"
|
|
500
|
+
)
|
|
501
|
+
if not isinstance(kv, dict):
|
|
502
|
+
raise CatalogueConfigError(
|
|
503
|
+
f"catalogue.toml: pack-defaults.{slug} must be a table"
|
|
504
|
+
)
|
|
505
|
+
pack_defaults[slug] = {str(k): str(v) for k, v in kv.items()}
|
|
506
|
+
|
|
449
507
|
return CatalogueConfig(
|
|
450
508
|
schema=schema_val,
|
|
451
509
|
name=name,
|
|
@@ -456,4 +514,6 @@ def load_catalogue_config(root: Path) -> CatalogueConfig | None:
|
|
|
456
514
|
build=build,
|
|
457
515
|
package=package,
|
|
458
516
|
distribution=distribution,
|
|
517
|
+
user_dir=user_dir_raw,
|
|
518
|
+
pack_defaults=pack_defaults,
|
|
459
519
|
)
|
|
@@ -11,8 +11,12 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
from agentbundle.catalogue_tooling.config import
|
|
15
|
-
|
|
14
|
+
from agentbundle.catalogue_tooling.config import (
|
|
15
|
+
CatalogueConfig,
|
|
16
|
+
CatalogueConfigError,
|
|
17
|
+
load_catalogue_config,
|
|
18
|
+
)
|
|
19
|
+
from agentbundle.catalogue_tooling.results import Diagnostic, Severity, SyncDefaultsResult
|
|
16
20
|
|
|
17
21
|
_GENERATED_HEADER = (
|
|
18
22
|
"# Generated by agentbundle catalogue sync-defaults"
|
|
@@ -93,6 +97,16 @@ def compile_defaults(config: CatalogueConfig) -> str:
|
|
|
93
97
|
parts.append("\n")
|
|
94
98
|
parts.append(_emit_section("defaults", defaults_rows))
|
|
95
99
|
|
|
100
|
+
# [pack-defaults.<pack>] — sorted by pack name, then by key within each section.
|
|
101
|
+
for pack_name in sorted(config.pack_defaults):
|
|
102
|
+
kv = config.pack_defaults[pack_name]
|
|
103
|
+
rows: list[tuple[str, str]] = sorted(
|
|
104
|
+
(k, _emit_str(v)) for k, v in kv.items()
|
|
105
|
+
)
|
|
106
|
+
if rows:
|
|
107
|
+
parts.append("\n")
|
|
108
|
+
parts.append(_emit_section(f"pack-defaults.{pack_name}", rows))
|
|
109
|
+
|
|
96
110
|
return "".join(parts)
|
|
97
111
|
|
|
98
112
|
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import enum
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class DiagnosticCode(
|
|
8
|
+
class DiagnosticCode(enum.StrEnum):
|
|
9
9
|
UNKNOWN = "UNKNOWN"
|
|
10
10
|
|
|
11
11
|
# Lint codes — CAT-L001 through CAT-L027
|
|
@@ -38,5 +38,5 @@ class DiagnosticCode(str, enum.Enum):
|
|
|
38
38
|
CAT_L027 = "CAT-L027" # Multiline metadata form not supported
|
|
39
39
|
CAT_L028 = "CAT-L028" # Install profile invariant violation (scope, deps, order)
|
|
40
40
|
CAT_L029 = "CAT-L029" # Catalogue seeds lint failure (blocklist, placeholder, patterns.jsonl)
|
|
41
|
-
CAT_L030 = "CAT-L030" # First-value contract violation (Level A/B fields, writes-to-repo, tutorial)
|
|
42
|
-
CAT_L031 = "CAT-L031" # Credentialed-skill convention violation (D1/D2/D2b/D3/broker-specific)
|
|
41
|
+
CAT_L030 = "CAT-L030" # First-value contract violation (Level A/B fields, writes-to-repo, tutorial) # noqa: E501
|
|
42
|
+
CAT_L031 = "CAT-L031" # Credentialed-skill convention violation (D1/D2/D2b/D3/broker-specific) # noqa: E501
|