agentbundle 0.10.2__tar.gz → 0.11.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 (128) hide show
  1. {agentbundle-0.10.2 → agentbundle-0.11.0}/PKG-INFO +7 -1
  2. {agentbundle-0.10.2 → agentbundle-0.11.0}/README.md +6 -0
  3. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/lint_packs.py +7 -8
  4. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +3 -0
  5. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/cli.py +25 -0
  6. agentbundle-0.11.0/agentbundle/commands/show.py +272 -0
  7. agentbundle-0.11.0/agentbundle/pack_inventory.py +61 -0
  8. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/version.py +1 -1
  9. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle.egg-info/PKG-INFO +7 -1
  10. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle.egg-info/SOURCES.txt +2 -0
  11. {agentbundle-0.10.2 → agentbundle-0.11.0}/pyproject.toml +1 -1
  12. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/__init__.py +0 -0
  13. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/__main__.py +0 -0
  14. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/_data/adapter.schema.json +0 -0
  15. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/_data/adapter.toml +0 -0
  16. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/_data/install-defaults.toml +0 -0
  17. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/_data/install-marker.py +0 -0
  18. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/_data/pack.schema.json +0 -0
  19. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
  20. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/_data/plugin-manifest.schema.json +0 -0
  21. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/_data/profile.schema.json +0 -0
  22. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/__init__.py +0 -0
  23. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/__main__.py +0 -0
  24. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapter_root_bins.py +0 -0
  25. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/__init__.py +0 -0
  26. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/claude_code.py +0 -0
  27. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/codex.py +0 -0
  28. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/copilot.py +0 -0
  29. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/cursor.py +0 -0
  30. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/gemini.py +0 -0
  31. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/kiro.py +0 -0
  32. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/kiro_cli.py +0 -0
  33. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/adapters/kiro_ide.py +0 -0
  34. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/contract.py +0 -0
  35. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/main.py +0 -0
  36. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/phase_order.py +0 -0
  37. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/__init__.py +0 -0
  38. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/codex_agent_toml.py +0 -0
  39. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/copilot_agent_md.py +0 -0
  40. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
  41. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/direct_directory.py +0 -0
  42. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/gemini_command_toml.py +0 -0
  43. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/hook_id.py +0 -0
  44. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
  45. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
  46. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/merge_json.py +0 -0
  47. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/projections/user_merge_json.py +0 -0
  48. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/scope_rails.py +0 -0
  49. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/self_host.py +0 -0
  50. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/shared_libs.py +0 -0
  51. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/target_resolver.py +0 -0
  52. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/__init__.py +0 -0
  53. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
  54. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_codex.py +0 -0
  55. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
  56. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_cursor.py +0 -0
  57. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_gemini.py +0 -0
  58. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
  59. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
  60. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
  61. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_kiro_ide.py +0 -0
  62. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
  63. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
  64. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
  65. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
  66. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_contract.py +0 -0
  67. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_contract_scope.py +0 -0
  68. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_contract_v07.py +0 -0
  69. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_contract_v08.py +0 -0
  70. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
  71. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
  72. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +0 -0
  73. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
  74. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
  75. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_lint_packs.py +0 -0
  76. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
  77. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_pack_schema.py +0 -0
  78. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
  79. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
  80. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_pipeline.py +0 -0
  81. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
  82. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_projectable_subset.py +0 -0
  83. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
  84. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_scope_rails.py +0 -0
  85. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_security.py +0 -0
  86. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_self_host_check.py +0 -0
  87. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_self_host_fixture_guard.py +0 -0
  88. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_self_host_recipe_config.py +0 -0
  89. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
  90. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_shared_prefix_contract.py +0 -0
  91. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
  92. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
  93. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_validate.py +0 -0
  94. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/tests/test_writers_emit_lf.py +0 -0
  95. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/user_libs.py +0 -0
  96. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/build/validate.py +0 -0
  97. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/catalogue.py +0 -0
  98. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/categories.py +0 -0
  99. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/__init__.py +0 -0
  100. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/_common.py +0 -0
  101. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/_drop_warning.py +0 -0
  102. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/adapt.py +0 -0
  103. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/config.py +0 -0
  104. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/diff.py +0 -0
  105. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/init_state.py +0 -0
  106. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/install.py +0 -0
  107. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/list_installed.py +0 -0
  108. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/list_packs.py +0 -0
  109. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/list_profiles.py +0 -0
  110. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/list_targets.py +0 -0
  111. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/profile.py +0 -0
  112. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/reconcile.py +0 -0
  113. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/render.py +0 -0
  114. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/scaffold.py +0 -0
  115. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/uninstall.py +0 -0
  116. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/upgrade.py +0 -0
  117. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/commands/validate.py +0 -0
  118. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/config.py +0 -0
  119. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/render.py +0 -0
  120. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/safety.py +0 -0
  121. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/scope.py +0 -0
  122. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/source_defaults.py +0 -0
  123. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/statelock.py +0 -0
  124. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle/user_config.py +0 -0
  125. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle.egg-info/dependency_links.txt +0 -0
  126. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle.egg-info/entry_points.txt +0 -0
  127. {agentbundle-0.10.2 → agentbundle-0.11.0}/agentbundle.egg-info/top_level.txt +0 -0
  128. {agentbundle-0.10.2 → agentbundle-0.11.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentbundle
3
- Version: 0.10.2
3
+ Version: 0.11.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
@@ -55,6 +55,10 @@ The install auto-detects your agent (`--adapter` overrides). Multi-IDE? Install
55
55
  agentbundle list-packs
56
56
  agentbundle list-profiles
57
57
 
58
+ # See what a single pack contains — skills and agents, derived live from its tree
59
+ agentbundle show core
60
+ agentbundle show core --format json # stable object for scripts/agents
61
+
58
62
  # See what YOU have installed — pack, adapter, scope, version, and whether
59
63
  # an upgrade is available (both scopes by default)
60
64
  agentbundle list-installed
@@ -78,6 +82,8 @@ agentbundle uninstall --pack core --yes
78
82
 
79
83
  **`list-installed`** reads your state files (not the catalogue) and reports every installed `(pack, adapter)` at each scope with its version and an `up-to-date` / `upgrade-available` / `unknown` status; it degrades to `unknown` (never an error) when the catalogue can't be resolved, and `--no-check` skips the check entirely.
80
84
 
85
+ **`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.
86
+
81
87
  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).
82
88
 
83
89
  **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).
@@ -37,6 +37,10 @@ The install auto-detects your agent (`--adapter` overrides). Multi-IDE? Install
37
37
  agentbundle list-packs
38
38
  agentbundle list-profiles
39
39
 
40
+ # See what a single pack contains — skills and agents, derived live from its tree
41
+ agentbundle show core
42
+ agentbundle show core --format json # stable object for scripts/agents
43
+
40
44
  # See what YOU have installed — pack, adapter, scope, version, and whether
41
45
  # an upgrade is available (both scopes by default)
42
46
  agentbundle list-installed
@@ -60,6 +64,8 @@ agentbundle uninstall --pack core --yes
60
64
 
61
65
  **`list-installed`** reads your state files (not the catalogue) and reports every installed `(pack, adapter)` at each scope with its version and an `up-to-date` / `upgrade-available` / `unknown` status; it degrades to `unknown` (never an error) when the catalogue can't be resolved, and `--no-check` skips the check entirely.
62
66
 
67
+ **`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.
68
+
63
69
  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).
