agentbundle 0.12.0__tar.gz → 0.12.1__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.12.0 → agentbundle-0.12.1}/PKG-INFO +1 -1
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/_common.py +49 -33
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/diff.py +3 -3
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/install.py +24 -24
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/uninstall.py +3 -2
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/upgrade.py +25 -21
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/safety.py +38 -22
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/version.py +1 -1
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle.egg-info/PKG-INFO +1 -1
- {agentbundle-0.12.0 → agentbundle-0.12.1}/pyproject.toml +1 -1
- {agentbundle-0.12.0 → agentbundle-0.12.1}/README.md +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/__init__.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/__main__.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/adapter.schema.json +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/adapter.toml +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/install-defaults.toml +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/install-marker.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/pack.schema.json +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/plugin-manifest.derived.schema.json +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/plugin-manifest.schema.json +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/profile.schema.json +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/__init__.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/__main__.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapter_root_bins.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/__init__.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/claude_code.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/codex.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/copilot.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/cursor.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/gemini.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/kiro.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/kiro_cli.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/adapters/kiro_ide.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/contract.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/lint_packs.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/main.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/phase_order.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/__init__.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/codex_agent_toml.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/copilot_agent_md.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/copilot_hooks_json.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/direct_directory.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/gemini_command_toml.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/hook_id.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/kiro_ide_hook.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/merge_into_agent_json.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/merge_json.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/user_merge_json.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/scope_rails.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/self_host.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/shared_libs.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/target_resolver.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/__init__.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_claude_code.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_codex.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_copilot.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_cursor.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_gemini.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_kiro.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_kiro_alias.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_kiro_cli.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_kiro_ide.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_root_bins_projection.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_architect_design_reviewer_projection.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_architect_design_reviewer_rubric_parity.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_build_ships_seeds.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_contract.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_contract_scope.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_contract_v07.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_contract_v08.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_direct_directory_cleanup.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_end_to_end_build.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_lint_agents_md_diataxis_block.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_lint_agents_md_legacy_block.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_lint_agents_md_risk_block.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_lint_packs.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_load_pack_hook_wiring_safely.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_pack_schema.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_pack_schema_allowed_adapters.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_pack_schema_install.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_pipeline.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_plugin_manifest_schema.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_projectable_subset.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_projections_merge_json.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_scope_rails.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_security.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_self_host_check.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_self_host_fixture_guard.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_self_host_recipe_config.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_shared_libs_projection.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_shared_prefix_contract.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_shipped_packs_v07_declarations.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_shipped_packs_v08_declarations.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_user_libs_projection.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_validate.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_writers_emit_lf.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/user_libs.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/validate.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/catalogue.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/categories.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/cli.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/__init__.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/_drop_warning.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/adapt.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/config.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/init_state.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/list_installed.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/list_packs.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/list_profiles.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/list_targets.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/profile.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/reconcile.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/render.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/scaffold.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/show.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/commands/validate.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/config.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/pack_inventory.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/render.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/scope.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/source_defaults.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/statelock.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/user_config.py +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle.egg-info/SOURCES.txt +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle.egg-info/dependency_links.txt +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle.egg-info/entry_points.txt +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle.egg-info/top_level.txt +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/setup.cfg +0 -0
- {agentbundle-0.12.0 → agentbundle-0.12.1}/tests/test_linear_primitive.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentbundle
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.1
|
|
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
|
|
@@ -40,6 +40,17 @@ def resolve_catalogue_uri(args: "argparse.Namespace") -> str:
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
def resolve_state_path(scope: str, root: Path) -> Path:
|
|
44
|
+
"""Return the state-file path for *scope* under *root*.
|
|
45
|
+
|
|
46
|
+
``scope="repo"`` → ``<root>/.agentbundle-state.toml``
|
|
47
|
+
``scope="user"`` → ``<root>/.agentbundle/state.toml``
|
|
48
|
+
"""
|
|
49
|
+
if scope == "user":
|
|
50
|
+
return root / ".agentbundle" / "state.toml"
|
|
51
|
+
return root / ".agentbundle-state.toml"
|
|
52
|
+
|
|
53
|
+
|
|
43
54
|
class SeedDelivery(NamedTuple):
|
|
44
55
|
"""One seed file's delivery outcome, returned by ``deliver_seeds``.
|
|
45
56
|
|
|
@@ -77,44 +88,20 @@ def _compose_agents_md_bytes(body: bytes, footer_path: Path) -> bytes:
|
|
|
77
88
|
return (text + footer).encode("utf-8")
|
|
78
89
|
|
|
79
90
|
|
|
80
|
-
def
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
For each file under ``seeds_dir`` (recursively):
|
|
84
|
-
- **Composition fragments** (name starts with ``_``, e.g.
|
|
85
|
-
``_agents-footer.md``) are *not* delivered standalone — they are folded
|
|
86
|
-
into ``AGENTS.md`` instead (per ``CONVENTIONS.md`` §Pack source-of-truth split).
|
|
87
|
-
- **Absent on disk** → write the seed (Tier-1).
|
|
88
|
-
- **Present, content matches** → no-op (already in sync).
|
|
89
|
-
- **Present, content differs** → write a ``*.upstream.<ext>`` companion
|
|
90
|
-
next to the original; leave the original untouched (Tier-2).
|
|
91
|
-
|
|
92
|
-
Every write routes through ``safety.write_jailed`` / ``safety.write_companion``
|
|
93
|
-
with the **bare under-root jail** (no ``allowed_prefixes`` — seeds land at the
|
|
94
|
-
repo root and ``docs/``, outside the adapter projection prefixes). The caller
|
|
95
|
-
decides whether to record state; this helper never writes ``.agentbundle-state.toml``.
|
|
91
|
+
def _classify_seeds(seeds_dir: Path, root: Path) -> list[SeedDelivery]:
|
|
92
|
+
"""Classify seeds without writing — pure read-only; same walk logic as deliver_seeds.
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
Returns a list of SeedDelivery records with action 'wrote' (absent on disk),
|
|
95
|
+
'skipped' (byte-identical), or 'companion' (differs — Tier-2). No filesystem
|
|
96
|
+
writes occur under root.
|
|
99
97
|
"""
|
|
100
98
|
import os
|
|
101
99
|
|
|
102
100
|
from agentbundle import safety
|
|
103
101
|
|
|
104
102
|
footer_path = seeds_dir / "_agents-footer.md"
|
|
105
|
-
# Guard the footer read too — ``_compose_agents_md_bytes`` reads
|
|
106
|
-
# ``footer_path`` directly, so a symlinked footer would be read through.
|
|
107
103
|
footer_ok = footer_path.is_file() and not footer_path.is_symlink()
|
|
108
104
|
|
|
109
|
-
# Defence-in-depth against a malicious pack exfiltrating a host file
|
|
110
|
-
# (``/etc/passwd``, ``~/.ssh/id_rsa``) into the adopter tree by symlinking
|
|
111
|
-
# a seed — never read *through* a pack-shipped symlink. We must not rely on
|
|
112
|
-
# ``Path.rglob``'s symlink posture: on Python 3.11/3.12 ``rglob`` recurses
|
|
113
|
-
# *into* symlinked directories (3.13 changed the default to
|
|
114
|
-
# ``recurse_symlinks=False``), so ``seeds/x -> /`` would surface real host
|
|
115
|
-
# files as non-symlink entries. ``os.walk(followlinks=False)`` never
|
|
116
|
-
# descends into a symlinked directory on any supported Python, and we also
|
|
117
|
-
# skip symlinked files — closing both the file and directory cases.
|
|
118
105
|
seed_files: list[Path] = []
|
|
119
106
|
for dirpath, _dirnames, filenames in os.walk(seeds_dir, followlinks=False):
|
|
120
107
|
for fname in filenames:
|
|
@@ -125,7 +112,6 @@ def deliver_seeds(seeds_dir: Path, output: Path) -> list[SeedDelivery]:
|
|
|
125
112
|
|
|
126
113
|
results: list[SeedDelivery] = []
|
|
127
114
|
for seed_file in sorted(seed_files):
|
|
128
|
-
# Composition fragments are folded in, never delivered standalone.
|
|
129
115
|
if seed_file.name.startswith("_"):
|
|
130
116
|
continue
|
|
131
117
|
relpath = seed_file.relative_to(seeds_dir).as_posix()
|
|
@@ -133,19 +119,49 @@ def deliver_seeds(seeds_dir: Path, output: Path) -> list[SeedDelivery]:
|
|
|
133
119
|
if relpath == "AGENTS.md" and footer_ok:
|
|
134
120
|
content = _compose_agents_md_bytes(content, footer_path)
|
|
135
121
|
|
|
136
|
-
on_disk =
|
|
122
|
+
on_disk = root / relpath
|
|
137
123
|
if not on_disk.exists():
|
|
138
|
-
safety.write_jailed(output, relpath, content)
|
|
139
124
|
results.append(SeedDelivery(relpath, content, "wrote", None))
|
|
140
125
|
elif on_disk.read_bytes() == content:
|
|
141
126
|
results.append(SeedDelivery(relpath, content, "skipped", None))
|
|
142
127
|
else:
|
|
143
|
-
safety.write_companion(output, relpath, content)
|
|
144
128
|
companion = safety.companion_path(Path(relpath)).as_posix()
|
|
145
129
|
results.append(SeedDelivery(relpath, content, "companion", companion))
|
|
146
130
|
return results
|
|
147
131
|
|
|
148
132
|
|
|
133
|
+
def deliver_seeds(seeds_dir: Path, output: Path) -> list[SeedDelivery]:
|
|
134
|
+
"""Deliver a pack's ``seeds/`` into ``output`` with Tier-1/2/3 safety.
|
|
135
|
+
|
|
136
|
+
For each file under ``seeds_dir`` (recursively):
|
|
137
|
+
- **Composition fragments** (name starts with ``_``, e.g.
|
|
138
|
+
``_agents-footer.md``) are *not* delivered standalone — they are folded
|
|
139
|
+
into ``AGENTS.md`` instead (per ``CONVENTIONS.md`` §Pack source-of-truth split).
|
|
140
|
+
- **Absent on disk** → write the seed (Tier-1).
|
|
141
|
+
- **Present, content matches** → no-op (already in sync).
|
|
142
|
+
- **Present, content differs** → write a ``*.upstream.<ext>`` companion
|
|
143
|
+
next to the original; leave the original untouched (Tier-2).
|
|
144
|
+
|
|
145
|
+
Every write routes through ``safety.write_jailed`` / ``safety.write_companion``
|
|
146
|
+
with the **bare under-root jail** (no ``allowed_prefixes`` — seeds land at the
|
|
147
|
+
repo root and ``docs/``, outside the adapter projection prefixes). The caller
|
|
148
|
+
decides whether to record state; this helper never writes ``.agentbundle-state.toml``.
|
|
149
|
+
|
|
150
|
+
Raises ``safety.PathJailError`` if any seed relpath would escape ``output``;
|
|
151
|
+
the caller is expected to catch it, print to stderr, and exit 1.
|
|
152
|
+
"""
|
|
153
|
+
from agentbundle import safety
|
|
154
|
+
|
|
155
|
+
records = _classify_seeds(seeds_dir, output)
|
|
156
|
+
for record in records:
|
|
157
|
+
if record.action == "wrote":
|
|
158
|
+
safety.write_jailed(output, record.relpath, record.content)
|
|
159
|
+
elif record.action == "companion":
|
|
160
|
+
safety.write_companion(output, record.relpath, record.content)
|
|
161
|
+
# "skipped" → no-op
|
|
162
|
+
return records
|
|
163
|
+
|
|
164
|
+
|
|
149
165
|
def check_spec_version_gate(pack_toml: dict[str, Any]) -> int | None:
|
|
150
166
|
"""Refuse if the pack's declared spec major version differs from ours.
|
|
151
167
|
|
|
@@ -22,7 +22,7 @@ import sys
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
24
|
from agentbundle import render, safety
|
|
25
|
-
from agentbundle.commands._common import check_spec_version_gate
|
|
25
|
+
from agentbundle.commands._common import check_spec_version_gate, resolve_state_path
|
|
26
26
|
from agentbundle.config import ConfigError, load_pack_toml
|
|
27
27
|
|
|
28
28
|
|
|
@@ -57,7 +57,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
57
57
|
# hard cross-version refusal); surface it as a clean refuse rather
|
|
58
58
|
# than a traceback.
|
|
59
59
|
try:
|
|
60
|
-
repo_state_for_check = load_state(
|
|
60
|
+
repo_state_for_check = load_state(resolve_state_path("repo", root))
|
|
61
61
|
except ConfigError as exc:
|
|
62
62
|
print(f"diff: {exc}", file=sys.stderr)
|
|
63
63
|
return 1
|
|
@@ -65,7 +65,7 @@ def run(args: argparse.Namespace) -> int:
|
|
|
65
65
|
try:
|
|
66
66
|
user_root_resolved = scope_mod.resolve_user_root()
|
|
67
67
|
user_state_for_check = load_state(
|
|
68
|
-
|
|
68
|
+
resolve_state_path("user", user_root_resolved)
|
|
69
69
|
)
|
|
70
70
|
installed_at_user = user_state_for_check.has_pack(pack_name)
|
|
71
71
|
except scope_mod.UserScopeUnresolvable:
|
|
@@ -892,30 +892,13 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
892
892
|
projection = repo_projection if plan.scope == "repo" else user_projection
|
|
893
893
|
if projection is None:
|
|
894
894
|
continue
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
# Per-prefix probe fires whenever the plan has an
|
|
903
|
-
# allowed_prefixes list (user scope always; repo scope when
|
|
904
|
-
# the per-IDE path is in use — RFC-0012). With
|
|
905
|
-
# `allowed_prefixes=None` the probe is skipped (legacy
|
|
906
|
-
# dist-tree producer at repo scope under
|
|
907
|
-
# --emit-install-routes).
|
|
908
|
-
if plan.allowed_prefixes is not None:
|
|
909
|
-
target_relpath = target.resolve().relative_to(plan.root.resolve()).as_posix()
|
|
910
|
-
prefixes = plan.allowed_prefixes or []
|
|
911
|
-
# Directory-boundary matching only — see safety.py.
|
|
912
|
-
if not any(target_relpath.startswith(p) for p in prefixes):
|
|
913
|
-
print(
|
|
914
|
-
f"install: refusing to write outside allowed prefixes "
|
|
915
|
-
f"for scope {plan.scope!r}: {target.resolve()}",
|
|
916
|
-
file=sys.stderr,
|
|
917
|
-
)
|
|
918
|
-
return 1
|
|
895
|
+
try:
|
|
896
|
+
safety.assert_projection_jailed(
|
|
897
|
+
plan.root, projection.keys(), plan.allowed_prefixes, command="install"
|
|
898
|
+
)
|
|
899
|
+
except safety.PathJailError as exc:
|
|
900
|
+
print(str(exc), file=sys.stderr)
|
|
901
|
+
return 1
|
|
919
902
|
|
|
920
903
|
# ── Dry-run: all read-only pre-flight passed — preview and stop ───────────
|
|
921
904
|
# At the top of Step 9 (after Step 8's path-jail probe) so every pre-flight
|
|
@@ -927,6 +910,8 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
927
910
|
# recap). `--force` is refused up front, so there is exactly one writing
|
|
928
911
|
# plan here (dual-scope writes arise only under --force).
|
|
929
912
|
if dry_run:
|
|
913
|
+
from agentbundle.commands._common import _classify_seeds
|
|
914
|
+
|
|
930
915
|
actions: list[str] = []
|
|
931
916
|
for plan in plans:
|
|
932
917
|
if plan.already_installed:
|
|
@@ -946,6 +931,21 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
946
931
|
)
|
|
947
932
|
print(format_plan_line(action, tier.value, relpath, companion))
|
|
948
933
|
actions.append(action)
|
|
934
|
+
if plan.scope == "repo":
|
|
935
|
+
seeds_dir = pack_dir / "seeds"
|
|
936
|
+
if seeds_dir.is_dir():
|
|
937
|
+
for record in _classify_seeds(seeds_dir, plan.root):
|
|
938
|
+
if record.action == "skipped":
|
|
939
|
+
continue
|
|
940
|
+
verb = "create" if record.action == "wrote" else "companion"
|
|
941
|
+
tier_str = "tier-1" if record.action == "wrote" else "tier-2"
|
|
942
|
+
path_str = (
|
|
943
|
+
record.relpath
|
|
944
|
+
if record.action == "wrote"
|
|
945
|
+
else f"{record.relpath} -> {record.companion_relpath}"
|
|
946
|
+
)
|
|
947
|
+
print(format_plan_line(verb, tier_str, path_str))
|
|
948
|
+
actions.append(verb)
|
|
949
949
|
print(summarize_plan(actions))
|
|
950
950
|
return 0
|
|
951
951
|
|
|
@@ -46,12 +46,13 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
46
46
|
"""
|
|
47
47
|
from agentbundle.config import ConfigError, dump_state, load_state
|
|
48
48
|
from agentbundle import safety
|
|
49
|
+
from agentbundle.commands._common import resolve_state_path
|
|
49
50
|
|
|
50
51
|
pack_name: str = args.pack
|
|
51
52
|
cli_scope: str | None = getattr(args, "scope", None)
|
|
52
53
|
cli_adapter: str | None = getattr(args, "adapter", None)
|
|
53
54
|
root = Path(args.root).resolve()
|
|
54
|
-
state_path =
|
|
55
|
+
state_path = resolve_state_path("repo", root)
|
|
55
56
|
|
|
56
57
|
# ── Step 1: Multi-scope disambiguator (RFC-0004) ──────────────────────────
|
|
57
58
|
# If the pack is at both repo and user scopes, --scope is required.
|
|
@@ -72,7 +73,7 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
72
73
|
user_state_for_check = None
|
|
73
74
|
try:
|
|
74
75
|
user_root = scope_mod.resolve_user_root()
|
|
75
|
-
user_state_path =
|
|
76
|
+
user_state_path = resolve_state_path("user", user_root)
|
|
76
77
|
user_state_for_check = load_state(user_state_path)
|
|
77
78
|
installed_at_user = user_state_for_check.has_pack(pack_name)
|
|
78
79
|
except scope_mod.UserScopeUnresolvable:
|
|
@@ -79,6 +79,7 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
79
79
|
format_plan_line,
|
|
80
80
|
plan_action,
|
|
81
81
|
resolve_catalogue_uri,
|
|
82
|
+
resolve_state_path,
|
|
82
83
|
summarize_plan,
|
|
83
84
|
)
|
|
84
85
|
from agentbundle.config import (
|
|
@@ -115,7 +116,7 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
115
116
|
# If the pack is at both scopes, --scope is required; at one scope, infer.
|
|
116
117
|
from agentbundle import scope as scope_mod
|
|
117
118
|
|
|
118
|
-
repo_state_path =
|
|
119
|
+
repo_state_path = resolve_state_path("repo", root)
|
|
119
120
|
# A legacy (non-v0.4) state file is refused on read too (RFC-0052);
|
|
120
121
|
# surface it as a clean refuse rather than a traceback.
|
|
121
122
|
try:
|
|
@@ -129,7 +130,7 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
129
130
|
user_state_for_check = None
|
|
130
131
|
try:
|
|
131
132
|
user_root_resolved = scope_mod.resolve_user_root()
|
|
132
|
-
user_state_path =
|
|
133
|
+
user_state_path = resolve_state_path("user", user_root_resolved)
|
|
133
134
|
user_state_for_check = load_state(user_state_path)
|
|
134
135
|
installed_at_user = user_state_for_check.has_pack(pack_name)
|
|
135
136
|
except scope_mod.UserScopeUnresolvable:
|
|
@@ -248,7 +249,7 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
248
249
|
if effective_scope == "user":
|
|
249
250
|
state_path = user_state_path # already resolved above
|
|
250
251
|
else:
|
|
251
|
-
state_path =
|
|
252
|
+
state_path = resolve_state_path("repo", root)
|
|
252
253
|
try:
|
|
253
254
|
state = load_state(state_path, for_write=True)
|
|
254
255
|
except ConfigError as exc:
|
|
@@ -541,24 +542,13 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
541
542
|
# the print loop (probe-all-then-print): a jail violation on a late file
|
|
542
543
|
# aborts with a clean stderr refusal instead of a partial plan already
|
|
543
544
|
# on stdout.
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if allowed_prefixes is not None:
|
|
552
|
-
target_relpath = (
|
|
553
|
-
target.resolve().relative_to(root.resolve()).as_posix()
|
|
554
|
-
)
|
|
555
|
-
if not any(target_relpath.startswith(p) for p in allowed_prefixes):
|
|
556
|
-
print(
|
|
557
|
-
f"upgrade: refusing to write outside allowed prefixes "
|
|
558
|
-
f"for scope {effective_scope!r}: {target.resolve()}",
|
|
559
|
-
file=sys.stderr,
|
|
560
|
-
)
|
|
561
|
-
return 1
|
|
545
|
+
try:
|
|
546
|
+
safety.assert_projection_jailed(
|
|
547
|
+
root, sorted(work_projection), allowed_prefixes, command="upgrade"
|
|
548
|
+
)
|
|
549
|
+
except safety.PathJailError as exc:
|
|
550
|
+
print(str(exc), file=sys.stderr)
|
|
551
|
+
return 1
|
|
562
552
|
actions: list[str] = []
|
|
563
553
|
for relpath in sorted(work_projection):
|
|
564
554
|
tier = safety.classify(relpath, root, state)
|
|
@@ -575,6 +565,20 @@ def run(args: "argparse.Namespace") -> int:
|
|
|
575
565
|
print(summarize_plan(actions))
|
|
576
566
|
return 0
|
|
577
567
|
|
|
568
|
+
# ── Path-jail pre-flight (AC4) — probe all before any write ──────────────
|
|
569
|
+
# Mirror the dry-run probe: refuse if any projected path escapes root or
|
|
570
|
+
# violates allowed_prefixes, before touching disk. Without this gate a
|
|
571
|
+
# prefix violation surfaces mid-loop via write_jailed, after earlier files
|
|
572
|
+
# have already been written — the probe-all-before-write contract means a
|
|
573
|
+
# violation is always a clean abort.
|
|
574
|
+
try:
|
|
575
|
+
safety.assert_projection_jailed(
|
|
576
|
+
root, sorted(work_projection), allowed_prefixes, command="upgrade"
|
|
577
|
+
)
|
|
578
|
+
except safety.PathJailError as exc:
|
|
579
|
+
print(str(exc), file=sys.stderr)
|
|
580
|
+
return 1
|
|
581
|
+
|
|
578
582
|
# ── Walk projection; apply Tier contract ──────────────────────────────────
|
|
579
583
|
# Collect `.upstream.<ext>` companions dropped this run so we can surface
|
|
580
584
|
# them after the walk. Without this notice the upgrade is silent on a
|
|
@@ -162,6 +162,33 @@ def assert_under(root: Path, target: Path) -> None:
|
|
|
162
162
|
) from exc
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
def assert_projection_jailed(
|
|
166
|
+
root: Path,
|
|
167
|
+
relpaths: "Iterable[str]",
|
|
168
|
+
allowed_prefixes: "list[str] | None",
|
|
169
|
+
*,
|
|
170
|
+
command: str,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Raise PathJailError if any relpath escapes root or violates allowed_prefixes.
|
|
173
|
+
|
|
174
|
+
When allowed_prefixes is None, only root-escape is checked.
|
|
175
|
+
root must be an absolute resolved Path.
|
|
176
|
+
The command parameter is prefixed on all PathJailError messages raised.
|
|
177
|
+
"""
|
|
178
|
+
for relpath in relpaths:
|
|
179
|
+
target = root / relpath
|
|
180
|
+
try:
|
|
181
|
+
assert_under(root, target)
|
|
182
|
+
except PathJailError as exc:
|
|
183
|
+
raise PathJailError(f"{command}: {exc}") from exc
|
|
184
|
+
if allowed_prefixes is not None:
|
|
185
|
+
target_relpath = target.resolve().relative_to(root.resolve()).as_posix()
|
|
186
|
+
if not any(target_relpath.startswith(p) for p in allowed_prefixes):
|
|
187
|
+
raise PathJailError(
|
|
188
|
+
f"{command}: path {relpath!r} is not within any declared prefix zone"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
165
192
|
# ---------------------------------------------------------------------------
|
|
166
193
|
# Windows-portability guard
|
|
167
194
|
# ---------------------------------------------------------------------------
|
|
@@ -320,31 +347,20 @@ def write_jailed(
|
|
|
320
347
|
assert_under(root, target)
|
|
321
348
|
|
|
322
349
|
if allowed_prefixes is not None:
|
|
323
|
-
#
|
|
324
|
-
# prefixes
|
|
325
|
-
# the
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
|
|
330
|
-
# Defense-in-depth: the adapter contract schema enforces a
|
|
331
|
-
# trailing slash on every `allowed-prefixes` entry; assert it
|
|
332
|
-
# at runtime so a future caller that bypasses the schema
|
|
333
|
-
# (e.g. constructing the list in code) cannot silently widen
|
|
334
|
-
# the jail. A `.claude` (no slash) prefix would otherwise
|
|
335
|
-
# admit `.claudefoo` — exactly the bug the equality-clause
|
|
336
|
-
# removal was meant to fix.
|
|
337
|
-
if not all(p.endswith("/") for p in prefixes):
|
|
350
|
+
# Defense-in-depth: the adapter contract schema enforces a trailing
|
|
351
|
+
# slash on every `allowed-prefixes` entry; assert it at runtime so a
|
|
352
|
+
# future caller that bypasses the schema (e.g. constructing the list in
|
|
353
|
+
# code) cannot silently widen the jail. A `.claude` (no slash) prefix
|
|
354
|
+
# would otherwise admit `.claudefoo` — exactly the bug the
|
|
355
|
+
# equality-clause removal was meant to fix.
|
|
356
|
+
if not all(p.endswith("/") for p in allowed_prefixes):
|
|
338
357
|
raise PathJailError(
|
|
339
358
|
f"refusing to write at scope {scope!r}: allowed_prefixes "
|
|
340
|
-
f"must each end with '/'; got {
|
|
341
|
-
)
|
|
342
|
-
target_relpath = target.resolve().relative_to(root.resolve()).as_posix()
|
|
343
|
-
if not any(target_relpath.startswith(p) for p in prefixes):
|
|
344
|
-
raise PathJailError(
|
|
345
|
-
f"refusing to write outside allowed prefixes for scope "
|
|
346
|
-
f"{scope!r}: {target.resolve()}"
|
|
359
|
+
f"must each end with '/'; got {allowed_prefixes!r}"
|
|
347
360
|
)
|
|
361
|
+
assert_projection_jailed(
|
|
362
|
+
root, [relpath], allowed_prefixes, command=f"scope={scope}"
|
|
363
|
+
)
|
|
348
364
|
try:
|
|
349
365
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
350
366
|
except OSError as exc:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentbundle
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.1
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentbundle"
|
|
7
|
-
version = "0.12.
|
|
7
|
+
version = "0.12.1"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/_data/plugin-manifest.derived.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/copilot_hooks_json.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/gemini_command_toml.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/projections/merge_into_agent_json.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_claude_code.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_adapter_kiro_alias.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_direct_directory_cleanup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_lint_agents_md_risk_block.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_pack_schema_install.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_plugin_manifest_schema.py
RENAMED
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_projectable_subset.py
RENAMED
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_projections_merge_json.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_self_host_fixture_guard.py
RENAMED
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_self_host_recipe_config.py
RENAMED
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_shared_libs_projection.py
RENAMED
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_shared_prefix_contract.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentbundle-0.12.0 → agentbundle-0.12.1}/agentbundle/build/tests/test_user_libs_projection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|