@event4u/agent-config 2.1.0 → 2.2.0
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/rules/no-cheap-questions.md +11 -2
- package/.agent-src/skills/readme-writing-package/SKILL.md +24 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +48 -0
- package/README.md +71 -6
- package/docs/architecture.md +1 -1
- package/docs/contracts/tier-3-contrib-plugin.md +129 -0
- package/docs/decisions/ADR-007-agent-discovery-scopes.md +278 -0
- package/docs/decisions/ADR-008-installed-tools-manifest.md +160 -0
- package/docs/decisions/INDEX.md +2 -0
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +32 -0
- package/docs/guidelines/agent-infra/installed-tools-manifest.md +135 -0
- package/docs/installation.md +59 -3
- package/docs/setup/per-ide/claude-desktop.md +8 -4
- package/package.json +1 -1
- package/scripts/_cli/cmd_export.py +157 -0
- package/scripts/_cli/cmd_sync.py +162 -0
- package/scripts/_cli/cmd_update.py +23 -1
- package/scripts/_cli/cmd_validate.py +164 -0
- package/scripts/_lib/installed_lock.py +160 -0
- package/scripts/_lib/installed_tools.py +237 -0
- package/scripts/agent-config +62 -0
- package/scripts/install +43 -10
- package/scripts/install.py +973 -12
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Project-scope installed-tools manifest at ``agents/installed-tools.lock``.
|
|
2
|
+
|
|
3
|
+
Phase 3 of road-to-global-first-install (ADR-008). Committed
|
|
4
|
+
bill-of-materials for AI tooling a project depends on. Sibling to the
|
|
5
|
+
global lockfile (``installed_lock.py``) but architecturally distinct:
|
|
6
|
+
|
|
7
|
+
- ``installed_lock.py`` lives in ``~/.config/agent-config/`` and tracks
|
|
8
|
+
the user-scope environment (a single ``agent_config_version`` and a
|
|
9
|
+
flat ``tools[]`` list).
|
|
10
|
+
- ``installed_tools.py`` lives in ``agents/`` and tracks **per-project**
|
|
11
|
+
tooling with richer per-entry metadata (``scope``, ``bridge_marker``,
|
|
12
|
+
``installed_at``).
|
|
13
|
+
|
|
14
|
+
The file is machine-managed: ``init`` appends / merges; ``sync`` replays;
|
|
15
|
+
``validate`` drift-checks. Schema is YAML; ``pyyaml`` is used when
|
|
16
|
+
available, otherwise a constrained manual parser handles the documented
|
|
17
|
+
schema (no anchors, no flow style, single-level nesting under
|
|
18
|
+
``tools``).
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import tempfile
|
|
25
|
+
from datetime import datetime, timezone
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Optional
|
|
28
|
+
|
|
29
|
+
MANIFEST_ENV = "AGENT_CONFIG_INSTALLED_TOOLS"
|
|
30
|
+
DEFAULT_MANIFEST_RELATIVE = Path("agents") / "installed-tools.lock"
|
|
31
|
+
SCHEMA_VERSION = 1
|
|
32
|
+
|
|
33
|
+
_VALID_SCOPES = ("global", "project")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def manifest_path(project_root: Path, env: Optional[dict] = None) -> Path:
|
|
37
|
+
"""Return the active manifest path, honoring the env override."""
|
|
38
|
+
env = env if env is not None else os.environ
|
|
39
|
+
override = env.get(MANIFEST_ENV)
|
|
40
|
+
if override:
|
|
41
|
+
return Path(override).expanduser()
|
|
42
|
+
return project_root / DEFAULT_MANIFEST_RELATIVE
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Read
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
_TOP_KEY_RE = re.compile(r'^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*"?([^"\n]*?)"?\s*$')
|
|
50
|
+
_LIST_DASH_RE = re.compile(r"^\s*-\s*(.+?)\s*$")
|
|
51
|
+
_INDENT_KEY_RE = re.compile(r'^\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*"?([^"\n]*?)"?\s*$')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def read_manifest(path: Path) -> Optional[dict[str, Any]]:
|
|
55
|
+
"""Parse the manifest into a dict; return ``None`` if absent.
|
|
56
|
+
|
|
57
|
+
Tolerates partial / malformed files: missing keys yield missing dict
|
|
58
|
+
entries rather than raising, so a corrupted file does not brick
|
|
59
|
+
``init``.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
text = path.read_text(encoding="utf-8")
|
|
63
|
+
except (FileNotFoundError, OSError):
|
|
64
|
+
return None
|
|
65
|
+
try:
|
|
66
|
+
import yaml # type: ignore[import-untyped]
|
|
67
|
+
data = yaml.safe_load(text) or {}
|
|
68
|
+
if isinstance(data, dict):
|
|
69
|
+
data.setdefault("tools", [])
|
|
70
|
+
return data
|
|
71
|
+
except ImportError:
|
|
72
|
+
pass
|
|
73
|
+
except Exception:
|
|
74
|
+
# Fall through to the manual parser; corrupt YAML is recoverable
|
|
75
|
+
# from our strict schema as long as the top-level shape holds.
|
|
76
|
+
pass
|
|
77
|
+
return _parse_manual(text)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _parse_manual(text: str) -> dict[str, Any]:
|
|
81
|
+
data: dict[str, Any] = {"tools": []}
|
|
82
|
+
in_tools = False
|
|
83
|
+
current: Optional[dict[str, Any]] = None
|
|
84
|
+
for raw in text.splitlines():
|
|
85
|
+
stripped = raw.strip()
|
|
86
|
+
if not stripped or stripped.startswith("#"):
|
|
87
|
+
continue
|
|
88
|
+
if stripped == "tools:":
|
|
89
|
+
in_tools = True
|
|
90
|
+
current = None
|
|
91
|
+
continue
|
|
92
|
+
if in_tools:
|
|
93
|
+
m = _LIST_DASH_RE.match(raw)
|
|
94
|
+
if m:
|
|
95
|
+
first = m.group(1)
|
|
96
|
+
current = {}
|
|
97
|
+
data["tools"].append(current)
|
|
98
|
+
# Could be inline like `- name: foo` — handle that.
|
|
99
|
+
inline = _TOP_KEY_RE.match(first)
|
|
100
|
+
if inline:
|
|
101
|
+
current[inline.group(1)] = inline.group(2)
|
|
102
|
+
continue
|
|
103
|
+
mk = _INDENT_KEY_RE.match(raw)
|
|
104
|
+
if mk and current is not None:
|
|
105
|
+
current[mk.group(1)] = mk.group(2)
|
|
106
|
+
continue
|
|
107
|
+
m_top = _TOP_KEY_RE.match(raw)
|
|
108
|
+
if m_top:
|
|
109
|
+
key, value = m_top.group(1), m_top.group(2)
|
|
110
|
+
if key == "schema_version":
|
|
111
|
+
try:
|
|
112
|
+
data[key] = int(value)
|
|
113
|
+
except ValueError:
|
|
114
|
+
data[key] = value
|
|
115
|
+
else:
|
|
116
|
+
data[key] = value
|
|
117
|
+
in_tools = False
|
|
118
|
+
current = None
|
|
119
|
+
return data
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# Write
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _render(
|
|
128
|
+
version: str,
|
|
129
|
+
tools: list[dict[str, Any]],
|
|
130
|
+
) -> str:
|
|
131
|
+
lines = [
|
|
132
|
+
f"schema_version: {SCHEMA_VERSION}",
|
|
133
|
+
f'agent_config_version: "{version}"',
|
|
134
|
+
"tools:",
|
|
135
|
+
]
|
|
136
|
+
for tool in tools:
|
|
137
|
+
lines.append(f" - name: {tool['name']}")
|
|
138
|
+
lines.append(f" scope: {tool['scope']}")
|
|
139
|
+
lines.append(f" bridge_marker: {tool['bridge_marker']}")
|
|
140
|
+
lines.append(f' installed_at: "{tool["installed_at"]}"')
|
|
141
|
+
return "\n".join(lines) + "\n"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def write_manifest(
|
|
145
|
+
path: Path,
|
|
146
|
+
version: str,
|
|
147
|
+
tools: list[dict[str, Any]],
|
|
148
|
+
) -> Path:
|
|
149
|
+
"""Atomically write the manifest; return the path written."""
|
|
150
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
rendered = _render(version, tools)
|
|
152
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
153
|
+
prefix=".installed-tools.lock.", dir=str(path.parent), text=False
|
|
154
|
+
)
|
|
155
|
+
try:
|
|
156
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
157
|
+
fh.write(rendered)
|
|
158
|
+
os.replace(tmp_name, path)
|
|
159
|
+
except Exception:
|
|
160
|
+
try:
|
|
161
|
+
os.unlink(tmp_name)
|
|
162
|
+
except OSError:
|
|
163
|
+
pass
|
|
164
|
+
raise
|
|
165
|
+
return path
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
# Mutation helpers
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ScopeMismatchError(RuntimeError):
|
|
174
|
+
"""Raised when an existing manifest entry conflicts with the new scope."""
|
|
175
|
+
|
|
176
|
+
def __init__(self, name: str, recorded_scope: str, new_scope: str):
|
|
177
|
+
super().__init__(
|
|
178
|
+
f"tool {name!r} is committed as scope={recorded_scope}; "
|
|
179
|
+
f"refusing to change it to scope={new_scope} without --force"
|
|
180
|
+
)
|
|
181
|
+
self.name = name
|
|
182
|
+
self.recorded_scope = recorded_scope
|
|
183
|
+
self.new_scope = new_scope
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def upsert_tool(
|
|
187
|
+
existing: list[dict[str, Any]],
|
|
188
|
+
*,
|
|
189
|
+
name: str,
|
|
190
|
+
scope: str,
|
|
191
|
+
bridge_marker: str,
|
|
192
|
+
installed_at: Optional[str] = None,
|
|
193
|
+
force: bool = False,
|
|
194
|
+
) -> list[dict[str, Any]]:
|
|
195
|
+
"""Return a new tools list with ``name`` added or refreshed.
|
|
196
|
+
|
|
197
|
+
Idempotency rules from ADR-008 §Lifecycle:
|
|
198
|
+
* Same name, same scope → no-op (timestamp preserved).
|
|
199
|
+
* Same name, different scope → raise ``ScopeMismatchError`` unless
|
|
200
|
+
``force=True``, in which case the entry is rewritten.
|
|
201
|
+
* New name → appended in install order (not alphabetised).
|
|
202
|
+
"""
|
|
203
|
+
if scope not in _VALID_SCOPES:
|
|
204
|
+
raise ValueError(f"scope must be one of {_VALID_SCOPES}: {scope!r}")
|
|
205
|
+
stamp = installed_at or _today()
|
|
206
|
+
result: list[dict[str, Any]] = []
|
|
207
|
+
found = False
|
|
208
|
+
for entry in existing:
|
|
209
|
+
if entry.get("name") == name:
|
|
210
|
+
found = True
|
|
211
|
+
recorded = str(entry.get("scope", ""))
|
|
212
|
+
if recorded == scope:
|
|
213
|
+
# Idempotent no-op — preserve original installed_at.
|
|
214
|
+
result.append(entry)
|
|
215
|
+
continue
|
|
216
|
+
if not force:
|
|
217
|
+
raise ScopeMismatchError(name, recorded, scope)
|
|
218
|
+
result.append({
|
|
219
|
+
"name": name,
|
|
220
|
+
"scope": scope,
|
|
221
|
+
"bridge_marker": bridge_marker,
|
|
222
|
+
"installed_at": stamp,
|
|
223
|
+
})
|
|
224
|
+
continue
|
|
225
|
+
result.append(entry)
|
|
226
|
+
if not found:
|
|
227
|
+
result.append({
|
|
228
|
+
"name": name,
|
|
229
|
+
"scope": scope,
|
|
230
|
+
"bridge_marker": bridge_marker,
|
|
231
|
+
"installed_at": stamp,
|
|
232
|
+
})
|
|
233
|
+
return result
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _today() -> str:
|
|
237
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
package/scripts/agent-config
CHANGED
|
@@ -98,6 +98,19 @@ Commands:
|
|
|
98
98
|
Flags: --check (read-only) | --to <version> (explicit pin)
|
|
99
99
|
migrate One-shot migration off legacy composer / npm install paths
|
|
100
100
|
Flags: --dry-run (detect only)
|
|
101
|
+
global Install to user-scope paths (~/.claude/, ~/.cursor/, …)
|
|
102
|
+
Forwards to `scripts/install --global` (ADR-007).
|
|
103
|
+
Flags: --tools=<list> | --ai=<list> | --yes | --force
|
|
104
|
+
export Eject a tool's canonical content into a chosen path
|
|
105
|
+
(real file, no symlink). Idempotent; --force overrides
|
|
106
|
+
content drift. See `./agent-config export --list`.
|
|
107
|
+
Flags: --tool=<id> | --output=<path> | --force | --list
|
|
108
|
+
sync Replay agents/installed-tools.lock — re-installs any
|
|
109
|
+
tool whose bridge marker is missing locally (ADR-008).
|
|
110
|
+
Flags: --dry-run | --force | --project=<path>
|
|
111
|
+
validate Read-only drift detection on the manifest
|
|
112
|
+
(marker missing, scope divergence, version drift).
|
|
113
|
+
Exits 1 on drift. Flags: --quiet | --skip-version-check
|
|
101
114
|
help Show this help
|
|
102
115
|
--version, -V Print package version
|
|
103
116
|
|
|
@@ -126,6 +139,14 @@ Examples:
|
|
|
126
139
|
./agent-config council:estimate prompt.txt
|
|
127
140
|
./agent-config council:run prompt.txt --output agents/council-sessions/out.json --confirm
|
|
128
141
|
./agent-config council:render agents/council-sessions/out.json
|
|
142
|
+
./agent-config global --tools=claude-code --yes
|
|
143
|
+
./agent-config global --ai=cursor,windsurf
|
|
144
|
+
./agent-config export --list
|
|
145
|
+
./agent-config export --tool=agents-md --output=AGENTS.md
|
|
146
|
+
./agent-config export --tool=copilot-instructions --output=.github/copilot-instructions.md
|
|
147
|
+
./agent-config sync --dry-run
|
|
148
|
+
./agent-config sync
|
|
149
|
+
./agent-config validate
|
|
129
150
|
|
|
130
151
|
All commands operate on the CURRENT DIRECTORY (your project root).
|
|
131
152
|
The CLI is strictly consumer-facing. Maintainer tasks live in Taskfile.yml.
|
|
@@ -524,6 +545,43 @@ cmd_migrate() {
|
|
|
524
545
|
exec env PYTHONPATH="$PACKAGE_ROOT" python3 -m scripts._cli.cmd_migrate "$@"
|
|
525
546
|
}
|
|
526
547
|
|
|
548
|
+
# `agent-config global` — user-scope install entry point. Forwards to the
|
|
549
|
+
# bash installer with `--global` set (ADR-007). Phase 1.2 of
|
|
550
|
+
# road-to-global-first-install.md. The bash wrapper handles option parsing
|
|
551
|
+
# and forwards to `scripts/install.py --global`, where `install_global()`
|
|
552
|
+
# currently scaffolds the per-tool anchor paths from USER_SCOPE_PATHS.
|
|
553
|
+
# Concrete writes land in Phase 1.5 (export) and Phase 1.6 (lockfile).
|
|
554
|
+
cmd_global() {
|
|
555
|
+
local script
|
|
556
|
+
script="$(resolve_script "scripts/install")" || return 1
|
|
557
|
+
exec bash "$script" --global "$@"
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
# `agent-config export` — write a tool's canonical content into a
|
|
561
|
+
# user-chosen path. ADR-007 D3 / Phase 1.5 of
|
|
562
|
+
# road-to-global-first-install.md. Replaces the rejected symlink-bridge.
|
|
563
|
+
# See scripts/_cli/cmd_export.py for the registry and idempotency logic.
|
|
564
|
+
cmd_export() {
|
|
565
|
+
require_python3
|
|
566
|
+
exec env PYTHONPATH="$PACKAGE_ROOT" python3 -m scripts._cli.cmd_export "$@"
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
# `agent-config sync` — replay agents/installed-tools.lock (ADR-008
|
|
570
|
+
# Phase 3.3). Re-installs any tool whose bridge marker is missing on
|
|
571
|
+
# disk. Typical onboarding flow: clone → `./agent-config sync` → done.
|
|
572
|
+
cmd_sync() {
|
|
573
|
+
require_python3
|
|
574
|
+
exec env PYTHONPATH="$PACKAGE_ROOT" python3 -m scripts._cli.cmd_sync "$@"
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
# `agent-config validate` — read-only drift detection (ADR-008 Phase 3.4).
|
|
578
|
+
# Surfaces marker-missing, scope-divergence, and version-drift; exits 1 on
|
|
579
|
+
# any drift. Never edits the manifest or re-runs the installer.
|
|
580
|
+
cmd_validate() {
|
|
581
|
+
require_python3
|
|
582
|
+
exec env PYTHONPATH="$PACKAGE_ROOT" python3 -m scripts._cli.cmd_validate "$@"
|
|
583
|
+
}
|
|
584
|
+
|
|
527
585
|
main() {
|
|
528
586
|
local cmd="${1-}"
|
|
529
587
|
[[ $# -gt 0 ]] && shift || true
|
|
@@ -564,6 +622,10 @@ main() {
|
|
|
564
622
|
council:render) cmd_council render "$@" ;;
|
|
565
623
|
update) cmd_update "$@" ;;
|
|
566
624
|
migrate) cmd_migrate "$@" ;;
|
|
625
|
+
global) cmd_global "$@" ;;
|
|
626
|
+
export) cmd_export "$@" ;;
|
|
627
|
+
sync) cmd_sync "$@" ;;
|
|
628
|
+
validate) cmd_validate "$@" ;;
|
|
567
629
|
help|--help|-h|"") usage ;;
|
|
568
630
|
--version|-V) print_version ;;
|
|
569
631
|
*)
|
package/scripts/install
CHANGED
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
# --profile <name> Cost profile for bridges (minimal|balanced|full)
|
|
18
18
|
# --tools <list> Comma-separated tool IDs to install (default: all).
|
|
19
19
|
# Valid: claude-code,claude-desktop,cursor,windsurf,
|
|
20
|
-
# cline,gemini-cli,copilot,augment,aider,codex,
|
|
20
|
+
# cline,gemini-cli,copilot,augment,aider,codex,
|
|
21
|
+
# roocode,continue,kilocode,zed,jetbrains,kiro,all
|
|
22
|
+
# --ai <list> Alias for --tools (same IDs). When both are passed
|
|
23
|
+
# the comma-separated values are unioned.
|
|
21
24
|
# --list-tools Print supported tool IDs with descriptions, then exit
|
|
22
25
|
# --yes, -y Non-interactive mode: do not prompt (default for non-TTY)
|
|
23
26
|
# --force Overwrite existing bridge files
|
|
@@ -26,12 +29,22 @@
|
|
|
26
29
|
# --quiet Suppress non-error output
|
|
27
30
|
# --skip-sync Skip payload sync (install.sh)
|
|
28
31
|
# --skip-bridges Skip bridge files (install.py)
|
|
32
|
+
# --global Install to user-scope paths (~/.claude/, ~/.cursor/, …)
|
|
33
|
+
# instead of project-locally. Implies --skip-sync (the
|
|
34
|
+
# payload sync stage is project-only). See ADR-007.
|
|
35
|
+
# --scope <mode> Override scope detection: auto | project | global | prompt.
|
|
36
|
+
# auto = honor multi-signal detection (Phase 1.3)
|
|
37
|
+
# project = force project-local install
|
|
38
|
+
# global = force user-scope install (same as --global)
|
|
39
|
+
# prompt = always show the 3-option chooser
|
|
40
|
+
# --custom-path <d> Use <d> as the project root when prompted; rejected with
|
|
41
|
+
# --scope=global / --global.
|
|
29
42
|
# --help, -h Show this help
|
|
30
43
|
#
|
|
31
44
|
# Examples:
|
|
32
45
|
# bash scripts/install # everything (default)
|
|
33
46
|
# bash scripts/install --tools=claude-code,cursor # only those two
|
|
34
|
-
# bash scripts/install --
|
|
47
|
+
# bash scripts/install --ai=cursor --yes # alias form (CI-friendly)
|
|
35
48
|
# bash scripts/install --list-tools # show catalog
|
|
36
49
|
|
|
37
50
|
set -uo pipefail
|
|
@@ -53,12 +66,15 @@ QUIET=false
|
|
|
53
66
|
SKIP_SYNC=false
|
|
54
67
|
SKIP_BRIDGES=false
|
|
55
68
|
LIST_TOOLS=false
|
|
69
|
+
GLOBAL=false
|
|
70
|
+
SCOPE=""
|
|
71
|
+
CUSTOM_PATH=""
|
|
56
72
|
|
|
57
73
|
# Single source of truth for valid tool IDs (also referenced by install.sh / install.py).
|
|
58
|
-
VALID_TOOLS="claude-code claude-desktop cursor windsurf cline gemini-cli copilot augment aider codex all"
|
|
74
|
+
VALID_TOOLS="claude-code claude-desktop cursor windsurf cline gemini-cli copilot augment aider codex roocode continue kilocode zed jetbrains kiro all"
|
|
59
75
|
|
|
60
76
|
show_help() {
|
|
61
|
-
sed -n '3,
|
|
77
|
+
sed -n '3,48p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
list_tools() {
|
|
@@ -66,19 +82,26 @@ list_tools() {
|
|
|
66
82
|
Supported --tools IDs (default: all):
|
|
67
83
|
|
|
68
84
|
claude-code .claude/rules, .claude/skills, .claude/commands, .claude/settings.json
|
|
69
|
-
claude-desktop
|
|
85
|
+
claude-desktop .claude-desktop/agent-config.md marker (global-scope tool — see ADR-007)
|
|
70
86
|
cursor .cursor/rules, .cursor/commands (legacy .cursorrules also written)
|
|
71
87
|
windsurf .windsurf/rules, .windsurf/workflows (legacy .windsurfrules also written)
|
|
72
88
|
cline .clinerules/ symlinks
|
|
73
89
|
gemini-cli GEMINI.md, .gemini/settings.json
|
|
74
90
|
copilot .github/copilot-instructions.md, .vscode/settings.json
|
|
75
91
|
augment .augment/ payload + settings (substrate — recommended for every install)
|
|
76
|
-
aider
|
|
77
|
-
codex
|
|
92
|
+
aider .aider/agent-config.md marker (wire via `read:` in .aider.conf.yml)
|
|
93
|
+
codex .codex/agent-config.md marker (Codex reads AGENTS.md directly)
|
|
94
|
+
roocode .roo/rules/agent-config.md marker (Roo Code auto-discovery)
|
|
95
|
+
continue .continue/rules/agent-config.md marker (Continue.dev auto-discovery)
|
|
96
|
+
kilocode .kilocode/rules/agent-config.md marker (Kilo Code auto-discovery)
|
|
97
|
+
zed .zed/agent-config.md marker (Zed reads .rules at project root)
|
|
98
|
+
jetbrains .jetbrains/agent-config.md marker (JetBrains AI Assistant)
|
|
99
|
+
kiro .kiro/steering/agent-config.md marker (Kiro auto-discovery)
|
|
78
100
|
all every ID above (the default; backward-compatible)
|
|
79
101
|
|
|
80
102
|
Examples:
|
|
81
103
|
--tools=claude-code,cursor project-local install for those two surfaces
|
|
104
|
+
--ai=cursor alias for --tools=cursor
|
|
82
105
|
--tools=all equivalent to omitting the flag
|
|
83
106
|
EOF
|
|
84
107
|
}
|
|
@@ -110,8 +133,10 @@ while [[ $# -gt 0 ]]; do
|
|
|
110
133
|
--target=*) TARGET_DIR="${1#*=}"; shift ;;
|
|
111
134
|
--profile) PROFILE="$2"; shift 2 ;;
|
|
112
135
|
--profile=*) PROFILE="${1#*=}"; shift ;;
|
|
113
|
-
--tools) TOOLS="$2"; TOOLS_EXPLICIT=true; shift 2 ;;
|
|
114
|
-
--tools=*) TOOLS="${1#*=}"; TOOLS_EXPLICIT=true; shift ;;
|
|
136
|
+
--tools) TOOLS="${TOOLS:+$TOOLS,}$2"; TOOLS_EXPLICIT=true; shift 2 ;;
|
|
137
|
+
--tools=*) TOOLS="${TOOLS:+$TOOLS,}${1#*=}"; TOOLS_EXPLICIT=true; shift ;;
|
|
138
|
+
--ai) TOOLS="${TOOLS:+$TOOLS,}$2"; TOOLS_EXPLICIT=true; shift 2 ;;
|
|
139
|
+
--ai=*) TOOLS="${TOOLS:+$TOOLS,}${1#*=}"; TOOLS_EXPLICIT=true; shift ;;
|
|
115
140
|
--list-tools) LIST_TOOLS=true; shift ;;
|
|
116
141
|
--yes|-y) YES=true; shift ;;
|
|
117
142
|
--force) FORCE=true; shift ;;
|
|
@@ -120,6 +145,11 @@ while [[ $# -gt 0 ]]; do
|
|
|
120
145
|
--quiet) QUIET=true; shift ;;
|
|
121
146
|
--skip-sync) SKIP_SYNC=true; shift ;;
|
|
122
147
|
--skip-bridges) SKIP_BRIDGES=true; shift ;;
|
|
148
|
+
--global) GLOBAL=true; SKIP_SYNC=true; shift ;;
|
|
149
|
+
--scope) SCOPE="$2"; shift 2 ;;
|
|
150
|
+
--scope=*) SCOPE="${1#*=}"; shift ;;
|
|
151
|
+
--custom-path) CUSTOM_PATH="$2"; shift 2 ;;
|
|
152
|
+
--custom-path=*) CUSTOM_PATH="${1#*=}"; shift ;;
|
|
123
153
|
--help|-h) show_help; exit 0 ;;
|
|
124
154
|
*) err "Unknown argument: $1"; show_help >&2; exit 1 ;;
|
|
125
155
|
esac
|
|
@@ -138,7 +168,7 @@ fi
|
|
|
138
168
|
# Otherwise we fall through to the backward-compatible "all" default.
|
|
139
169
|
prompt_tools() {
|
|
140
170
|
local choice picked tool i=0
|
|
141
|
-
local -a menu=(claude-code claude-desktop cursor windsurf cline gemini-cli copilot augment aider codex)
|
|
171
|
+
local -a menu=(claude-code claude-desktop cursor windsurf cline gemini-cli copilot augment aider codex roocode continue kilocode zed jetbrains kiro)
|
|
142
172
|
echo ""
|
|
143
173
|
echo " 📦 Pick the tools to install (comma-separated numbers, blank = all):"
|
|
144
174
|
for tool in "${menu[@]}"; do
|
|
@@ -263,6 +293,9 @@ run_bridges() {
|
|
|
263
293
|
[[ -n "$PROFILE" ]] && args+=(--profile="$PROFILE")
|
|
264
294
|
$FORCE && args+=(--force)
|
|
265
295
|
$QUIET && args+=(--quiet)
|
|
296
|
+
$GLOBAL && args+=(--global)
|
|
297
|
+
[[ -n "$SCOPE" ]] && args+=(--scope="$SCOPE")
|
|
298
|
+
[[ -n "$CUSTOM_PATH" ]] && args+=(--custom-path="$CUSTOM_PATH")
|
|
266
299
|
args+=(--tools="$TOOLS")
|
|
267
300
|
"$python_bin" "$INSTALL_PY" "${args[@]}"
|
|
268
301
|
}
|