@event4u/agent-config 2.4.0 → 2.4.1
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/scripts/_cli/cmd_uninstall.py +16 -6
- package/scripts/_cli/cmd_update.py +7 -6
- package/scripts/_lib/claude_desktop_bundler.py +4 -4
- package/scripts/_lib/installed_lock.py +23 -4
- package/scripts/install.py +32 -26
package/CHANGELOG.md
CHANGED
|
@@ -343,6 +343,15 @@ our recommendation order, not its support status.
|
|
|
343
343
|
users" tension without removing any path that an existing user
|
|
344
344
|
might rely on.
|
|
345
345
|
|
|
346
|
+
## [2.4.1](https://github.com/event4u-app/agent-config/compare/2.4.0...2.4.1) (2026-05-13)
|
|
347
|
+
|
|
348
|
+
### Bug Fixes
|
|
349
|
+
|
|
350
|
+
* **install:** write lockfile to canonical event4u namespace ([ca57607](https://github.com/event4u-app/agent-config/commit/ca5760785f150903bcad74e278b59e534f83634d))
|
|
351
|
+
* **install:** source global deploy from .agent-src/ subdirectories ([f151caf](https://github.com/event4u-app/agent-config/commit/f151caf854d9ce8519bc244d441a7c8bf73e7fde))
|
|
352
|
+
|
|
353
|
+
Tests: 3521 (+0 since 2.4.0)
|
|
354
|
+
|
|
346
355
|
## [2.4.0](https://github.com/event4u-app/agent-config/compare/2.3.0...2.4.0) (2026-05-13)
|
|
347
356
|
|
|
348
357
|
### Features
|
package/package.json
CHANGED
|
@@ -422,6 +422,7 @@ def _uninstall_project(opts: argparse.Namespace) -> int:
|
|
|
422
422
|
|
|
423
423
|
def _uninstall_global(opts: argparse.Namespace) -> int:
|
|
424
424
|
lock_path = installed_lock.lockfile_path()
|
|
425
|
+
write_path = installed_lock.lockfile_write_path()
|
|
425
426
|
lock = installed_lock.read_lockfile(lock_path)
|
|
426
427
|
if lock is None and not opts.force:
|
|
427
428
|
print(f"❌ no global lockfile at {lock_path}", file=sys.stderr)
|
|
@@ -442,15 +443,24 @@ def _uninstall_global(opts: argparse.Namespace) -> int:
|
|
|
442
443
|
removed_names.append(tool)
|
|
443
444
|
if lock is not None and not opts.dry_run:
|
|
444
445
|
remaining = [t for t in lock.get("tools", []) if t not in tools]
|
|
446
|
+
version = lock.get("agent_config_version", "")
|
|
445
447
|
if remaining:
|
|
446
|
-
installed_lock.write_lockfile(remaining,
|
|
448
|
+
installed_lock.write_lockfile(version, remaining, path=write_path)
|
|
449
|
+
# Drop the legacy file if it differs from the canonical write
|
|
450
|
+
# target so the namespace migration completes on uninstall.
|
|
451
|
+
if lock_path != write_path:
|
|
452
|
+
try:
|
|
453
|
+
lock_path.unlink()
|
|
454
|
+
except OSError:
|
|
455
|
+
pass
|
|
447
456
|
print(f"✅ lockfile updated ({len(tools)} entries removed, {len(remaining)} kept)")
|
|
448
457
|
else:
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
458
|
+
for target in {lock_path, write_path}:
|
|
459
|
+
try:
|
|
460
|
+
target.unlink()
|
|
461
|
+
except OSError:
|
|
462
|
+
pass
|
|
463
|
+
print(f"✅ lockfile deleted ({write_path})")
|
|
454
464
|
return 0
|
|
455
465
|
|
|
456
466
|
|
|
@@ -243,17 +243,18 @@ def _refresh_global_lockfile(version: str, *, out=sys.stdout) -> None:
|
|
|
243
243
|
when ``update`` flips the pin. Atomic write goes through
|
|
244
244
|
``installed_lock.write_lockfile``.
|
|
245
245
|
"""
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
read_path = installed_lock.lockfile_path()
|
|
247
|
+
write_path = installed_lock.lockfile_write_path()
|
|
248
|
+
existing = installed_lock.read_lockfile(path=read_path)
|
|
248
249
|
if existing is None:
|
|
249
250
|
return
|
|
250
251
|
recorded = existing.get("agent_config_version")
|
|
251
252
|
tools = list(existing.get("tools", []))
|
|
252
|
-
if recorded == version:
|
|
253
|
-
print(f"ℹ️ {
|
|
253
|
+
if recorded == version and read_path == write_path:
|
|
254
|
+
print(f"ℹ️ {write_path} already records {version}.", file=out)
|
|
254
255
|
return
|
|
255
|
-
installed_lock.write_lockfile(version, tools, path=
|
|
256
|
-
print(f"✅ Refreshed global lockfile at {
|
|
256
|
+
installed_lock.write_lockfile(version, tools, path=write_path)
|
|
257
|
+
print(f"✅ Refreshed global lockfile at {write_path}.", file=out)
|
|
257
258
|
|
|
258
259
|
|
|
259
260
|
def _detect_installed_version() -> str:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Claude Desktop has no filesystem convention for skills; the Customize →
|
|
4
4
|
Skills UI accepts a ZIP per skill via the Upload button. This module
|
|
5
|
-
walks ``<package_root>/.
|
|
5
|
+
walks ``<package_root>/.agent-src/skills/*`` and produces one
|
|
6
6
|
``<skill-name>.zip`` per directory into ``dest_dir``.
|
|
7
7
|
|
|
8
8
|
Contract:
|
|
@@ -51,8 +51,8 @@ def _walk_skill_files(skill_dir: Path) -> list[tuple[Path, tuple[str, ...]]]:
|
|
|
51
51
|
"""Return ``[(abs_path, rel_parts), ...]`` for every file in the skill.
|
|
52
52
|
|
|
53
53
|
Symlinks are followed (``os.walk(..., followlinks=True)``) so a
|
|
54
|
-
bundle from a symlinked entry under ``.
|
|
55
|
-
actual target content, not a dangling symlink.
|
|
54
|
+
bundle from a symlinked entry under ``.agent-src/skills/`` contains
|
|
55
|
+
the actual target content, not a dangling symlink.
|
|
56
56
|
"""
|
|
57
57
|
out: list[tuple[Path, tuple[str, ...]]] = []
|
|
58
58
|
resolved = skill_dir.resolve()
|
|
@@ -121,7 +121,7 @@ def build_skill_bundles(
|
|
|
121
121
|
``curation`` optionally restricts the build to the given skill
|
|
122
122
|
names; ``None`` bundles every skill folder containing ``SKILL.md``.
|
|
123
123
|
"""
|
|
124
|
-
skills_root = package_root / ".
|
|
124
|
+
skills_root = package_root / ".agent-src" / "skills"
|
|
125
125
|
if not skills_root.is_dir():
|
|
126
126
|
return []
|
|
127
127
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -50,7 +50,7 @@ _TOOL_RE = re.compile(r"^\s*-\s*([A-Za-z0-9_\-.]+)\s*$")
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def lockfile_path(env: Optional[dict] = None) -> Path:
|
|
53
|
-
"""Return the active lockfile path
|
|
53
|
+
"""Return the active lockfile path for **reads**, honoring overrides.
|
|
54
54
|
|
|
55
55
|
Resolution order:
|
|
56
56
|
|
|
@@ -59,9 +59,10 @@ def lockfile_path(env: Optional[dict] = None) -> Path:
|
|
|
59
59
|
3. ``~/.config/agent-config/installed.lock`` (legacy fallback, read-only).
|
|
60
60
|
4. Canonical write target under the new namespace (Step 2 fallthrough).
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
Readers benefit from (3) so pre-2.4 installs keep working while the
|
|
63
|
+
migration shim has not yet run. Writers must use
|
|
64
|
+
:func:`lockfile_write_path` so a stale legacy file does not anchor
|
|
65
|
+
subsequent writes to the deprecated location.
|
|
65
66
|
"""
|
|
66
67
|
env = env if env is not None else os.environ
|
|
67
68
|
override = env.get(LOCKFILE_ENV)
|
|
@@ -73,6 +74,24 @@ def lockfile_path(env: Optional[dict] = None) -> Path:
|
|
|
73
74
|
return user_global_paths.write_target("installed.lock", env=env)
|
|
74
75
|
|
|
75
76
|
|
|
77
|
+
def lockfile_write_path(env: Optional[dict] = None) -> Path:
|
|
78
|
+
"""Return the canonical write target for the lockfile.
|
|
79
|
+
|
|
80
|
+
Unlike :func:`lockfile_path`, this never falls back to the legacy
|
|
81
|
+
``~/.config/agent-config/`` location. Honors the
|
|
82
|
+
``$AGENT_CONFIG_INSTALLED_LOCK`` override for tests, otherwise pins
|
|
83
|
+
to ``~/.event4u/agent-config/installed.lock``. Callers in
|
|
84
|
+
``init``, ``update``, and ``uninstall`` use this so writes always
|
|
85
|
+
land in the new namespace regardless of whether a stale legacy
|
|
86
|
+
lockfile is still present.
|
|
87
|
+
"""
|
|
88
|
+
env = env if env is not None else os.environ
|
|
89
|
+
override = env.get(LOCKFILE_ENV)
|
|
90
|
+
if override:
|
|
91
|
+
return Path(override).expanduser()
|
|
92
|
+
return user_global_paths.write_target("installed.lock", env=env)
|
|
93
|
+
|
|
94
|
+
|
|
76
95
|
def read_lockfile(path: Optional[Path] = None) -> Optional[dict]:
|
|
77
96
|
"""Parse ``path`` (or the default) into a dict; return ``None`` if absent.
|
|
78
97
|
|
package/scripts/install.py
CHANGED
|
@@ -2092,36 +2092,41 @@ PROJECT_BRIDGE_MARKERS = {
|
|
|
2092
2092
|
# ``_write_claude_desktop_marker`` rather than via this map.
|
|
2093
2093
|
#
|
|
2094
2094
|
# Tools that follow the markdown-skills convention (anchors lifted from
|
|
2095
|
-
# nextlevelbuilder/ui-ux-pro-max-skill) deploy
|
|
2096
|
-
#
|
|
2097
|
-
#
|
|
2095
|
+
# nextlevelbuilder/ui-ux-pro-max-skill) deploy the universal Anthropic-
|
|
2096
|
+
# shaped skill bundle — sourced from ``.agent-src/`` (the npm-shipped
|
|
2097
|
+
# canonical asset tree) — into ``<anchor>/skills/`` (or
|
|
2098
|
+
# ``<anchor>/steering/`` for kiro). ``.agent-src/rules`` is also copied
|
|
2098
2099
|
# where the destination is a true rules-aware tool root.
|
|
2100
|
+
#
|
|
2101
|
+
# All source paths reference ``.agent-src/<subdir>`` because that is the
|
|
2102
|
+
# only asset tree included in the npm tarball (see ``package.json#files``).
|
|
2103
|
+
# The legacy ``.augment/``, ``.claude/``, ``.cursor/`` projections only
|
|
2104
|
+
# exist in the development checkout — they are not shipped.
|
|
2099
2105
|
_CLAUDE_SKILL_BUNDLE: list[tuple[str, str]] = [
|
|
2100
|
-
(".
|
|
2101
|
-
(".
|
|
2102
|
-
(".
|
|
2106
|
+
(".agent-src/rules", "rules"),
|
|
2107
|
+
(".agent-src/skills", "skills"),
|
|
2108
|
+
(".agent-src/personas", "personas"),
|
|
2103
2109
|
]
|
|
2104
2110
|
GLOBAL_DEPLOY_SOURCES: dict[str, list[tuple[str, str]]] = {
|
|
2105
2111
|
"claude-code": _CLAUDE_SKILL_BUNDLE,
|
|
2106
2112
|
"augment": [
|
|
2107
|
-
(".
|
|
2108
|
-
(".
|
|
2109
|
-
(".
|
|
2110
|
-
(".
|
|
2111
|
-
(".
|
|
2112
|
-
(".
|
|
2113
|
+
(".agent-src/rules", "rules"),
|
|
2114
|
+
(".agent-src/skills", "skills"),
|
|
2115
|
+
(".agent-src/commands", "commands"),
|
|
2116
|
+
(".agent-src/contexts", "contexts"),
|
|
2117
|
+
(".agent-src/personas", "personas"),
|
|
2118
|
+
(".agent-src/templates", "templates"),
|
|
2113
2119
|
],
|
|
2114
2120
|
"cursor": [
|
|
2115
|
-
(".
|
|
2116
|
-
(".
|
|
2117
|
-
(".
|
|
2121
|
+
(".agent-src/rules", "rules"),
|
|
2122
|
+
(".agent-src/commands", "commands"),
|
|
2123
|
+
(".agent-src/personas", "personas"),
|
|
2118
2124
|
],
|
|
2119
2125
|
"windsurf": [
|
|
2120
|
-
(".
|
|
2121
|
-
(".windsurf/workflows", "workflows"),
|
|
2126
|
+
(".agent-src/rules", "rules"),
|
|
2122
2127
|
],
|
|
2123
2128
|
"cline": [
|
|
2124
|
-
(".
|
|
2129
|
+
(".agent-src/rules", ""),
|
|
2125
2130
|
],
|
|
2126
2131
|
# Markdown-skills tools — mirror the universal skill bundle into the
|
|
2127
2132
|
# tool-specific anchor. Subpath matches the reference repo's
|
|
@@ -2142,9 +2147,9 @@ GLOBAL_DEPLOY_SOURCES: dict[str, list[tuple[str, str]]] = {
|
|
|
2142
2147
|
# Kiro reads from `steering/` not `skills/` (per
|
|
2143
2148
|
# platforms/kiro.json#folderStructure.skillPath).
|
|
2144
2149
|
"kiro": [
|
|
2145
|
-
(".
|
|
2146
|
-
(".
|
|
2147
|
-
(".
|
|
2150
|
+
(".agent-src/rules", "rules"),
|
|
2151
|
+
(".agent-src/skills", "steering"),
|
|
2152
|
+
(".agent-src/personas", "personas"),
|
|
2148
2153
|
],
|
|
2149
2154
|
}
|
|
2150
2155
|
|
|
@@ -2988,14 +2993,15 @@ def install_global(
|
|
|
2988
2993
|
|
|
2989
2994
|
lock_mod = _load_installed_lock_module()
|
|
2990
2995
|
installed_version = lock_mod.current_package_version()
|
|
2991
|
-
|
|
2992
|
-
|
|
2996
|
+
read_path = lock_mod.lockfile_path()
|
|
2997
|
+
write_path = lock_mod.lockfile_write_path()
|
|
2998
|
+
ok, recorded = lock_mod.check_version(installed_version, path=read_path)
|
|
2993
2999
|
|
|
2994
3000
|
if not ok and not force:
|
|
2995
3001
|
if not QUIET:
|
|
2996
3002
|
print()
|
|
2997
3003
|
warn("Refusing global install: lockfile version mismatch.")
|
|
2998
|
-
info(f" Lockfile: {
|
|
3004
|
+
info(f" Lockfile: {read_path}")
|
|
2999
3005
|
info(f" Recorded version: {recorded}")
|
|
3000
3006
|
info(f" Current package: {installed_version}")
|
|
3001
3007
|
info(" Fix: run `agent-config update`")
|
|
@@ -3013,10 +3019,10 @@ def install_global(
|
|
|
3013
3019
|
continue
|
|
3014
3020
|
print(f" {tool_id:<15} → {anchor}")
|
|
3015
3021
|
|
|
3016
|
-
existing = lock_mod.read_lockfile(path=
|
|
3022
|
+
existing = lock_mod.read_lockfile(path=read_path) or {}
|
|
3017
3023
|
existing_tools = list(existing.get("tools", []))
|
|
3018
3024
|
merged_tools = sorted(set(existing_tools) | set(tools))
|
|
3019
|
-
written = lock_mod.write_lockfile(installed_version, merged_tools, path=
|
|
3025
|
+
written = lock_mod.write_lockfile(installed_version, merged_tools, path=write_path)
|
|
3020
3026
|
|
|
3021
3027
|
if not QUIET:
|
|
3022
3028
|
print()
|