@event4u/agent-config 1.39.0 → 1.40.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/commands/orchestrate.md +123 -0
- package/.agent-src/commands/sync-gitignore/fix.md +135 -0
- package/.agent-src/commands/sync-gitignore.md +31 -5
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +30 -2
- package/.agent-src/skills/subagent-orchestration/SKILL.md +9 -0
- package/.agent-src/skills/using-git-worktrees/SKILL.md +25 -0
- package/.agent-src/templates/agent-settings.md +9 -0
- package/.agent-src/templates/scripts/work_engine/orchestration.py +168 -0
- package/.claude-plugin/marketplace.json +3 -1
- package/CHANGELOG.md +42 -0
- package/README.md +5 -5
- package/bin/install.php +13 -6
- package/config/agent-settings.template.yml +21 -0
- package/docs/DISTRIBUTION_CHECKLIST.md +169 -0
- package/docs/architecture.md +1 -1
- package/docs/catalog.md +3 -2
- package/docs/contracts/audit-log-v1.md +142 -0
- package/docs/contracts/command-clusters.md +2 -0
- package/docs/contracts/file-ownership-matrix.json +20 -0
- package/docs/contracts/orchestration-dsl-v1.md +152 -0
- package/docs/getting-started.md +1 -1
- package/docs/installation.md +132 -0
- package/docs/setup/per-ide/aider.md +48 -0
- package/docs/setup/per-ide/claude-code.md +108 -0
- package/docs/setup/per-ide/claude-desktop.md +148 -0
- package/docs/setup/per-ide/cline.md +43 -0
- package/docs/setup/per-ide/codex.md +46 -0
- package/docs/setup/per-ide/copilot.md +80 -0
- package/docs/setup/per-ide/cursor.md +125 -0
- package/docs/setup/per-ide/gemini-cli.md +45 -0
- package/docs/setup/per-ide/windsurf.md +120 -0
- package/package.json +1 -1
- package/scripts/compress.py +153 -1
- package/scripts/extract_audit_patterns.py +202 -0
- package/scripts/install +156 -1
- package/scripts/install.py +270 -10
- package/scripts/install.sh +52 -7
- package/scripts/lint_orchestration_dsl.py +214 -0
- package/scripts/skill_linter.py +9 -0
- package/scripts/sync_gitignore.py +56 -1
- package/templates/claude_desktop_config.json.template +21 -0
- package/templates/cursor-rule.mdc.j2 +7 -0
- package/templates/global-install-manifest.yml +91 -0
- package/templates/marketing-copy.yml +64 -0
- package/templates/windsurf-rule.md.j2 +7 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Mine repeated phase patterns from ``agents/state/audit/*.jsonl``.
|
|
3
|
+
|
|
4
|
+
Consumer side of `audit-log-v1` (see
|
|
5
|
+
`docs/contracts/audit-log-v1.md`). Reads append-only JSONL audit
|
|
6
|
+
lines emitted by the `work_engine` phase hook and surfaces patterns
|
|
7
|
+
that repeat across **independent** runs — i.e. distinct `work_id`
|
|
8
|
+
values — so the human reviewer can promote them via the
|
|
9
|
+
`learning-to-rule-or-skill` skill.
|
|
10
|
+
|
|
11
|
+
Read-only: never mutates the JSONL, never writes outside `--output`.
|
|
12
|
+
|
|
13
|
+
Pattern shape (one per row):
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
"summary": "<phase>:<outcome>:<rules_hash>",
|
|
17
|
+
"phase": "verify",
|
|
18
|
+
"outcome": "success",
|
|
19
|
+
"rules_applied": ["verify-before-complete", "commit-policy"],
|
|
20
|
+
"count": 7, # distinct work_ids
|
|
21
|
+
"line_ids": ["01HXY...", ...],
|
|
22
|
+
"first_seen": "2026-05-01T...",
|
|
23
|
+
"last_seen": "2026-05-11T..."
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Repetition gate: a pattern is emitted only when ``count >= 2`` and
|
|
27
|
+
the two contributing lines come from **different** ``work_id`` values
|
|
28
|
+
(independence floor — same gate as the skill's evidence rule).
|
|
29
|
+
|
|
30
|
+
Usage::
|
|
31
|
+
|
|
32
|
+
python3 scripts/extract_audit_patterns.py # human table
|
|
33
|
+
python3 scripts/extract_audit_patterns.py --json # machine
|
|
34
|
+
python3 scripts/extract_audit_patterns.py --min-count 3
|
|
35
|
+
python3 scripts/extract_audit_patterns.py --month 2026-05 # one file
|
|
36
|
+
python3 scripts/extract_audit_patterns.py --audit-dir <p> # override
|
|
37
|
+
"""
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import argparse
|
|
41
|
+
import json
|
|
42
|
+
import sys
|
|
43
|
+
from collections import defaultdict
|
|
44
|
+
from dataclasses import dataclass, field, asdict
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
from typing import Iterable
|
|
47
|
+
|
|
48
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
49
|
+
DEFAULT_AUDIT_DIR = ROOT / "agents" / "state" / "audit"
|
|
50
|
+
SCHEMA_VERSION = 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class Pattern:
|
|
55
|
+
summary: str
|
|
56
|
+
phase: str
|
|
57
|
+
outcome: str
|
|
58
|
+
rules_applied: list[str]
|
|
59
|
+
count: int = 0
|
|
60
|
+
line_ids: list[str] = field(default_factory=list)
|
|
61
|
+
work_ids: set[str] = field(default_factory=set)
|
|
62
|
+
first_seen: str = ""
|
|
63
|
+
last_seen: str = ""
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> dict:
|
|
66
|
+
d = asdict(self)
|
|
67
|
+
d["work_ids"] = sorted(self.work_ids)
|
|
68
|
+
return d
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _iter_lines(audit_dir: Path, month: str | None) -> Iterable[dict]:
|
|
72
|
+
"""Yield parsed JSONL records from the audit directory.
|
|
73
|
+
|
|
74
|
+
Silently skips malformed lines (forward-compat per contract § 86).
|
|
75
|
+
"""
|
|
76
|
+
if not audit_dir.exists():
|
|
77
|
+
return
|
|
78
|
+
files = (
|
|
79
|
+
[audit_dir / f"{month}.jsonl"] if month
|
|
80
|
+
else sorted(audit_dir.glob("*.jsonl"))
|
|
81
|
+
)
|
|
82
|
+
for path in files:
|
|
83
|
+
if not path.exists():
|
|
84
|
+
continue
|
|
85
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
86
|
+
for raw in fh:
|
|
87
|
+
raw = raw.strip()
|
|
88
|
+
if not raw:
|
|
89
|
+
continue
|
|
90
|
+
try:
|
|
91
|
+
rec = json.loads(raw)
|
|
92
|
+
except json.JSONDecodeError:
|
|
93
|
+
continue
|
|
94
|
+
if rec.get("schema_version") != SCHEMA_VERSION:
|
|
95
|
+
continue
|
|
96
|
+
yield rec
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _pattern_key(rec: dict) -> tuple[str, str, tuple[str, ...]]:
|
|
100
|
+
rules = tuple(sorted(rec.get("rules_applied") or []))
|
|
101
|
+
return (rec.get("phase", ""), rec.get("outcome", ""), rules)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _resolve_supersedes(records: list[dict]) -> list[dict]:
|
|
105
|
+
"""Apply supersede chains: drop records whose id is superseded."""
|
|
106
|
+
superseded: set[str] = set()
|
|
107
|
+
for rec in records:
|
|
108
|
+
if rec.get("type") == "supersede" and rec.get("supersedes"):
|
|
109
|
+
superseded.add(rec["supersedes"])
|
|
110
|
+
return [r for r in records if r.get("id") not in superseded]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def mine(audit_dir: Path, month: str | None, min_count: int) -> list[dict]:
|
|
114
|
+
"""Group records into patterns; enforce independence floor."""
|
|
115
|
+
records = _resolve_supersedes(list(_iter_lines(audit_dir, month)))
|
|
116
|
+
groups: dict[tuple, Pattern] = {}
|
|
117
|
+
for rec in records:
|
|
118
|
+
if rec.get("type") not in (None, "phase"):
|
|
119
|
+
continue
|
|
120
|
+
key = _pattern_key(rec)
|
|
121
|
+
if key not in groups:
|
|
122
|
+
phase, outcome, rules = key
|
|
123
|
+
rules_hash = "+".join(rules) or "<none>"
|
|
124
|
+
groups[key] = Pattern(
|
|
125
|
+
summary=f"{phase}:{outcome}:{rules_hash}",
|
|
126
|
+
phase=phase,
|
|
127
|
+
outcome=outcome,
|
|
128
|
+
rules_applied=list(rules),
|
|
129
|
+
)
|
|
130
|
+
pat = groups[key]
|
|
131
|
+
ts = rec.get("ts", "")
|
|
132
|
+
wid = rec.get("work_id", "")
|
|
133
|
+
if wid:
|
|
134
|
+
pat.work_ids.add(wid)
|
|
135
|
+
line_id = rec.get("id", "")
|
|
136
|
+
if line_id:
|
|
137
|
+
pat.line_ids.append(line_id)
|
|
138
|
+
pat.count = len(pat.work_ids)
|
|
139
|
+
if not pat.first_seen or ts < pat.first_seen:
|
|
140
|
+
pat.first_seen = ts
|
|
141
|
+
if not pat.last_seen or ts > pat.last_seen:
|
|
142
|
+
pat.last_seen = ts
|
|
143
|
+
out = [p.to_dict() for p in groups.values() if p.count >= min_count]
|
|
144
|
+
out.sort(key=lambda d: (-d["count"], d["summary"]))
|
|
145
|
+
return out
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _render_table(patterns: list[dict]) -> str:
|
|
149
|
+
if not patterns:
|
|
150
|
+
return "(no patterns at or above the min-count threshold)"
|
|
151
|
+
lines = [
|
|
152
|
+
f"{'count':>5} {'phase':<10} {'outcome':<8} {'rules':<40} summary",
|
|
153
|
+
f"{'-' * 5} {'-' * 10} {'-' * 8} {'-' * 40} -------",
|
|
154
|
+
]
|
|
155
|
+
for p in patterns:
|
|
156
|
+
rules = ",".join(p["rules_applied"]) or "<none>"
|
|
157
|
+
if len(rules) > 38:
|
|
158
|
+
rules = rules[:35] + "..."
|
|
159
|
+
lines.append(
|
|
160
|
+
f"{p['count']:>5} {p['phase']:<10} {p['outcome']:<8} "
|
|
161
|
+
f"{rules:<40} {p['summary']}"
|
|
162
|
+
)
|
|
163
|
+
return "\n".join(lines)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main(argv: list[str] | None = None) -> int:
|
|
167
|
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
168
|
+
ap.add_argument(
|
|
169
|
+
"--audit-dir", type=Path, default=DEFAULT_AUDIT_DIR,
|
|
170
|
+
help="Override audit-log directory (default: %(default)s).",
|
|
171
|
+
)
|
|
172
|
+
ap.add_argument(
|
|
173
|
+
"--month", help="Single YYYY-MM file instead of all months.",
|
|
174
|
+
)
|
|
175
|
+
ap.add_argument(
|
|
176
|
+
"--min-count", type=int, default=2,
|
|
177
|
+
help="Minimum distinct work_ids required (default: 2).",
|
|
178
|
+
)
|
|
179
|
+
ap.add_argument(
|
|
180
|
+
"--json", action="store_true", help="Emit machine-readable JSON.",
|
|
181
|
+
)
|
|
182
|
+
args = ap.parse_args(argv)
|
|
183
|
+
|
|
184
|
+
if args.min_count < 2:
|
|
185
|
+
print(
|
|
186
|
+
"❌ --min-count must be >= 2 (independence floor per "
|
|
187
|
+
"audit-log-v1 § Privacy floor).",
|
|
188
|
+
file=sys.stderr,
|
|
189
|
+
)
|
|
190
|
+
return 2
|
|
191
|
+
|
|
192
|
+
patterns = mine(args.audit_dir, args.month, args.min_count)
|
|
193
|
+
if args.json:
|
|
194
|
+
json.dump(patterns, sys.stdout, indent=2, sort_keys=True)
|
|
195
|
+
sys.stdout.write("\n")
|
|
196
|
+
else:
|
|
197
|
+
print(_render_table(patterns))
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
sys.exit(main())
|
package/scripts/install
CHANGED
|
@@ -15,13 +15,30 @@
|
|
|
15
15
|
# --source <dir> Package source directory (default: auto-detect)
|
|
16
16
|
# --target <dir> Target project root (default: cwd)
|
|
17
17
|
# --profile <name> Cost profile for bridges (minimal|balanced|full)
|
|
18
|
+
# --tools <list> Comma-separated tool IDs to install (default: all).
|
|
19
|
+
# Valid: claude-code,claude-desktop,cursor,windsurf,
|
|
20
|
+
# cline,gemini-cli,copilot,augment,aider,codex,all
|
|
21
|
+
# --list-tools Print supported tool IDs with descriptions, then exit
|
|
22
|
+
# --yes, -y Non-interactive mode: do not prompt (default for non-TTY)
|
|
18
23
|
# --force Overwrite existing bridge files
|
|
19
24
|
# --dry-run Show what payload sync would do (does not run bridges)
|
|
20
25
|
# --verbose Detailed payload sync output
|
|
21
26
|
# --quiet Suppress non-error output
|
|
22
27
|
# --skip-sync Skip payload sync (install.sh)
|
|
23
28
|
# --skip-bridges Skip bridge files (install.py)
|
|
29
|
+
# --global Phase-3: ship kernel rules + curated skills to user-scope
|
|
30
|
+
# dirs (~/.claude/, ~/.cursor/, ~/.codeium/windsurf/,
|
|
31
|
+
# ~/.config/agent-config/) so the agent has them in every
|
|
32
|
+
# project. Pair with --tools to scope surfaces; default = all.
|
|
33
|
+
# --uninstall With --global: remove the event4u/ namespace dir from each
|
|
34
|
+
# enabled surface (no effect on user-added files).
|
|
24
35
|
# --help, -h Show this help
|
|
36
|
+
#
|
|
37
|
+
# Examples:
|
|
38
|
+
# bash scripts/install # everything (default)
|
|
39
|
+
# bash scripts/install --tools=claude-code,cursor # only those two
|
|
40
|
+
# bash scripts/install --tools=cursor --yes # CI-friendly
|
|
41
|
+
# bash scripts/install --list-tools # show catalog
|
|
25
42
|
|
|
26
43
|
set -uo pipefail
|
|
27
44
|
|
|
@@ -32,19 +49,67 @@ INSTALL_PY="$SCRIPT_DIR/install.py"
|
|
|
32
49
|
SOURCE_DIR=""
|
|
33
50
|
TARGET_DIR=""
|
|
34
51
|
PROFILE=""
|
|
52
|
+
TOOLS=""
|
|
53
|
+
TOOLS_EXPLICIT=false
|
|
54
|
+
YES=false
|
|
35
55
|
FORCE=false
|
|
36
56
|
DRY_RUN=false
|
|
37
57
|
VERBOSE=false
|
|
38
58
|
QUIET=false
|
|
39
59
|
SKIP_SYNC=false
|
|
40
60
|
SKIP_BRIDGES=false
|
|
61
|
+
LIST_TOOLS=false
|
|
62
|
+
GLOBAL_INSTALL=false
|
|
63
|
+
UNINSTALL=false
|
|
64
|
+
|
|
65
|
+
# Single source of truth for valid tool IDs (also referenced by install.sh / install.py).
|
|
66
|
+
VALID_TOOLS="claude-code claude-desktop cursor windsurf cline gemini-cli copilot augment aider codex all"
|
|
41
67
|
|
|
42
68
|
show_help() {
|
|
43
|
-
sed -n '3,
|
|
69
|
+
sed -n '3,35p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
list_tools() {
|
|
73
|
+
cat <<'EOF'
|
|
74
|
+
Supported --tools IDs (default: all):
|
|
75
|
+
|
|
76
|
+
claude-code .claude/rules, .claude/skills, .claude/commands, .claude/settings.json
|
|
77
|
+
claude-desktop ~/Library/Application Support/Claude/ (global, see Phase 4 docs)
|
|
78
|
+
cursor .cursor/rules, .cursor/commands (legacy .cursorrules also written)
|
|
79
|
+
windsurf .windsurf/rules, .windsurf/workflows (legacy .windsurfrules also written)
|
|
80
|
+
cline .clinerules/ symlinks
|
|
81
|
+
gemini-cli GEMINI.md, .gemini/settings.json
|
|
82
|
+
copilot .github/copilot-instructions.md, .vscode/settings.json
|
|
83
|
+
augment .augment/ payload + settings (substrate — recommended for every install)
|
|
84
|
+
aider AGENTS.md (Linux Foundation cross-tool contract; always written)
|
|
85
|
+
codex AGENTS.md (same as aider — no extra action)
|
|
86
|
+
all every ID above (the default; backward-compatible)
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
--tools=claude-code,cursor project-local install for those two surfaces
|
|
90
|
+
--tools=all equivalent to omitting the flag
|
|
91
|
+
EOF
|
|
44
92
|
}
|
|
45
93
|
|
|
46
94
|
err() { echo " ❌ $*" >&2; }
|
|
47
95
|
|
|
96
|
+
# Validate a comma-separated tool list against $VALID_TOOLS. Empty input is
|
|
97
|
+
# rejected so a stray --tools= does not silently behave like --tools=all.
|
|
98
|
+
validate_tools() {
|
|
99
|
+
local raw="$1"
|
|
100
|
+
[[ -z "$raw" ]] && { err "--tools requires a non-empty value (use --list-tools to see options)"; return 1; }
|
|
101
|
+
local IFS=','
|
|
102
|
+
local item
|
|
103
|
+
for item in $raw; do
|
|
104
|
+
[[ -z "$item" ]] && { err "--tools contains an empty entry"; return 1; }
|
|
105
|
+
if [[ " $VALID_TOOLS " != *" $item "* ]]; then
|
|
106
|
+
err "Unknown tool ID: $item (run --list-tools for the catalog)"
|
|
107
|
+
return 1
|
|
108
|
+
fi
|
|
109
|
+
done
|
|
110
|
+
return 0
|
|
111
|
+
}
|
|
112
|
+
|
|
48
113
|
while [[ $# -gt 0 ]]; do
|
|
49
114
|
case "$1" in
|
|
50
115
|
--source) SOURCE_DIR="$2"; shift 2 ;;
|
|
@@ -53,17 +118,84 @@ while [[ $# -gt 0 ]]; do
|
|
|
53
118
|
--target=*) TARGET_DIR="${1#*=}"; shift ;;
|
|
54
119
|
--profile) PROFILE="$2"; shift 2 ;;
|
|
55
120
|
--profile=*) PROFILE="${1#*=}"; shift ;;
|
|
121
|
+
--tools) TOOLS="$2"; TOOLS_EXPLICIT=true; shift 2 ;;
|
|
122
|
+
--tools=*) TOOLS="${1#*=}"; TOOLS_EXPLICIT=true; shift ;;
|
|
123
|
+
--list-tools) LIST_TOOLS=true; shift ;;
|
|
124
|
+
--yes|-y) YES=true; shift ;;
|
|
56
125
|
--force) FORCE=true; shift ;;
|
|
57
126
|
--dry-run) DRY_RUN=true; shift ;;
|
|
58
127
|
--verbose) VERBOSE=true; shift ;;
|
|
59
128
|
--quiet) QUIET=true; shift ;;
|
|
60
129
|
--skip-sync) SKIP_SYNC=true; shift ;;
|
|
61
130
|
--skip-bridges) SKIP_BRIDGES=true; shift ;;
|
|
131
|
+
--global) GLOBAL_INSTALL=true; shift ;;
|
|
132
|
+
--uninstall) UNINSTALL=true; shift ;;
|
|
62
133
|
--help|-h) show_help; exit 0 ;;
|
|
63
134
|
*) err "Unknown argument: $1"; show_help >&2; exit 1 ;;
|
|
64
135
|
esac
|
|
65
136
|
done
|
|
66
137
|
|
|
138
|
+
if $LIST_TOOLS; then
|
|
139
|
+
list_tools
|
|
140
|
+
exit 0
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Interactive --tools picker (S9). Fires only when:
|
|
144
|
+
# - --tools was not explicitly passed
|
|
145
|
+
# - --yes / -y was not passed (CI / non-interactive opt-out)
|
|
146
|
+
# - stdin AND stdout are both TTYs (so we're not in a pipe / curl|bash flow)
|
|
147
|
+
# - --dry-run, --quiet, --skip-sync, --skip-bridges did not opt out of UX
|
|
148
|
+
# Otherwise we fall through to the backward-compatible "all" default.
|
|
149
|
+
prompt_tools() {
|
|
150
|
+
local choice picked tool i=0
|
|
151
|
+
local -a menu=(claude-code claude-desktop cursor windsurf cline gemini-cli copilot augment aider codex)
|
|
152
|
+
echo ""
|
|
153
|
+
echo " 📦 Pick the tools to install (comma-separated numbers, blank = all):"
|
|
154
|
+
for tool in "${menu[@]}"; do
|
|
155
|
+
i=$((i+1))
|
|
156
|
+
printf " %2d) %s\n" "$i" "$tool"
|
|
157
|
+
done
|
|
158
|
+
echo " a) all (default)"
|
|
159
|
+
echo ""
|
|
160
|
+
printf " Selection: "
|
|
161
|
+
IFS= read -r choice || choice=""
|
|
162
|
+
choice="${choice// /}"
|
|
163
|
+
if [[ -z "$choice" || "$choice" == "a" || "$choice" == "all" ]]; then
|
|
164
|
+
TOOLS="all"; return
|
|
165
|
+
fi
|
|
166
|
+
picked=""
|
|
167
|
+
local IFS=','
|
|
168
|
+
for n in $choice; do
|
|
169
|
+
if ! [[ "$n" =~ ^[0-9]+$ ]] || (( n < 1 || n > ${#menu[@]} )); then
|
|
170
|
+
err "Invalid selection: $n (expected 1-${#menu[@]} or 'a')"; exit 1
|
|
171
|
+
fi
|
|
172
|
+
picked+="${menu[$((n-1))]},"
|
|
173
|
+
done
|
|
174
|
+
TOOLS="${picked%,}"
|
|
175
|
+
echo " ✅ Selected: $TOOLS"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if ! $TOOLS_EXPLICIT && ! $YES && ! $QUIET && ! $LIST_TOOLS && ! $GLOBAL_INSTALL && [[ -t 0 && -t 1 ]]; then
|
|
179
|
+
prompt_tools
|
|
180
|
+
TOOLS_EXPLICIT=true
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
if $UNINSTALL && ! $GLOBAL_INSTALL; then
|
|
184
|
+
err "--uninstall is only valid combined with --global"
|
|
185
|
+
exit 1
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# Default = "all": backward compatible with pre-Phase-1 invocations. An
|
|
189
|
+
# explicit --tools= (empty value) is rejected by validate_tools — only an
|
|
190
|
+
# absent flag falls through to "all".
|
|
191
|
+
if ! $TOOLS_EXPLICIT && [[ -z "$TOOLS" ]]; then
|
|
192
|
+
TOOLS="all"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
if ! validate_tools "$TOOLS"; then
|
|
196
|
+
exit 1
|
|
197
|
+
fi
|
|
198
|
+
|
|
67
199
|
# Auto-detect source: directory above this script
|
|
68
200
|
if [[ -z "$SOURCE_DIR" ]]; then
|
|
69
201
|
SOURCE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
@@ -103,6 +235,7 @@ run_sync() {
|
|
|
103
235
|
$DRY_RUN && args+=(--dry-run)
|
|
104
236
|
$VERBOSE && args+=(--verbose)
|
|
105
237
|
$QUIET && args+=(--quiet)
|
|
238
|
+
args+=(--tools="$TOOLS")
|
|
106
239
|
bash "$INSTALL_SH" "${args[@]}"
|
|
107
240
|
}
|
|
108
241
|
|
|
@@ -123,10 +256,32 @@ run_bridges() {
|
|
|
123
256
|
[[ -n "$PROFILE" ]] && args+=(--profile="$PROFILE")
|
|
124
257
|
$FORCE && args+=(--force)
|
|
125
258
|
$QUIET && args+=(--quiet)
|
|
259
|
+
args+=(--tools="$TOOLS")
|
|
126
260
|
"$python_bin" "$INSTALL_PY" "${args[@]}"
|
|
127
261
|
}
|
|
128
262
|
|
|
129
263
|
RC=0
|
|
264
|
+
|
|
265
|
+
# --global: dedicated user-scope path. Skips the project-bridge sync entirely
|
|
266
|
+
# and forwards to install.py --global (Phase 3 / S13). --uninstall pairs with
|
|
267
|
+
# --global to wipe the event4u/ namespace dir under each surface.
|
|
268
|
+
if $GLOBAL_INSTALL; then
|
|
269
|
+
if ! python_bin="$(find_python)"; then
|
|
270
|
+
err "Python 3 not found — required for --global install"
|
|
271
|
+
exit 1
|
|
272
|
+
fi
|
|
273
|
+
if [[ ! -f "$INSTALL_PY" ]]; then
|
|
274
|
+
err "Missing $INSTALL_PY"
|
|
275
|
+
exit 1
|
|
276
|
+
fi
|
|
277
|
+
args=(--package "$SOURCE_DIR" --global --tools="$TOOLS")
|
|
278
|
+
$FORCE && args+=(--force)
|
|
279
|
+
$QUIET && args+=(--quiet)
|
|
280
|
+
$UNINSTALL && args+=(--uninstall)
|
|
281
|
+
"$python_bin" "$INSTALL_PY" "${args[@]}"
|
|
282
|
+
exit $?
|
|
283
|
+
fi
|
|
284
|
+
|
|
130
285
|
if ! $SKIP_SYNC; then
|
|
131
286
|
if [[ ! -f "$INSTALL_SH" ]]; then
|
|
132
287
|
err "Missing $INSTALL_SH"
|