64
70
 
65
71
  **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).
@@ -35,6 +35,7 @@ import tomllib
35
35
  from typing import NamedTuple, Pattern
36
36
  from pathlib import Path
37
37
 
38
+ from agentbundle.pack_inventory import apm_entries
38
39
  from agentbundle.safety import PathJailError, assert_portable_name
39
40
 
40
41
  # Subtrees in a pack that ship to adopters. `seeds/` is the
@@ -278,10 +279,10 @@ def _extract_frontmatter_fields(
278
279
 
279
280
  def _check_skill_metadata(pack_dir: Path, constraints: Constraints) -> list[str]:
280
281
  findings: list[str] = []
281
- skills_dir = pack_dir / ".apm" / "skills"
282
- if not skills_dir.is_dir():
283
- return findings
284
- for entry in sorted(skills_dir.iterdir()):
282
+ # Enumeration lives in one place (RFC-0060 AC9): the shared raw walk.
283
+ # Lint keeps its own per-entry filtering (it lints SKILL.md-less dirs too,
284
+ # which `show` excludes) — the walk is shared, the filter is not.
285
+ for entry in apm_entries(pack_dir, "skills"):
285
286
  if not entry.is_dir():
286
287
  continue
287
288
  dir_name = entry.name
@@ -331,10 +332,8 @@ def _check_skill_metadata(pack_dir: Path, constraints: Constraints) -> list[str]
331
332
 
332
333
  def _check_agent_metadata(pack_dir: Path, constraints: Constraints) -> list[str]:
333
334
  findings: list[str] = []
334
- agents_dir = pack_dir / ".apm" / "agents"
335
- if not agents_dir.is_dir():
336
- return findings
337
- for entry in sorted(agents_dir.iterdir()):
335
+ # Shared raw walk (RFC-0060 AC9); lint keeps its own .md-file filter.
336
+ for entry in apm_entries(pack_dir, "agents"):
338
337
  if not entry.is_file() or entry.suffix != ".md":
339
338
  continue
340
339
  stem = entry.stem
@@ -12,6 +12,8 @@ The `research` pack (shipped later by the `research-pack` spec) also
12
12
  declared v0.8 at birth. A future pack landing at v0.8 should add itself to
13
13
  ``V08_PACKS`` so this test surfaces the new declaration.
14
14
 
15
+ - ``catalogue-curation`` (RFC-0059) landed at v0.8 and is registered here.
16
+
15
17
  Packs in-tree NOT at v0.8:
16
18
 
17
19
  - ``credential-brokers``: still at v0.7 (RFC-0013 shipped on v0.7 and
@@ -37,6 +39,7 @@ PACKS_DIR = REPO_ROOT / "packs"
37
39
 
38
40
  V08_PACKS = (
39
41
  "atlassian",
42
+ "catalogue-curation",
40
43
  "contracts",
41
44
  "converters",
42
45
  "figma",
@@ -274,6 +274,31 @@ def _build_parser() -> argparse.ArgumentParser:
274
274
  )
275
275
  sp.set_defaults(func=_lazy("list_installed"))
276
276
 
277
+ # --- show --- (catalogue query; walks a pack's .apm/ tree live)
278
+ sp = subparsers.add_parser(
279
+ "show",
280
+ help=(
281
+ "Show a pack's skills and agents, derived live from its .apm/ tree. "
282
+ "Falls back to the install state when the catalogue is unresolvable."
283
+ ),
284
+ )
285
+ sp.add_argument("pack", help="Pack name to inspect (e.g. core).")
286
+ sp.add_argument(
287
+ "--format",
288
+ choices=("table", "json"),
289
+ default="table",
290
+ help="Output format (default: table).",
291
+ )
292
+ sp.add_argument(
293
+ "--root",
294
+ default=".",
295
+ help=(
296
+ "Repo root used to locate the repo-scope install-state file for the "
297
+ "catalogue-unresolvable fallback. Default: current directory."
298
+ ),
299
+ )
300
+ sp.set_defaults(func=_lazy("show"))
301
+
277
302
  # --- scaffold --- (no --scope; always repo-targeted)
278
303
  sp = subparsers.add_parser(
279
304
  "scaffold",
@@ -0,0 +1,272 @@
1
+ """``agentbundle show <pack>`` subcommand.
2
+
3
+ Answers "what skills and agents does pack X contain?" by walking the pack's
4
+ ``.apm/`` source tree **live** on each call (ADR-0049 / RFC-0060): nothing is
5
+ persisted and no manifest is touched, so the answer cannot drift from what the
6
+ pack actually ships.
7
+
8
+ Two honest, state-differentiated sources:
9
+
10
+ - **Primary — catalogue.** Resolve the pack in the active catalogue, walk
11
+ ``.apm/skills/`` and ``.apm/agents/``, and print the pack's ``pack.toml``
12
+ metadata alongside its full, sorted skill + agent inventory
13
+ (``source: catalogue``).
14
+ - **Degrade — install state.** When the catalogue is unresolvable, an
15
+ *installed* pack still yields its inventory from the install-state file —
16
+ the union of skill/agent names across every adapter row in both the user
17
+ and repo scope (``source: installed-state``) — while a *not-installed* pack
18
+ fails with a one-line error, because there is no source to read.
19
+
20
+ ``--format json`` emits the same as a single stable object for programmatic
21
+ consumers. The inventory is never filtered (``show`` reports what *exists*, not
22
+ what a user can invoke), and it is the full, untagged set — every skill under
23
+ ``.apm/skills/``, not the ``[pack.evals].skills`` subset.
24
+
25
+ Exit codes:
26
+ 0 — success (catalogue path or installed-state fallback).
27
+ 1 — unknown pack, or unresolvable-catalogue-and-not-installed (one-line
28
+ stderr, empty stdout — including under ``--format json``).
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import sys
34
+ from pathlib import Path
35
+ from typing import TYPE_CHECKING
36
+
37
+ if TYPE_CHECKING:
38
+ import argparse
39
+
40
+ from agentbundle.config import State
41
+
42
+
43
+ def run(args: "argparse.Namespace") -> int:
44
+ """Entry point for ``agentbundle show``."""
45
+ from agentbundle.catalogue import CatalogueError, resolve_catalogue
46
+ from agentbundle.commands._common import resolve_catalogue_uri
47
+ from agentbundle.pack_inventory import agent_names, skill_names
48
+
49
+ pack_name: str = args.pack
50
+ fmt: str = getattr(args, "format", "table")
51
+
52
+ # ── Resolve the catalogue (authoritative primary source) ──────────────────
53
+ try:
54
+ catalogue_dir = resolve_catalogue(resolve_catalogue_uri(args))
55
+ except CatalogueError:
56
+ # Honest, state-differentiated degrade (ADR-0049).
57
+ return _degrade(args, pack_name, fmt)
58
+
59
+ # ── Name-match the pack in the catalogue ──────────────────────────────────
60
+ match = _find_pack_dir(catalogue_dir, pack_name)
61
+ if match is None:
62
+ print(f"show: pack {pack_name!r} not found in catalogue", file=sys.stderr)
63
+ return 1
64
+
65
+ pack_dir, toml = match
66
+ pack = toml.get("pack", {})
67
+ _emit(
68
+ fmt,
69
+ name=pack.get("name") or pack_dir.name,
70
+ # Pass metadata through as-is: an *absent* key is null; a present
71
+ # (even empty) value is not coerced, so "declared empty" and "absent"
72
+ # stay distinguishable on the authoritative catalogue path.
73
+ version=pack.get("version"),
74
+ description=pack.get("description"),
75
+ skills=skill_names(pack_dir),
76
+ agents=agent_names(pack_dir),
77
+ source="catalogue",
78
+ )
79
+ return 0
80
+
81
+
82
+ # ---------------------------------------------------------------------------
83
+ # Primary path — catalogue lookup
84
+ # ---------------------------------------------------------------------------
85
+
86
+
87
+ def _find_pack_dir(catalogue_dir: Path, pack_name: str) -> tuple[Path, dict] | None:
88
+ """Return ``(pack_dir, parsed pack.toml)`` for *pack_name*, or None.
89
+
90
+ Reuses ``list-packs``' ``_discover_pack_dirs`` so the accepted catalogue
91
+ layouts stay identical, then matches on the pack's declared ``[pack].name``
92
+ (falling back to the directory name) — the step the AC5 "unknown pack" path
93
+ depends on.
94
+ """
95
+ from agentbundle.commands.list_packs import _discover_pack_dirs
96
+ from agentbundle.config import ConfigError, load_pack_toml
97
+
98
+ for pack_dir in _discover_pack_dirs(catalogue_dir):
99
+ try:
100
+ toml = load_pack_toml(pack_dir / "pack.toml")
101
+ except ConfigError:
102
+ continue
103
+ # A declared ``[pack].name`` is authoritative (list-packs keys display
104
+ # on it); fall back to the directory name only when it is absent, so a
105
+ # coincidental dir-name match never shadows a pack declaring a different
106
+ # name.
107
+ name = toml.get("pack", {}).get("name") or pack_dir.name
108
+ if name == pack_name:
109
+ return pack_dir, toml
110
+ return None
111
+
112
+
113
+ # ---------------------------------------------------------------------------
114
+ # Degrade path — install-state fallback
115
+ # ---------------------------------------------------------------------------
116
+
117
+
118
+ def _degrade(args: "argparse.Namespace", pack_name: str, fmt: str) -> int:
119
+ """Recover the inventory from the install state when the catalogue is gone.
120
+
121
+ Reads both the user and repo scope (mirroring ``list-installed``). An
122
+ *installed* pack yields the union of skill/agent names across every adapter
123
+ row in both scopes, deduped + sorted (``source: installed-state``, metadata
124
+ null); a *not-installed* pack errors and exits non-zero.
125
+ """
126
+ states = _load_states(args)
127
+ if not any(state.has_pack(pack_name) for state in states):
128
+ print(
129
+ f"show: catalogue unavailable and pack {pack_name!r} is not installed",
130
+ file=sys.stderr,
131
+ )
132
+ return 1
133
+
134
+ skills: set[str] = set()
135
+ agents: set[str] = set()
136
+ for state in states:
137
+ for pack_state in state.rows_for_pack(pack_name).values():
138
+ for relpath in pack_state.files:
139
+ skill = _skill_from_relpath(relpath)
140
+ if skill:
141
+ skills.add(skill)
142
+ agent = _agent_from_relpath(relpath)
143
+ if agent:
144
+ agents.add(agent)
145
+
146
+ _emit(
147
+ fmt,
148
+ name=pack_name, # the argument passed to `show`; state has no pack name
149
+ version=None, # inventory-only fallback — never fabricated from state
150
+ description=None,
151
+ skills=sorted(skills),
152
+ agents=sorted(agents),
153
+ source="installed-state",
154
+ )
155
+ return 0
156
+
157
+
158
+ def _load_states(args: "argparse.Namespace") -> list["State"]:
159
+ """Load the user- and repo-scope install-state files, read-only.
160
+
161
+ Mirrors ``list-installed``'s two-scope gather: user
162
+ (``~/.agentbundle/state.toml`` via ``scope.resolve_user_root``) and repo
163
+ (``<--root>/.agentbundle-state.toml``). An unresolvable user home is skipped;
164
+ an absent state file loads as an empty ``State``; a legacy/incompatible file
165
+ (``StateFileLegacy`` — a ``ConfigError``) is skipped, not fatal.
166
+ """
167
+ from agentbundle import scope as scope_mod
168
+ from agentbundle.config import ConfigError, load_state
169
+
170
+ candidates: list[tuple[str, Path]] = []
171
+ try:
172
+ user_root = scope_mod.resolve_user_root()
173
+ except scope_mod.UserScopeUnresolvable:
174
+ pass
175
+ else:
176
+ candidates.append(("user", user_root / ".agentbundle" / "state.toml"))
177
+ repo_root = Path(getattr(args, "root", ".") or ".").resolve()
178
+ candidates.append(("repo", repo_root / ".agentbundle-state.toml"))
179
+
180
+ states: list["State"] = []
181
+ for scope_name, path in candidates:
182
+ try:
183
+ states.append(load_state(path))
184
+ except ConfigError as exc:
185
+ # Mirror `list-installed`: a legacy/incompatible state file is
186
+ # warned-and-skipped, not silently dropped — otherwise an installed
187
+ # pack whose only row lives there would masquerade as not-installed
188
+ # on this degrade path with no hint why.
189
+ print(f"show: skipping {scope_name} scope: {exc}", file=sys.stderr)
190
+ return states
191
+
192
+
193
+ def _skill_from_relpath(relpath: str) -> str | None:
194
+ """Skill name = the path segment immediately after a ``skills/`` component.
195
+
196
+ Layout-agnostic across ``.claude/skills/``, the shared ``.agents/skills/``
197
+ (codex/copilot/cursor/gemini), and ``.kiro/skills/``. Any projected file
198
+ under ``skills/<name>/`` maps to ``<name>``; the caller dedupes.
199
+ """
200
+ parts = Path(relpath).parts
201
+ for i, part in enumerate(parts):
202
+ if part == "skills" and i + 1 < len(parts):
203
+ return parts[i + 1]
204
+ return None
205
+
206
+
207
+ def _agent_from_relpath(relpath: str) -> str | None:
208
+ """Agent name = the filename directly under an ``agents/`` component, with
209
+ its extension stripped by an **extension-agnostic** rule.
210
+
211
+ Strip a trailing ``.agent.md`` (copilot's double-suffix) if present, else
212
+ ``Path.stem`` (the single final extension). Recovers ``<n>`` uniformly from
213
+ ``.claude/agents/<n>.md``, ``.codex/agents/<n>.toml``,
214
+ ``.kiro/agents/<n>.json``, and ``.github/agents/<n>.agent.md`` — so
215
+ co-installed adapter rows dedupe to one entry.
216
+ """
217
+ path = Path(relpath)
218
+ parts = path.parts
219
+ # A top-level ``agents/`` component only — never an ``agents/`` dir nested
220
+ # inside a skill's payload (``.../skills/<n>/agents/helper.md``), which would
221
+ # surface a phantom agent.
222
+ if len(parts) >= 2 and parts[-2] == "agents" and "skills" not in parts:
223
+ name = path.name
224
+ if name.endswith(".agent.md"):
225
+ return name[: -len(".agent.md")]
226
+ return path.stem
227
+ return None
228
+
229
+
230
+ # ---------------------------------------------------------------------------
231
+ # Rendering
232
+ # ---------------------------------------------------------------------------
233
+
234
+
235
+ def _emit(
236
+ fmt: str,
237
+ *,
238
+ name: str,
239
+ version: str | None,
240
+ description: str | None,
241
+ skills: list[str],
242
+ agents: list[str],
243
+ source: str,
244
+ ) -> None:
245
+ """Render the inventory as a table block or a single JSON object."""
246
+ if fmt == "json":
247
+ import json
248
+
249
+ obj = {
250
+ "name": name,
251
+ "version": version,
252
+ "description": description,
253
+ "skills": skills,
254
+ "agents": agents,
255
+ "source": source,
256
+ }
257
+ print(json.dumps(obj))
258
+ return
259
+
260
+ from agentbundle.commands._common import render_table
261
+
262
+ rows: list[list[str]] = [["name", name]]
263
+ # Fallback (installed-state) omits version/description — the state has neither.
264
+ if version is not None:
265
+ rows.append(["version", version])
266
+ if description is not None:
267
+ rows.append(["description", description])
268
+ rows.append(["skills", ", ".join(skills) if skills else "-"])
269
+ rows.append(["agents", ", ".join(agents) if agents else "-"])
270
+ render_table(["FIELD", "VALUE"], rows, wrap_col=1)
271
+ if source != "catalogue":
272
+ print(f"source: {source} (catalogue unavailable)")
@@ -0,0 +1,61 @@
1
+ """Live pack-inventory walk over a pack's ``.apm/`` source tree.
2
+
3
+ A pack's authoritative contents live under ``<pack>/.apm/``:
4
+
5
+ - skills at ``.apm/skills/<name>/SKILL.md`` → ``<name>``
6
+ - agents at ``.apm/agents/<name>.md`` → ``<name>``
7
+
8
+ This module is the single enumeration path for that tree (ADR-0049 /
9
+ RFC-0060 AC9): both ``agentbundle show`` and ``build/lint_packs`` walk it
10
+ through :func:`apm_entries`, so the directory traversal lives in one place.
11
+ The primitive returns **raw sorted entries** — each caller applies its own
12
+ filter (``show`` counts only ``SKILL.md``-bearing skill dirs and ``.md``
13
+ agent files; ``lint_packs`` lints every entry, ``SKILL.md`` or not).
14
+
15
+ Pure stdlib ``pathlib`` — no I/O beyond reading the directory tree, and
16
+ nothing is persisted (ADR-0049: derive live, persist nothing).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from pathlib import Path
22
+
23
+
24
+ def apm_entries(pack_dir: Path, subdir: str) -> list[Path]:
25
+ """Return the sorted direct children of ``<pack_dir>/.apm/<subdir>``.
26
+
27
+ Returns ``[]`` when the directory is absent (AC4) — a pack that ships no
28
+ skills, or no agents, is not an error. Sorted ascending by path so every
29
+ consumer sees a deterministic order. This is the shared raw walk (AC9);
30
+ callers filter the entries themselves.
31
+ """
32
+ d = pack_dir / ".apm" / subdir
33
+ if not d.is_dir():
34
+ return []
35
+ return sorted(d.iterdir())
36
+
37
+
38
+ def skill_names(pack_dir: Path) -> list[str]:
39
+ """Sorted names of ``.apm/skills/<name>/`` dirs that contain a ``SKILL.md``.
40
+
41
+ A skills subdirectory without a ``SKILL.md`` is not a skill (AC2); a
42
+ missing ``.apm/skills/`` yields ``[]`` (AC4).
43
+ """
44
+ return [
45
+ entry.name
46
+ for entry in apm_entries(pack_dir, "skills")
47
+ if entry.is_dir() and (entry / "SKILL.md").is_file()
48
+ ]
49
+
50
+
51
+ def agent_names(pack_dir: Path) -> list[str]:
52
+ """Sorted stems of ``.apm/agents/*.md`` files.
53
+
54
+ Non-``.md`` entries and subdirectories are ignored (AC2); a missing
55
+ ``.apm/agents/`` yields ``[]`` (AC4).
56
+ """
57
+ return [
58
+ entry.stem
59
+ for entry in apm_entries(pack_dir, "agents")
60
+ if entry.is_file() and entry.suffix == ".md"
61
+ ]
@@ -14,7 +14,7 @@ import tomllib
14
14
  from importlib.resources import files
15
15
  from pathlib import Path
16
16
 
17
- CLI_VERSION = "0.10.2"
17
+ CLI_VERSION = "0.11.0"
18
18
 
19
19
  _HERE = Path(__file__).resolve().parent
20
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentbundle
3
- Version: 0.10.2
3
+ Version: 0.11.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
@@ -55,6 +55,10 @@ The install auto-detects your agent (`--adapter` overrides). Multi-IDE? Install
55
55
  agentbundle list-packs
56
56
  agentbundle list-profiles
57
57
 
58
+ # See what a single pack contains — skills and agents, derived live from its tree
59
+ agentbundle show core
60
+ agentbundle show core --format json # stable object for scripts/agents
61
+
58
62
  # See what YOU have installed — pack, adapter, scope, version, and whether
59
63
  # an upgrade is available (both scopes by default)
60
64
  agentbundle list-installed
@@ -78,6 +82,8 @@ agentbundle uninstall --pack core --yes
78
82
 
79
83
  **`list-installed`** reads your state files (not the catalogue) and reports every installed `(pack, adapter)` at each scope with its version and an `up-to-date` / `upgrade-available` / `unknown` status; it degrades to `unknown` (never an error) when the catalogue can't be resolved, and `--no-check` skips the check entirely.
80
84
 
85
+ **`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.
86
+
81
87
  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).
82
88
 
83
89
  **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).
@@ -6,6 +6,7 @@ agentbundle/catalogue.py
6
6
  agentbundle/categories.py
7
7
  agentbundle/cli.py
8
8
  agentbundle/config.py
9
+ agentbundle/pack_inventory.py
9
10
  agentbundle/render.py
10
11
  agentbundle/safety.py
11
12
  agentbundle/scope.py
@@ -119,6 +120,7 @@ agentbundle/commands/profile.py
119
120
  agentbundle/commands/reconcile.py
120
121
  agentbundle/commands/render.py
121
122
  agentbundle/commands/scaffold.py
123
+ agentbundle/commands/show.py
122
124
  agentbundle/commands/uninstall.py
123
125
  agentbundle/commands/upgrade.py
124
126
  agentbundle/commands/validate.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentbundle"
7
- version = "0.10.2"
7
+ version = "0.11.0"
8
8
  description = "npm for your coding agent. Install packs of skills, subagents, and hooks into any repo, for every major agent."
9
9
  requires-python = ">=3.11"
10
10
  dependencies = []
File without changes