@event4u/agent-config 2.3.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/.agent-src/commands/onboard.md +14 -9
- package/.agent-src/skills/ai-council/SKILL.md +5 -3
- package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -1
- package/.agent-src/templates/agents/agent-project-settings.example.yml +4 -3
- package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +29 -7
- package/.agent-src/templates/scripts/work_engine/_lib/user_global_paths.py +249 -0
- package/.agent-src/templates/scripts/work_engine/hooks/settings.py +8 -5
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +39 -0
- package/config/agent-settings.template.yml +5 -3
- package/docs/catalog.md +5 -3
- package/docs/contracts/installed-tools-lockfile.md +4 -0
- package/docs/customization.md +23 -17
- package/docs/decisions/ADR-007-agent-discovery-scopes.md +6 -0
- package/docs/decisions/ADR-009-event4u-namespace.md +188 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/guidelines/agent-infra/installed-tools-manifest.md +1 -1
- package/docs/guidelines/agent-infra/layered-settings.md +6 -4
- package/docs/installation.md +3 -2
- package/docs/migration/v1-to-v2.md +45 -0
- package/docs/setup/per-ide/claude-desktop.md +107 -65
- package/package.json +1 -1
- package/scripts/_cli/cmd_uninstall.py +17 -7
- package/scripts/_cli/cmd_update.py +11 -7
- package/scripts/_lib/agent_settings.py +29 -7
- package/scripts/_lib/agents_overlay.py +15 -4
- package/scripts/_lib/claude_desktop_bundler.py +150 -0
- package/scripts/_lib/installed_lock.py +56 -4
- package/scripts/_lib/installed_tools.py +1 -1
- package/scripts/_lib/update_check.py +29 -5
- package/scripts/_lib/user_global_paths.py +249 -0
- package/scripts/ai_council/__init__.py +4 -3
- package/scripts/ai_council/budget_guard.py +34 -4
- package/scripts/ai_council/bundler.py +2 -0
- package/scripts/ai_council/clients.py +28 -7
- package/scripts/install.py +149 -49
- package/scripts/install_anthropic_key.sh +5 -3
- package/scripts/install_openai_key.sh +5 -3
- package/scripts/skill_trigger_eval.py +13 -2
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
|
|
|
@@ -2158,17 +2163,34 @@ _CLAUDE_DESKTOP_MARKER_TEMPLATE = """\
|
|
|
2158
2163
|
|
|
2159
2164
|
Installed by `@event4u/agent-config` (user scope, ADR-007).
|
|
2160
2165
|
|
|
2161
|
-
- Lockfile:
|
|
2162
|
-
- Anchor:
|
|
2166
|
+
- Lockfile: `{lockfile}`
|
|
2167
|
+
- Anchor: `{anchor}`
|
|
2168
|
+
- Skill bundles: `{bundles_dir}` ({bundle_count} ZIPs)
|
|
2169
|
+
|
|
2170
|
+
## Import skills into Claude Desktop
|
|
2171
|
+
|
|
2172
|
+
Claude Desktop has no filesystem skill-discovery convention — skills are
|
|
2173
|
+
imported manually via the Customize → Skills UI.
|
|
2174
|
+
|
|
2175
|
+
1. Open Claude Desktop → **Settings → Customize → Skills**.
|
|
2176
|
+
2. Click the **Upload skill** button.
|
|
2177
|
+
3. Browse to `{bundles_dir}` and pick the `<skill-name>.zip` files you
|
|
2178
|
+
want to install. One ZIP = one skill.
|
|
2179
|
+
4. Repeat per skill. Claude Desktop keeps each upload until you remove it.
|
|
2163
2180
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
`~/.cursor/`, `~/.codeium/windsurf/`, `~/Documents/Cline/Rules/`).
|
|
2181
|
+
The bundle directory is regenerated on every
|
|
2182
|
+
`npx @event4u/agent-config init --tools=claude-desktop` run (only
|
|
2183
|
+
changed skills are rewritten — content-hash idempotency).
|
|
2168
2184
|
|
|
2169
2185
|
To remove this marker, delete this file.
|
|
2170
2186
|
"""
|
|
2171
2187
|
|
|
2188
|
+
#: Subpath under ``~/.event4u/agent-config/`` where Claude Desktop ZIP
|
|
2189
|
+
#: bundles are written. Kept separate from the per-tool USER_SCOPE_PATHS
|
|
2190
|
+
#: anchor (which is the Claude Desktop config dir) because bundles are
|
|
2191
|
+
#: package-owned, not Claude-owned, content.
|
|
2192
|
+
_CLAUDE_DESKTOP_BUNDLES_SUBPATH = "claude-desktop/bundles"
|
|
2193
|
+
|
|
2172
2194
|
|
|
2173
2195
|
def _bridge_marker(tool_id: str, scope: str) -> str:
|
|
2174
2196
|
"""Return the canonical bridge-marker path for ``(tool_id, scope)``.
|
|
@@ -2477,6 +2499,24 @@ def _load_installed_tools_module():
|
|
|
2477
2499
|
return installed_tools
|
|
2478
2500
|
|
|
2479
2501
|
|
|
2502
|
+
def _load_user_global_paths_module():
|
|
2503
|
+
"""Lazy-import ``scripts._lib.user_global_paths`` (Phase 3 migration shim)."""
|
|
2504
|
+
pkg_root = str(Path(__file__).resolve().parents[1])
|
|
2505
|
+
if pkg_root not in sys.path:
|
|
2506
|
+
sys.path.insert(0, pkg_root)
|
|
2507
|
+
from scripts._lib import user_global_paths # noqa: WPS433 — lazy by design
|
|
2508
|
+
return user_global_paths
|
|
2509
|
+
|
|
2510
|
+
|
|
2511
|
+
def _load_claude_desktop_bundler_module():
|
|
2512
|
+
"""Lazy-import ``scripts._lib.claude_desktop_bundler`` (Phase 4 ZIP bundler)."""
|
|
2513
|
+
pkg_root = str(Path(__file__).resolve().parents[1])
|
|
2514
|
+
if pkg_root not in sys.path:
|
|
2515
|
+
sys.path.insert(0, pkg_root)
|
|
2516
|
+
from scripts._lib import claude_desktop_bundler # noqa: WPS433 — lazy by design
|
|
2517
|
+
return claude_desktop_bundler
|
|
2518
|
+
|
|
2519
|
+
|
|
2480
2520
|
def _sha256_of_file(path: Path) -> Optional[str]:
|
|
2481
2521
|
"""Return the hex SHA-256 of ``path`` content, or ``None`` if unreadable.
|
|
2482
2522
|
|
|
@@ -2794,29 +2834,68 @@ def _copy_dir_dereferencing_symlinks(
|
|
|
2794
2834
|
return (written, skipped, written_paths)
|
|
2795
2835
|
|
|
2796
2836
|
|
|
2837
|
+
def _claude_desktop_bundles_dir() -> Path:
|
|
2838
|
+
"""Return the canonical bundle output dir under the event4u namespace.
|
|
2839
|
+
|
|
2840
|
+
Located via :func:`user_global_paths.write_target` so the path
|
|
2841
|
+
honours the ``EVENT4U_HOME`` env override used by tests.
|
|
2842
|
+
"""
|
|
2843
|
+
paths_mod = _load_user_global_paths_module()
|
|
2844
|
+
return paths_mod.write_target(_CLAUDE_DESKTOP_BUNDLES_SUBPATH)
|
|
2845
|
+
|
|
2846
|
+
|
|
2797
2847
|
def _write_claude_desktop_marker(
|
|
2798
|
-
force: bool,
|
|
2848
|
+
force: bool,
|
|
2849
|
+
lockfile_path: Path,
|
|
2850
|
+
*,
|
|
2851
|
+
bundles_dir: Path,
|
|
2852
|
+
bundle_count: int,
|
|
2799
2853
|
) -> tuple[int, int, list[Path]]:
|
|
2800
2854
|
"""Write the Claude Desktop user-scope marker file.
|
|
2801
2855
|
|
|
2802
2856
|
Returns ``(written, skipped, written_paths)`` for symmetry with the
|
|
2803
|
-
tree copier (P1.4). The marker
|
|
2804
|
-
|
|
2857
|
+
tree copier (P1.4). The marker points users at ``bundles_dir`` for
|
|
2858
|
+
the Customize → Skills import flow (Phase 4). Existing markers are
|
|
2859
|
+
overwritten unconditionally because the bundle count is part of the
|
|
2860
|
+
body and we want it to stay current.
|
|
2805
2861
|
"""
|
|
2806
2862
|
anchor = Path(USER_SCOPE_PATHS["claude-desktop"]).expanduser()
|
|
2807
2863
|
target = anchor / "agent-config.md"
|
|
2808
|
-
decision = _resolve_file_conflict(target, force_hint=force)
|
|
2809
|
-
if decision == "skip":
|
|
2810
|
-
return (0, 1, [])
|
|
2811
2864
|
anchor.mkdir(parents=True, exist_ok=True)
|
|
2812
2865
|
body = _CLAUDE_DESKTOP_MARKER_TEMPLATE.format(
|
|
2813
2866
|
lockfile=str(lockfile_path),
|
|
2814
2867
|
anchor=str(anchor),
|
|
2868
|
+
bundles_dir=str(bundles_dir),
|
|
2869
|
+
bundle_count=bundle_count,
|
|
2815
2870
|
)
|
|
2816
2871
|
target.write_text(body, encoding="utf-8")
|
|
2817
2872
|
return (1, 0, [target])
|
|
2818
2873
|
|
|
2819
2874
|
|
|
2875
|
+
def _deploy_claude_desktop(
|
|
2876
|
+
force: bool, package_root: Path, lockfile_path: Path,
|
|
2877
|
+
) -> tuple[int, int, str, list[Path]]:
|
|
2878
|
+
"""Build skill ZIP bundles + write the marker for ``claude-desktop``.
|
|
2879
|
+
|
|
2880
|
+
Phase 4 of road-to-event4u-namespace-and-claude-desktop. Bundles
|
|
2881
|
+
land in ``~/.event4u/agent-config/claude-desktop/bundles/``; the
|
|
2882
|
+
marker file in the Claude Desktop config dir points at them with
|
|
2883
|
+
Customize → Skills import instructions.
|
|
2884
|
+
|
|
2885
|
+
Returns ``(bundle_count, 0, "deployed", [bundles_dir, marker])``.
|
|
2886
|
+
The ``deployed`` status replaces the v2.3 ``marker``-only status.
|
|
2887
|
+
"""
|
|
2888
|
+
bundler = _load_claude_desktop_bundler_module()
|
|
2889
|
+
bundles_dir = _claude_desktop_bundles_dir()
|
|
2890
|
+
bundler.build_skill_bundles(package_root, bundles_dir, force=force)
|
|
2891
|
+
# Count total existing ZIPs (idempotent runs may not rewrite any).
|
|
2892
|
+
bundle_count = sum(1 for _ in bundles_dir.glob("*.zip")) if bundles_dir.is_dir() else 0
|
|
2893
|
+
_, _, marker_paths = _write_claude_desktop_marker(
|
|
2894
|
+
force, lockfile_path, bundles_dir=bundles_dir, bundle_count=bundle_count,
|
|
2895
|
+
)
|
|
2896
|
+
return (bundle_count, 0, "deployed", [bundles_dir, *marker_paths])
|
|
2897
|
+
|
|
2898
|
+
|
|
2820
2899
|
def _deploy_global_content(
|
|
2821
2900
|
tools: set[str],
|
|
2822
2901
|
force: bool,
|
|
@@ -2827,20 +2906,21 @@ def _deploy_global_content(
|
|
|
2827
2906
|
|
|
2828
2907
|
For each tool in ``tools`` that has a ``GLOBAL_DEPLOY_SOURCES`` entry,
|
|
2829
2908
|
copies the listed package subtrees into ``USER_SCOPE_PATHS[tool_id]``
|
|
2830
|
-
(expanded). For ``claude-desktop``
|
|
2831
|
-
|
|
2832
|
-
|
|
2909
|
+
(expanded). For ``claude-desktop`` builds per-skill ZIP bundles under
|
|
2910
|
+
``~/.event4u/agent-config/claude-desktop/bundles/`` and writes the
|
|
2911
|
+
marker file pointing at them (Phase 4). For tools without a deployment
|
|
2912
|
+
plan (e.g. ``copilot``), records a ``hint`` status so the caller can
|
|
2913
|
+
print an actionable next step.
|
|
2833
2914
|
|
|
2834
2915
|
Returns ``{tool_id: (written, skipped, status, written_paths)}``
|
|
2835
|
-
where ``status`` is one of ``deployed``, ``
|
|
2836
|
-
|
|
2837
|
-
|
|
2916
|
+
where ``status`` is one of ``deployed``, ``hint``, ``unsupported``
|
|
2917
|
+
and ``written_paths`` is the absolute path list of every file the
|
|
2918
|
+
deploy actually wrote (P1.4).
|
|
2838
2919
|
"""
|
|
2839
2920
|
results: dict[str, tuple[int, int, str, list[Path]]] = {}
|
|
2840
2921
|
for tool_id in sorted(tools):
|
|
2841
2922
|
if tool_id == "claude-desktop":
|
|
2842
|
-
|
|
2843
|
-
results[tool_id] = (w, s, "marker", paths)
|
|
2923
|
+
results[tool_id] = _deploy_claude_desktop(force, package_root, lockfile_path)
|
|
2844
2924
|
continue
|
|
2845
2925
|
plan = GLOBAL_DEPLOY_SOURCES.get(tool_id)
|
|
2846
2926
|
if plan is None:
|
|
@@ -2879,8 +2959,9 @@ def install_global(
|
|
|
2879
2959
|
) -> int:
|
|
2880
2960
|
"""User-scope install path (ADR-007 + Phase 1.6 lockfile lifecycle).
|
|
2881
2961
|
|
|
2882
|
-
Reads ``~/.
|
|
2883
|
-
|
|
2962
|
+
Reads ``~/.event4u/agent-config/installed.lock`` first (with a read
|
|
2963
|
+
fallback to the legacy ``~/.config/agent-config/installed.lock``). A
|
|
2964
|
+
recorded version that does not match the running package version refuses the
|
|
2884
2965
|
install with a remediation hint unless ``--force`` is passed. On
|
|
2885
2966
|
success the lockfile is rewritten atomically with the current
|
|
2886
2967
|
package version + the union of previously-recorded and now-installed
|
|
@@ -2894,17 +2975,33 @@ def install_global(
|
|
|
2894
2975
|
presence of ``.agent-settings.yml``), the project-scope manifest at
|
|
2895
2976
|
``agents/installed-tools.lock`` is also refreshed with ``scope=global``
|
|
2896
2977
|
entries per ADR-008 Phase 3.2.
|
|
2978
|
+
|
|
2979
|
+
Phase 3 namespace migration: before any lockfile read, the legacy
|
|
2980
|
+
``~/.config/agent-config/`` tree (pre-2.4 installs) is migrated into
|
|
2981
|
+
``~/.event4u/agent-config/`` so subsequent reads land on the canonical
|
|
2982
|
+
path. The migration is idempotent and leaves a ``MIGRATED.md``
|
|
2983
|
+
breadcrumb behind; the legacy tree is never auto-deleted.
|
|
2897
2984
|
"""
|
|
2985
|
+
paths_mod = _load_user_global_paths_module()
|
|
2986
|
+
migrated = paths_mod.migrate_legacy_namespace()
|
|
2987
|
+
if migrated and not QUIET:
|
|
2988
|
+
info(
|
|
2989
|
+
"🔁 Migrated user-global config to "
|
|
2990
|
+
f"{paths_mod.event4u_root()} (legacy "
|
|
2991
|
+
f"{paths_mod.legacy_xdg_root()} preserved as fallback)"
|
|
2992
|
+
)
|
|
2993
|
+
|
|
2898
2994
|
lock_mod = _load_installed_lock_module()
|
|
2899
2995
|
installed_version = lock_mod.current_package_version()
|
|
2900
|
-
|
|
2901
|
-
|
|
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)
|
|
2902
2999
|
|
|
2903
3000
|
if not ok and not force:
|
|
2904
3001
|
if not QUIET:
|
|
2905
3002
|
print()
|
|
2906
3003
|
warn("Refusing global install: lockfile version mismatch.")
|
|
2907
|
-
info(f" Lockfile: {
|
|
3004
|
+
info(f" Lockfile: {read_path}")
|
|
2908
3005
|
info(f" Recorded version: {recorded}")
|
|
2909
3006
|
info(f" Current package: {installed_version}")
|
|
2910
3007
|
info(" Fix: run `agent-config update`")
|
|
@@ -2922,10 +3019,10 @@ def install_global(
|
|
|
2922
3019
|
continue
|
|
2923
3020
|
print(f" {tool_id:<15} → {anchor}")
|
|
2924
3021
|
|
|
2925
|
-
existing = lock_mod.read_lockfile(path=
|
|
3022
|
+
existing = lock_mod.read_lockfile(path=read_path) or {}
|
|
2926
3023
|
existing_tools = list(existing.get("tools", []))
|
|
2927
3024
|
merged_tools = sorted(set(existing_tools) | set(tools))
|
|
2928
|
-
written = lock_mod.write_lockfile(installed_version, merged_tools, path=
|
|
3025
|
+
written = lock_mod.write_lockfile(installed_version, merged_tools, path=write_path)
|
|
2929
3026
|
|
|
2930
3027
|
if not QUIET:
|
|
2931
3028
|
print()
|
|
@@ -2945,7 +3042,10 @@ def install_global(
|
|
|
2945
3042
|
for tool_id in sorted(deploy_results):
|
|
2946
3043
|
w, s, status, _ = deploy_results[tool_id]
|
|
2947
3044
|
anchor = USER_SCOPE_PATHS.get(tool_id, "")
|
|
2948
|
-
if status == "deployed":
|
|
3045
|
+
if status == "deployed" and tool_id == "claude-desktop":
|
|
3046
|
+
bundles_dir = _claude_desktop_bundles_dir()
|
|
3047
|
+
print(f" {tool_id:<15} → {bundles_dir} ({w} bundles)")
|
|
3048
|
+
elif status == "deployed":
|
|
2949
3049
|
print(f" {tool_id:<15} → {anchor} ({w} files, {s} skipped)")
|
|
2950
3050
|
elif status == "marker":
|
|
2951
3051
|
print(f" {tool_id:<15} → {anchor}agent-config.md ({'written' if w else 'skipped'})")
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Reads the key with `read -s` so it never echoes to the terminal and
|
|
5
5
|
# never lands in shell history or scrollback. Writes atomically to
|
|
6
|
-
# ~/.
|
|
6
|
+
# ~/.event4u/agent-config/anthropic.key with mode 0600. The legacy
|
|
7
|
+
# ~/.config/agent-config/anthropic.key is read as a fallback by the
|
|
8
|
+
# loaders so pre-2.4 installs keep working until the namespace shim runs.
|
|
7
9
|
#
|
|
8
10
|
# Contract — companion to scripts/skill_trigger_eval.py:
|
|
9
|
-
# - File path: $HOME/.
|
|
11
|
+
# - File path: $HOME/.event4u/agent-config/anthropic.key
|
|
10
12
|
# - File mode: 0600 (owner read/write only)
|
|
11
13
|
# - Key format: must start with `sk-ant-`
|
|
12
14
|
# - No --force, no --yes, no env-var bypass. Piped stdin is rejected.
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
|
|
17
19
|
set -euo pipefail
|
|
18
20
|
|
|
19
|
-
TARGET_DIR="${HOME}/.
|
|
21
|
+
TARGET_DIR="${HOME}/.event4u/agent-config"
|
|
20
22
|
TARGET_FILE="${TARGET_DIR}/anthropic.key"
|
|
21
23
|
|
|
22
24
|
# ── controlling-terminal requirement ─────────────────────────────────────
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Reads the key with `read -s` so it never echoes to the terminal and
|
|
5
5
|
# never lands in shell history or scrollback. Writes atomically to
|
|
6
|
-
# ~/.
|
|
6
|
+
# ~/.event4u/agent-config/openai.key with mode 0600. The legacy
|
|
7
|
+
# ~/.config/agent-config/openai.key is read as a fallback by the loaders
|
|
8
|
+
# so pre-2.4 installs keep working until the namespace shim runs.
|
|
7
9
|
#
|
|
8
10
|
# Contract — companion to scripts/ai_council/clients.py:
|
|
9
|
-
# - File path: $HOME/.
|
|
11
|
+
# - File path: $HOME/.event4u/agent-config/openai.key
|
|
10
12
|
# - File mode: 0600 (owner read/write only)
|
|
11
13
|
# - Key format: must start with `sk-`
|
|
12
14
|
# - No --force, no --yes, no env-var bypass. Piped stdin is rejected.
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
|
|
17
19
|
set -euo pipefail
|
|
18
20
|
|
|
19
|
-
TARGET_DIR="${HOME}/.
|
|
21
|
+
TARGET_DIR="${HOME}/.event4u/agent-config"
|
|
20
22
|
TARGET_FILE="${TARGET_DIR}/openai.key"
|
|
21
23
|
|
|
22
24
|
# ── controlling-terminal requirement ─────────────────────────────────────
|
|
@@ -46,7 +46,16 @@ PRICE_PER_MTOK_OUT = {"claude-sonnet-4-5": 15.0, "claude-opus-4": 75.0}
|
|
|
46
46
|
|
|
47
47
|
# On-disk key file. Companion: scripts/install_anthropic_key.sh writes it
|
|
48
48
|
# with mode 0600; load_anthropic_key() refuses to read anything else.
|
|
49
|
-
|
|
49
|
+
# Resolution prefers the new namespace (``~/.event4u/agent-config/``) and
|
|
50
|
+
# falls back to the legacy ``~/.config/agent-config/`` so pre-2.4 keys
|
|
51
|
+
# stay usable until the user runs the migration shim.
|
|
52
|
+
from scripts._lib import user_global_paths # noqa: E402
|
|
53
|
+
|
|
54
|
+
ANTHROPIC_KEY_FILENAME = "anthropic.key"
|
|
55
|
+
ANTHROPIC_KEY_PATH = (
|
|
56
|
+
user_global_paths.resolve_with_fallback(ANTHROPIC_KEY_FILENAME)
|
|
57
|
+
or user_global_paths.write_target(ANTHROPIC_KEY_FILENAME)
|
|
58
|
+
)
|
|
50
59
|
# Token heuristics used for the *pre-run* cost preview. Real billing
|
|
51
60
|
# comes from the API response once the user has confirmed.
|
|
52
61
|
TOKENS_PER_CHAR = 0.25 # ~4 chars per token, industry rule of thumb.
|
|
@@ -569,7 +578,9 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|
|
569
578
|
default=ANTHROPIC_KEY_PATH,
|
|
570
579
|
help=(
|
|
571
580
|
"Override the key file location. Default: "
|
|
572
|
-
"~/.
|
|
581
|
+
"~/.event4u/agent-config/anthropic.key (legacy "
|
|
582
|
+
"~/.config/agent-config/anthropic.key read as fallback). "
|
|
583
|
+
"Mode 0600 required."
|
|
573
584
|
),
|
|
574
585
|
)
|
|
575
586
|
return parser
|