@trac3er/oh-my-god 1.0.2
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 +36 -0
- package/.claude-plugin/plugin.json +23 -0
- package/.claude-plugin/scripts/install.sh +49 -0
- package/.claude-plugin/scripts/uninstall.sh +80 -0
- package/.claude-plugin/scripts/update.sh +84 -0
- package/.mcp.json +20 -0
- package/LICENSE +21 -0
- package/OMG-setup.sh +1093 -0
- package/README.md +335 -0
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/UPSTREAM_DIFF.md +20 -0
- package/agents/__init__.py +1 -0
- package/agents/_model_roles.yaml +26 -0
- package/agents/designer.md +67 -0
- package/agents/explore.md +60 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-api-builder.md +23 -0
- package/agents/omg-architect-mode.md +43 -0
- package/agents/omg-architect.md +13 -0
- package/agents/omg-backend-engineer.md +43 -0
- package/agents/omg-critic.md +16 -0
- package/agents/omg-database-engineer.md +43 -0
- package/agents/omg-escalation-router.md +17 -0
- package/agents/omg-executor.md +12 -0
- package/agents/omg-frontend-designer.md +42 -0
- package/agents/omg-implement-mode.md +50 -0
- package/agents/omg-infra-engineer.md +43 -0
- package/agents/omg-qa-tester.md +16 -0
- package/agents/omg-research-mode.md +43 -0
- package/agents/omg-security-auditor.md +43 -0
- package/agents/omg-testing-engineer.md +43 -0
- package/agents/plan.md +80 -0
- package/agents/quick_task.md +64 -0
- package/agents/reviewer.md +83 -0
- package/agents/task.md +71 -0
- package/commands/OMG:ccg.md +22 -0
- package/commands/OMG:compat.md +57 -0
- package/commands/OMG:crazy.md +125 -0
- package/commands/OMG:domain-init.md +11 -0
- package/commands/OMG:escalate.md +52 -0
- package/commands/OMG:health-check.md +45 -0
- package/commands/OMG:init.md +134 -0
- package/commands/OMG:mode.md +44 -0
- package/commands/OMG:project-init.md +11 -0
- package/commands/OMG:ralph-start.md +43 -0
- package/commands/OMG:ralph-stop.md +23 -0
- package/commands/OMG:teams.md +39 -0
- package/commands/ai-commit.md +113 -0
- package/commands/ccg.md +9 -0
- package/commands/create-agent.md +183 -0
- package/commands/omc-teams.md +9 -0
- package/commands/session-branch.md +85 -0
- package/commands/session-fork.md +53 -0
- package/commands/session-merge.md +134 -0
- package/commands/theme.md +44 -0
- package/config/lsp_languages.yaml +324 -0
- package/config/themes/catppuccin-frappe.yaml +14 -0
- package/config/themes/catppuccin-latte.yaml +14 -0
- package/config/themes/catppuccin-macchiato.yaml +14 -0
- package/config/themes/catppuccin-mocha.yaml +14 -0
- package/config/themes/dracula.yaml +14 -0
- package/config/themes/gruvbox-dark.yaml +14 -0
- package/config/themes/nord.yaml +14 -0
- package/config/themes/one-dark.yaml +14 -0
- package/config/themes/solarized-dark.yaml +14 -0
- package/config/themes/tokyo-night.yaml +14 -0
- package/control_plane/__init__.py +2 -0
- package/control_plane/openapi.yaml +109 -0
- package/control_plane/server.py +107 -0
- package/control_plane/service.py +148 -0
- package/crates/omg-natives/Cargo.toml +17 -0
- package/crates/omg-natives/src/clipboard.rs +5 -0
- package/crates/omg-natives/src/glob.rs +15 -0
- package/crates/omg-natives/src/grep.rs +15 -0
- package/crates/omg-natives/src/highlight.rs +15 -0
- package/crates/omg-natives/src/html.rs +14 -0
- package/crates/omg-natives/src/image.rs +5 -0
- package/crates/omg-natives/src/keys.rs +5 -0
- package/crates/omg-natives/src/lib.rs +36 -0
- package/crates/omg-natives/src/prof.rs +5 -0
- package/crates/omg-natives/src/ps.rs +5 -0
- package/crates/omg-natives/src/shell.rs +5 -0
- package/crates/omg-natives/src/task.rs +5 -0
- package/crates/omg-natives/src/text.rs +14 -0
- package/hooks/_agent_registry.py +421 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +476 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/config-guard.py +163 -0
- package/hooks/context_pressure.py +53 -0
- package/hooks/credential_store.py +801 -0
- package/hooks/fetch-rate-limits.py +212 -0
- package/hooks/firewall.py +48 -0
- package/hooks/hashline-formatter-bridge.py +224 -0
- package/hooks/hashline-injector.py +273 -0
- package/hooks/hashline-validator.py +216 -0
- package/hooks/idle-detector.py +95 -0
- package/hooks/intentgate-keyword-detector.py +188 -0
- package/hooks/magic-keyword-router.py +195 -0
- package/hooks/policy_engine.py +310 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +199 -0
- package/hooks/pre-compact.py +204 -0
- package/hooks/pre-tool-inject.py +98 -0
- package/hooks/prompt-enhancer.py +672 -0
- package/hooks/quality-runner.py +191 -0
- package/hooks/secret-guard.py +47 -0
- package/hooks/session-end-capture.py +137 -0
- package/hooks/session-start.py +275 -0
- package/hooks/shadow_manager.py +297 -0
- package/hooks/state_migration.py +209 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +929 -0
- package/hooks/test-validator.py +138 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +126 -0
- package/hooks/trust_review.py +524 -0
- package/install.sh +9 -0
- package/omg_natives/__init__.py +186 -0
- package/omg_natives/_bindings.py +165 -0
- package/omg_natives/clipboard.py +36 -0
- package/omg_natives/glob.py +42 -0
- package/omg_natives/grep.py +61 -0
- package/omg_natives/highlight.py +54 -0
- package/omg_natives/html.py +157 -0
- package/omg_natives/image.py +51 -0
- package/omg_natives/keys.py +46 -0
- package/omg_natives/prof.py +39 -0
- package/omg_natives/ps.py +93 -0
- package/omg_natives/shell.py +58 -0
- package/omg_natives/task.py +41 -0
- package/omg_natives/text.py +50 -0
- package/package.json +26 -0
- package/plugins/README.md +82 -0
- package/plugins/advanced/commands/OMG:code-review.md +114 -0
- package/plugins/advanced/commands/OMG:deep-plan.md +221 -0
- package/plugins/advanced/commands/OMG:handoff.md +115 -0
- package/plugins/advanced/commands/OMG:learn.md +110 -0
- package/plugins/advanced/commands/OMG:maintainer.md +31 -0
- package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG:security-review.md +119 -0
- package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG:ship.md +46 -0
- package/plugins/advanced/plugin.json +96 -0
- package/plugins/core/plugin.json +82 -0
- package/pytest.ini +5 -0
- package/registry/__init__.py +1 -0
- package/registry/verify_artifact.py +90 -0
- package/rules/contextual/architect-mode.md +9 -0
- package/rules/contextual/big-picture.md +20 -0
- package/rules/contextual/code-hygiene.md +26 -0
- package/rules/contextual/context-management.md +19 -0
- package/rules/contextual/context-minimization.md +32 -0
- package/rules/contextual/ddd-sdd.md +28 -0
- package/rules/contextual/dependency-safety.md +16 -0
- package/rules/contextual/doc-check.md +13 -0
- package/rules/contextual/implement-mode.md +9 -0
- package/rules/contextual/infra-safety.md +14 -0
- package/rules/contextual/outside-in.md +13 -0
- package/rules/contextual/persistent-mode.md +24 -0
- package/rules/contextual/research-mode.md +9 -0
- package/rules/contextual/security-domains.md +25 -0
- package/rules/contextual/vision-detection.md +27 -0
- package/rules/contextual/web-search.md +25 -0
- package/rules/contextual/write-verify.md +23 -0
- package/rules/core/00-truth.md +20 -0
- package/rules/core/01-surgical.md +19 -0
- package/rules/core/02-circuit-breaker.md +22 -0
- package/rules/core/03-ensemble.md +28 -0
- package/rules/core/04-testing.md +30 -0
- package/runtime/__init__.py +32 -0
- package/runtime/adapters/__init__.py +13 -0
- package/runtime/adapters/claude.py +60 -0
- package/runtime/adapters/gpt.py +53 -0
- package/runtime/adapters/local.py +53 -0
- package/runtime/business_workflow.py +220 -0
- package/runtime/compat.py +1299 -0
- package/runtime/custom_agent_loader.py +366 -0
- package/runtime/dispatcher.py +47 -0
- package/runtime/ecosystem.py +371 -0
- package/runtime/legacy_compat.py +7 -0
- package/runtime/omc_compat.py +7 -0
- package/runtime/omc_contract_snapshot.json +916 -0
- package/runtime/omg_compat_contract_snapshot.json +916 -0
- package/runtime/subagent_dispatcher.py +362 -0
- package/runtime/team_router.py +838 -0
- package/scripts/check-omc-contract-snapshot.py +12 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-standalone-clean.py +102 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-omc.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +493 -0
- package/scripts/settings-merge.py +224 -0
- package/scripts/verify-no-omc.sh +5 -0
- package/scripts/verify-standalone.sh +21 -0
- package/templates/idea.yml +30 -0
- package/templates/policy.yaml +15 -0
- package/templates/profile.yaml +25 -0
- package/templates/runtime.yaml +12 -0
- package/templates/working-memory.md +17 -0
- package/tools/__init__.py +2 -0
- package/tools/browser_consent.py +289 -0
- package/tools/browser_stealth.py +481 -0
- package/tools/browser_tool.py +448 -0
- package/tools/changelog_generator.py +268 -0
- package/tools/commit_splitter.py +361 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -0
- package/tools/git_inspector.py +298 -0
- package/tools/lsp_client.py +275 -0
- package/tools/lsp_discovery.py +231 -0
- package/tools/lsp_operations.py +392 -0
- package/tools/python_repl.py +656 -0
- package/tools/python_sandbox.py +609 -0
- package/tools/search_providers/__init__.py +77 -0
- package/tools/search_providers/brave.py +115 -0
- package/tools/search_providers/exa.py +116 -0
- package/tools/search_providers/jina.py +104 -0
- package/tools/search_providers/perplexity.py +139 -0
- package/tools/search_providers/synthetic.py +74 -0
- package/tools/session_snapshot.py +736 -0
- package/tools/ssh_manager.py +912 -0
- package/tools/theme_engine.py +294 -0
- package/tools/theme_selector.py +137 -0
- package/tools/web_search.py +622 -0
package/OMG-setup.sh
ADDED
|
@@ -0,0 +1,1093 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
6
|
+
BACKUP_TS="$(date +%Y%m%d_%H%M%S)"
|
|
7
|
+
BACKUP_DIR="$CLAUDE_DIR/.omg-backup-$BACKUP_TS"
|
|
8
|
+
VERSION="omg-v1-$(date +%Y%m%d)"
|
|
9
|
+
|
|
10
|
+
PLUGIN_NAME="omg"
|
|
11
|
+
PLUGIN_MARKETPLACE="oh-advanced-layer"
|
|
12
|
+
LEGACY_PLUGIN_MARKETPLACE="omg"
|
|
13
|
+
PLUGIN_REF="${PLUGIN_NAME}@${PLUGIN_MARKETPLACE}"
|
|
14
|
+
LEGACY_PLUGIN_REF="${PLUGIN_NAME}@${LEGACY_PLUGIN_MARKETPLACE}"
|
|
15
|
+
PLUGIN_CACHE_DIR="$CLAUDE_DIR/plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME"
|
|
16
|
+
LEGACY_PLUGIN_CACHE_DIR="$CLAUDE_DIR/plugins/cache/$LEGACY_PLUGIN_MARKETPLACE/$PLUGIN_NAME"
|
|
17
|
+
PLUGIN_BUNDLE_MARKER_FILE=".omg-plugin-bundle"
|
|
18
|
+
|
|
19
|
+
ACTION="install"
|
|
20
|
+
ACTION_EXPLICIT=false
|
|
21
|
+
DRY_RUN=false
|
|
22
|
+
NON_INTERACTIVE=false
|
|
23
|
+
MERGE_POLICY="ask"
|
|
24
|
+
FRESH_INSTALL=false
|
|
25
|
+
INSTALL_AS_PLUGIN=false
|
|
26
|
+
USE_SYMLINK=false
|
|
27
|
+
ERRORS=0
|
|
28
|
+
OMG_MANIFEST="$CLAUDE_DIR/.omg-manifest"
|
|
29
|
+
NEW_MANIFEST_ENTRIES=()
|
|
30
|
+
|
|
31
|
+
V3_RULES=(
|
|
32
|
+
"00-truth-evidence.md" "01-enforcement-map.md" "02-doc-check.md"
|
|
33
|
+
"03-working-memory.md" "04-quality-gate.md" "05-structured-reports.md"
|
|
34
|
+
"06-infra-safety.md" "07-cross-model.md" "08-big-picture.md"
|
|
35
|
+
"09-surgical-changes.md" "10-code-simplifier.md" "11-dependency-safety.md"
|
|
36
|
+
"12-circuit-breaker.md" "13-planning-checklist.md" "14-auto-commands.md"
|
|
37
|
+
"15-context-management.md" "16-honest-testing.md" "17-ensemble-collaboration.md"
|
|
38
|
+
"18-collaborative-solving.md" "19-outside-in.md" "20-project-identity.md"
|
|
39
|
+
"21-verified-claims.md" "22-auto-plugin-mcp.md"
|
|
40
|
+
)
|
|
41
|
+
V3_AGENTS_REMOVE=(cross-validator.md dependency-guardian.md infra-guardian.md perf-analyst.md ui-reviewer.md)
|
|
42
|
+
OLD_OMG_AGENTS=(architect.md critic.md executor.md qa-tester.md escalation-router.md)
|
|
43
|
+
V3_COMMANDS_REMOVE=(cross-review.md simplify.md)
|
|
44
|
+
V4_COMMANDS_REMOVE=(
|
|
45
|
+
code-review.md deep-plan.md domain-init.md escalate.md handoff.md
|
|
46
|
+
health-check.md learn.md project-init.md security-review.md
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Dynamic hook discovery — no hardcoded list.
|
|
50
|
+
# Used by remove_omg_files() as fallback when manifest is absent.
|
|
51
|
+
build_omg_hooks_list() {
|
|
52
|
+
OMG_HOOKS=()
|
|
53
|
+
for f in "$SCRIPT_DIR"/hooks/*.py; do
|
|
54
|
+
[ -f "$f" ] && OMG_HOOKS+=("$(basename "$f")")
|
|
55
|
+
done
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
usage() {
|
|
59
|
+
cat <<EOF
|
|
60
|
+
OMG Setup Manager
|
|
61
|
+
|
|
62
|
+
Usage:
|
|
63
|
+
./OMG-setup.sh <action> [OPTIONS]
|
|
64
|
+
./OMG-setup.sh [OPTIONS] # defaults to install
|
|
65
|
+
./OMG-setup.sh # interactive menu in terminal mode
|
|
66
|
+
|
|
67
|
+
Actions:
|
|
68
|
+
install Install or upgrade OMG components
|
|
69
|
+
update Alias of install (explicit update mode)
|
|
70
|
+
reinstall Clean reinstall (remove OMG files, then install)
|
|
71
|
+
uninstall Remove OMG-managed files from ~/.claude
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
--fresh For install/update: clean reinstall before install
|
|
75
|
+
--symlink Use symlinks instead of copies (dev mode - live updates)
|
|
76
|
+
--install-as-plugin
|
|
77
|
+
Install plugin bundle (plugin.json + MCP + HUD) together
|
|
78
|
+
--dry-run Show what would happen without writing files
|
|
79
|
+
--non-interactive Skip prompts (CI/automation mode)
|
|
80
|
+
--merge-policy=X Settings merge: ask (default), apply, skip
|
|
81
|
+
-h, --help Show this help
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
./OMG-setup.sh install
|
|
85
|
+
./OMG-setup.sh install --symlink # Dev mode: live updates from repo
|
|
86
|
+
./OMG-setup.sh install --install-as-plugin
|
|
87
|
+
./OMG-setup.sh update --non-interactive --merge-policy=apply
|
|
88
|
+
./OMG-setup.sh reinstall --dry-run
|
|
89
|
+
./OMG-setup.sh uninstall --dry-run
|
|
90
|
+
EOF
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
is_standalone_installed() {
|
|
94
|
+
[ -f "$CLAUDE_DIR/hooks/.omg-version" ] || [ -d "$CLAUDE_DIR/omg-runtime" ]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
is_plugin_installed() {
|
|
98
|
+
local marker_new="$PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
|
|
99
|
+
local marker_legacy="$LEGACY_PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
|
|
100
|
+
if [ -f "$marker_new" ] || [ -f "$marker_legacy" ]; then
|
|
101
|
+
return 0
|
|
102
|
+
fi
|
|
103
|
+
local installed_plugins="$CLAUDE_DIR/plugins/installed_plugins.json"
|
|
104
|
+
if [ -f "$installed_plugins" ] && grep -Eq "\"$PLUGIN_REF\"|\"$LEGACY_PLUGIN_REF\"" "$installed_plugins" 2>/dev/null; then
|
|
105
|
+
return 0
|
|
106
|
+
fi
|
|
107
|
+
return 1
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
prompt_start_action() {
|
|
111
|
+
if $ACTION_EXPLICIT || $NON_INTERACTIVE || $DRY_RUN; then
|
|
112
|
+
return 0
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
local standalone_installed=false
|
|
116
|
+
local plugin_installed=false
|
|
117
|
+
is_standalone_installed && standalone_installed=true
|
|
118
|
+
is_plugin_installed && plugin_installed=true
|
|
119
|
+
|
|
120
|
+
echo ""
|
|
121
|
+
echo "Select OMG setup action:"
|
|
122
|
+
echo " 1. Install standalone"
|
|
123
|
+
if $standalone_installed; then
|
|
124
|
+
echo " 2. Update standalone"
|
|
125
|
+
fi
|
|
126
|
+
echo " 3. Install as plugin"
|
|
127
|
+
if $plugin_installed; then
|
|
128
|
+
echo " 4. Update plugin install"
|
|
129
|
+
fi
|
|
130
|
+
echo " 5. Uninstall"
|
|
131
|
+
echo " 0. Cancel"
|
|
132
|
+
echo ""
|
|
133
|
+
|
|
134
|
+
read -p "Choose [1/2/3/4/5/0]: " -r
|
|
135
|
+
case "${REPLY:-}" in
|
|
136
|
+
1)
|
|
137
|
+
ACTION="install"
|
|
138
|
+
INSTALL_AS_PLUGIN=false
|
|
139
|
+
;;
|
|
140
|
+
2)
|
|
141
|
+
if $standalone_installed; then
|
|
142
|
+
ACTION="update"
|
|
143
|
+
INSTALL_AS_PLUGIN=false
|
|
144
|
+
else
|
|
145
|
+
echo "Standalone update unavailable (not installed)."
|
|
146
|
+
exit 1
|
|
147
|
+
fi
|
|
148
|
+
;;
|
|
149
|
+
3)
|
|
150
|
+
ACTION="install"
|
|
151
|
+
INSTALL_AS_PLUGIN=true
|
|
152
|
+
;;
|
|
153
|
+
4)
|
|
154
|
+
if $plugin_installed; then
|
|
155
|
+
ACTION="update"
|
|
156
|
+
INSTALL_AS_PLUGIN=true
|
|
157
|
+
else
|
|
158
|
+
echo "Plugin update unavailable (plugin install not detected)."
|
|
159
|
+
exit 1
|
|
160
|
+
fi
|
|
161
|
+
;;
|
|
162
|
+
5)
|
|
163
|
+
ACTION="uninstall"
|
|
164
|
+
;;
|
|
165
|
+
0)
|
|
166
|
+
echo "Cancelled by user."
|
|
167
|
+
exit 0
|
|
168
|
+
;;
|
|
169
|
+
*)
|
|
170
|
+
echo "Invalid selection."
|
|
171
|
+
exit 1
|
|
172
|
+
;;
|
|
173
|
+
esac
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
parse_args() {
|
|
177
|
+
if [ $# -gt 0 ]; then
|
|
178
|
+
case "$1" in
|
|
179
|
+
install|update|reinstall|uninstall)
|
|
180
|
+
ACTION="$1"
|
|
181
|
+
ACTION_EXPLICIT=true
|
|
182
|
+
shift
|
|
183
|
+
;;
|
|
184
|
+
help|-h|--help)
|
|
185
|
+
usage
|
|
186
|
+
exit 0
|
|
187
|
+
;;
|
|
188
|
+
esac
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
for arg in "$@"; do
|
|
192
|
+
case "$arg" in
|
|
193
|
+
--dry-run) DRY_RUN=true ;;
|
|
194
|
+
--symlink) USE_SYMLINK=true ;;
|
|
195
|
+
--non-interactive) NON_INTERACTIVE=true ;;
|
|
196
|
+
--fresh) FRESH_INSTALL=true ;;
|
|
197
|
+
--install-as-plugin) INSTALL_AS_PLUGIN=true ;;
|
|
198
|
+
--merge-policy=*) MERGE_POLICY="${arg#*=}" ;;
|
|
199
|
+
--help|-h)
|
|
200
|
+
usage
|
|
201
|
+
exit 0
|
|
202
|
+
;;
|
|
203
|
+
*)
|
|
204
|
+
echo "Unknown option: $arg"
|
|
205
|
+
echo ""
|
|
206
|
+
usage
|
|
207
|
+
exit 1
|
|
208
|
+
;;
|
|
209
|
+
esac
|
|
210
|
+
done
|
|
211
|
+
|
|
212
|
+
if [ "$ACTION" = "reinstall" ]; then
|
|
213
|
+
FRESH_INSTALL=true
|
|
214
|
+
fi
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
preflight() {
|
|
218
|
+
echo "Pre-flight checks..."
|
|
219
|
+
if ! command -v python3 &>/dev/null; then
|
|
220
|
+
echo " ❌ python3 not found. Install: https://www.python.org/downloads/"
|
|
221
|
+
exit 1
|
|
222
|
+
fi
|
|
223
|
+
local py_ver py_maj py_min
|
|
224
|
+
py_ver=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
|
|
225
|
+
py_maj=$(echo "$py_ver" | cut -d. -f1)
|
|
226
|
+
py_min=$(echo "$py_ver" | cut -d. -f2)
|
|
227
|
+
if [ "$py_maj" -lt 3 ] || { [ "$py_maj" -eq 3 ] && [ "$py_min" -lt 8 ]; }; then
|
|
228
|
+
echo " ❌ Python $py_ver found, 3.8+ required"
|
|
229
|
+
exit 1
|
|
230
|
+
fi
|
|
231
|
+
echo " ✓ Python $py_ver"
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
ensure_backup() {
|
|
235
|
+
if ! $DRY_RUN; then
|
|
236
|
+
mkdir -p "$BACKUP_DIR"
|
|
237
|
+
for dir in rules hooks agents commands; do
|
|
238
|
+
[ -d "$CLAUDE_DIR/$dir" ] && cp -r "$CLAUDE_DIR/$dir" "$BACKUP_DIR/$dir" 2>/dev/null || true
|
|
239
|
+
done
|
|
240
|
+
[ -f "$CLAUDE_DIR/settings.json" ] && cp "$CLAUDE_DIR/settings.json" "$BACKUP_DIR/"
|
|
241
|
+
prune_old_backups
|
|
242
|
+
fi
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
prune_old_backups() {
|
|
246
|
+
local backups=()
|
|
247
|
+
while IFS= read -r path; do
|
|
248
|
+
backups+=("$path")
|
|
249
|
+
done < <(find "$CLAUDE_DIR" -maxdepth 1 -type d -name ".omg-backup-*" | sort)
|
|
250
|
+
|
|
251
|
+
local total=${#backups[@]}
|
|
252
|
+
if [ "$total" -le 2 ]; then
|
|
253
|
+
return 0
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
local remove_count=$((total - 2))
|
|
257
|
+
for old in "${backups[@]:0:$remove_count}"; do
|
|
258
|
+
[[ "$old" == "$CLAUDE_DIR"/* ]] || {
|
|
259
|
+
echo "ERROR: backup prune target outside expected directory: $old" >&2
|
|
260
|
+
exit 1
|
|
261
|
+
}
|
|
262
|
+
rm -rf "$old"
|
|
263
|
+
done
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
is_omg_managed_command_file() {
|
|
267
|
+
local file="$1"
|
|
268
|
+
if [ ! -f "$file" ]; then
|
|
269
|
+
return 1
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
if grep -q "OMG-AUTO-COMPAT-ALIAS" "$file" 2>/dev/null; then
|
|
273
|
+
return 0
|
|
274
|
+
fi
|
|
275
|
+
if grep -q "OMG-MANAGED-COMMAND" "$file" 2>/dev/null; then
|
|
276
|
+
return 0
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
local base
|
|
280
|
+
base="$(basename "$file")"
|
|
281
|
+
if [[ "$base" == OMG:* ]] && grep -q "/OMG:" "$file" 2>/dev/null; then
|
|
282
|
+
return 0
|
|
283
|
+
fi
|
|
284
|
+
return 1
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
mark_omg_managed_command_file() {
|
|
288
|
+
local file="$1"
|
|
289
|
+
if [ ! -f "$file" ]; then
|
|
290
|
+
return 0
|
|
291
|
+
fi
|
|
292
|
+
if ! grep -q "OMG-MANAGED-COMMAND" "$file" 2>/dev/null; then
|
|
293
|
+
printf "\n<!-- OMG-MANAGED-COMMAND -->\n" >> "$file"
|
|
294
|
+
fi
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# Install a file or directory - either copy or symlink based on USE_SYMLINK
|
|
298
|
+
# Usage: install_file <source> <target> [type: file|dir]
|
|
299
|
+
install_file() {
|
|
300
|
+
local src="$1"
|
|
301
|
+
local target="$2"
|
|
302
|
+
local type="${3:-file}"
|
|
303
|
+
|
|
304
|
+
if $USE_SYMLINK; then
|
|
305
|
+
# In symlink mode, create symlink from target -> source
|
|
306
|
+
# First remove existing file/dir if present
|
|
307
|
+
if [ -e "$target" ] || [ -L "$target" ]; then
|
|
308
|
+
rm -rf "$target"
|
|
309
|
+
fi
|
|
310
|
+
ln -s "$src" "$target"
|
|
311
|
+
else
|
|
312
|
+
# In copy mode, do regular copy
|
|
313
|
+
if [ "$type" = "dir" ]; then
|
|
314
|
+
cp -R "$src" "$target"
|
|
315
|
+
else
|
|
316
|
+
cp "$src" "$target"
|
|
317
|
+
fi
|
|
318
|
+
fi
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
track_file() {
|
|
322
|
+
NEW_MANIFEST_ENTRIES+=("$1")
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
reconcile_stale_files() {
|
|
326
|
+
if [ ! -f "$OMG_MANIFEST" ]; then
|
|
327
|
+
echo " (no previous manifest — first install, skipping reconciliation)"
|
|
328
|
+
return 0
|
|
329
|
+
fi
|
|
330
|
+
local stale=0
|
|
331
|
+
while IFS= read -r old_entry; do
|
|
332
|
+
[ -n "$old_entry" ] || continue
|
|
333
|
+
[[ "$old_entry" == "#"* ]] && continue
|
|
334
|
+
local found=false
|
|
335
|
+
for new_entry in "${NEW_MANIFEST_ENTRIES[@]}"; do
|
|
336
|
+
if [ "$old_entry" = "$new_entry" ]; then
|
|
337
|
+
found=true
|
|
338
|
+
break
|
|
339
|
+
fi
|
|
340
|
+
done
|
|
341
|
+
if ! $found; then
|
|
342
|
+
local target="$CLAUDE_DIR/$old_entry"
|
|
343
|
+
if [ -f "$target" ]; then
|
|
344
|
+
if ! $DRY_RUN; then
|
|
345
|
+
rm -f "$target"
|
|
346
|
+
fi
|
|
347
|
+
echo " - $old_entry (removed from source)"
|
|
348
|
+
stale=$((stale + 1))
|
|
349
|
+
fi
|
|
350
|
+
fi
|
|
351
|
+
done < "$OMG_MANIFEST"
|
|
352
|
+
if [ $stale -eq 0 ]; then
|
|
353
|
+
echo " (no stale files)"
|
|
354
|
+
elif $DRY_RUN; then
|
|
355
|
+
echo " (dry-run: would remove $stale stale file(s))"
|
|
356
|
+
else
|
|
357
|
+
echo " ✓ Cleaned $stale stale file(s)"
|
|
358
|
+
fi
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
write_omg_manifest() {
|
|
362
|
+
if ! $DRY_RUN; then
|
|
363
|
+
printf '%s\n' "${NEW_MANIFEST_ENTRIES[@]}" | sort > "$OMG_MANIFEST"
|
|
364
|
+
fi
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
prune_plugin_mcp_from_settings() {
|
|
368
|
+
local mcp_path="$CLAUDE_DIR/.mcp.json"
|
|
369
|
+
if [ ! -f "$mcp_path" ]; then
|
|
370
|
+
return 0
|
|
371
|
+
fi
|
|
372
|
+
python3 - "$mcp_path" <<'PY'
|
|
373
|
+
import json
|
|
374
|
+
import sys
|
|
375
|
+
from pathlib import Path
|
|
376
|
+
|
|
377
|
+
path = Path(sys.argv[1])
|
|
378
|
+
try:
|
|
379
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
380
|
+
except Exception:
|
|
381
|
+
print("0")
|
|
382
|
+
raise SystemExit(0)
|
|
383
|
+
|
|
384
|
+
servers = data.get("mcpServers")
|
|
385
|
+
if not isinstance(servers, dict):
|
|
386
|
+
print("0")
|
|
387
|
+
raise SystemExit(0)
|
|
388
|
+
|
|
389
|
+
removed = 0
|
|
390
|
+
for key in ("context7", "filesystem", "websearch", "chrome-devtools"):
|
|
391
|
+
if key in servers:
|
|
392
|
+
servers.pop(key, None)
|
|
393
|
+
removed += 1
|
|
394
|
+
|
|
395
|
+
if removed:
|
|
396
|
+
path.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
397
|
+
|
|
398
|
+
print(str(removed))
|
|
399
|
+
PY
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
merge_plugin_mcp_into_settings() {
|
|
403
|
+
local mcp_path="$CLAUDE_DIR/.mcp.json"
|
|
404
|
+
local source_mcp_path="$SCRIPT_DIR/.mcp.json"
|
|
405
|
+
python3 - "$mcp_path" "$source_mcp_path" <<'PY'
|
|
406
|
+
import json
|
|
407
|
+
import sys
|
|
408
|
+
from pathlib import Path
|
|
409
|
+
|
|
410
|
+
mcp_path = Path(sys.argv[1])
|
|
411
|
+
source_mcp_path = Path(sys.argv[2])
|
|
412
|
+
|
|
413
|
+
mcp_config = {}
|
|
414
|
+
if mcp_path.exists():
|
|
415
|
+
try:
|
|
416
|
+
mcp_config = json.loads(mcp_path.read_text(encoding="utf-8"))
|
|
417
|
+
except Exception:
|
|
418
|
+
mcp_config = {}
|
|
419
|
+
if not isinstance(mcp_config, dict):
|
|
420
|
+
mcp_config = {}
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
source_mcp = json.loads(source_mcp_path.read_text(encoding="utf-8"))
|
|
424
|
+
except Exception:
|
|
425
|
+
source_mcp = {}
|
|
426
|
+
|
|
427
|
+
incoming = source_mcp.get("mcpServers") if isinstance(source_mcp, dict) else {}
|
|
428
|
+
if not isinstance(incoming, dict):
|
|
429
|
+
incoming = {}
|
|
430
|
+
|
|
431
|
+
servers = mcp_config.get("mcpServers")
|
|
432
|
+
if not isinstance(servers, dict):
|
|
433
|
+
servers = {}
|
|
434
|
+
for key, value in incoming.items():
|
|
435
|
+
if key not in servers:
|
|
436
|
+
servers[key] = value
|
|
437
|
+
mcp_config["mcpServers"] = servers
|
|
438
|
+
|
|
439
|
+
mcp_path.parent.mkdir(parents=True, exist_ok=True)
|
|
440
|
+
mcp_path.write_text(json.dumps(mcp_config, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
441
|
+
print(str(len(incoming)))
|
|
442
|
+
PY
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
write_plugin_mcp_file() {
|
|
446
|
+
local target_path="$1"
|
|
447
|
+
local source_mcp_path="$SCRIPT_DIR/.mcp.json"
|
|
448
|
+
python3 - "$target_path" "$source_mcp_path" <<'PY'
|
|
449
|
+
import json
|
|
450
|
+
import sys
|
|
451
|
+
from pathlib import Path
|
|
452
|
+
|
|
453
|
+
target_path = Path(sys.argv[1])
|
|
454
|
+
source_mcp_path = Path(sys.argv[2])
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
source_mcp = json.loads(source_mcp_path.read_text(encoding="utf-8"))
|
|
458
|
+
except Exception:
|
|
459
|
+
source_mcp = {}
|
|
460
|
+
|
|
461
|
+
mcp_servers = source_mcp.get("mcpServers") if isinstance(source_mcp, dict) else {}
|
|
462
|
+
if not isinstance(mcp_servers, dict):
|
|
463
|
+
mcp_servers = {}
|
|
464
|
+
|
|
465
|
+
payload = {"mcpServers": mcp_servers}
|
|
466
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
467
|
+
target_path.write_text(json.dumps(payload, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
468
|
+
print(str(len(mcp_servers)))
|
|
469
|
+
PY
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
register_plugin_in_registry() {
|
|
473
|
+
local plugin_ref="$1"
|
|
474
|
+
local install_path="$2"
|
|
475
|
+
local version="$3"
|
|
476
|
+
local settings_path="$CLAUDE_DIR/settings.json"
|
|
477
|
+
local installed_plugins_path="$CLAUDE_DIR/plugins/installed_plugins.json"
|
|
478
|
+
|
|
479
|
+
python3 - "$settings_path" "$installed_plugins_path" "$plugin_ref" "$install_path" "$version" <<'PY'
|
|
480
|
+
import json
|
|
481
|
+
import sys
|
|
482
|
+
from pathlib import Path
|
|
483
|
+
from datetime import datetime, timezone
|
|
484
|
+
|
|
485
|
+
settings_path = Path(sys.argv[1])
|
|
486
|
+
installed_plugins_path = Path(sys.argv[2])
|
|
487
|
+
plugin_ref = sys.argv[3]
|
|
488
|
+
install_path = sys.argv[4]
|
|
489
|
+
version = sys.argv[5]
|
|
490
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
491
|
+
|
|
492
|
+
settings = {}
|
|
493
|
+
if settings_path.exists():
|
|
494
|
+
try:
|
|
495
|
+
settings = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
496
|
+
except Exception:
|
|
497
|
+
settings = {}
|
|
498
|
+
if not isinstance(settings, dict):
|
|
499
|
+
settings = {}
|
|
500
|
+
enabled = settings.get("enabledPlugins")
|
|
501
|
+
if not isinstance(enabled, dict):
|
|
502
|
+
enabled = {}
|
|
503
|
+
enabled[plugin_ref] = True
|
|
504
|
+
settings["enabledPlugins"] = enabled
|
|
505
|
+
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
506
|
+
settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
507
|
+
|
|
508
|
+
installed = {}
|
|
509
|
+
if installed_plugins_path.exists():
|
|
510
|
+
try:
|
|
511
|
+
installed = json.loads(installed_plugins_path.read_text(encoding="utf-8"))
|
|
512
|
+
except Exception:
|
|
513
|
+
installed = {}
|
|
514
|
+
if not isinstance(installed, dict):
|
|
515
|
+
installed = {}
|
|
516
|
+
installed["version"] = 2
|
|
517
|
+
plugins = installed.get("plugins")
|
|
518
|
+
if not isinstance(plugins, dict):
|
|
519
|
+
plugins = {}
|
|
520
|
+
plugins[plugin_ref] = [{
|
|
521
|
+
"scope": "user",
|
|
522
|
+
"installPath": install_path,
|
|
523
|
+
"version": version,
|
|
524
|
+
"installedAt": now,
|
|
525
|
+
"lastUpdated": now,
|
|
526
|
+
}]
|
|
527
|
+
installed["plugins"] = plugins
|
|
528
|
+
installed_plugins_path.parent.mkdir(parents=True, exist_ok=True)
|
|
529
|
+
installed_plugins_path.write_text(json.dumps(installed, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
530
|
+
PY
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
unregister_plugin_from_registry() {
|
|
534
|
+
local plugin_ref="$1"
|
|
535
|
+
local settings_path="$CLAUDE_DIR/settings.json"
|
|
536
|
+
local installed_plugins_path="$CLAUDE_DIR/plugins/installed_plugins.json"
|
|
537
|
+
|
|
538
|
+
python3 - "$settings_path" "$installed_plugins_path" "$plugin_ref" <<'PY'
|
|
539
|
+
import json
|
|
540
|
+
import sys
|
|
541
|
+
from pathlib import Path
|
|
542
|
+
|
|
543
|
+
settings_path = Path(sys.argv[1])
|
|
544
|
+
installed_plugins_path = Path(sys.argv[2])
|
|
545
|
+
plugin_ref = sys.argv[3]
|
|
546
|
+
|
|
547
|
+
if settings_path.exists():
|
|
548
|
+
try:
|
|
549
|
+
settings = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
550
|
+
except Exception:
|
|
551
|
+
settings = {}
|
|
552
|
+
if isinstance(settings, dict):
|
|
553
|
+
enabled = settings.get("enabledPlugins")
|
|
554
|
+
if isinstance(enabled, dict) and plugin_ref in enabled:
|
|
555
|
+
enabled.pop(plugin_ref, None)
|
|
556
|
+
settings["enabledPlugins"] = enabled
|
|
557
|
+
settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
558
|
+
|
|
559
|
+
if installed_plugins_path.exists():
|
|
560
|
+
try:
|
|
561
|
+
installed = json.loads(installed_plugins_path.read_text(encoding="utf-8"))
|
|
562
|
+
except Exception:
|
|
563
|
+
installed = {}
|
|
564
|
+
if isinstance(installed, dict):
|
|
565
|
+
plugins = installed.get("plugins")
|
|
566
|
+
if isinstance(plugins, dict) and plugin_ref in plugins:
|
|
567
|
+
plugins.pop(plugin_ref, None)
|
|
568
|
+
installed["plugins"] = plugins
|
|
569
|
+
installed_plugins_path.write_text(json.dumps(installed, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
570
|
+
PY
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
remove_omg_files() {
|
|
574
|
+
if ! $DRY_RUN; then
|
|
575
|
+
local plugin_bundle_marker="$PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
|
|
576
|
+
local plugin_bundle_marker_legacy="$LEGACY_PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
|
|
577
|
+
local remove_plugin_managed_mcp=false
|
|
578
|
+
if [ -f "$plugin_bundle_marker" ] || [ -f "$plugin_bundle_marker_legacy" ]; then
|
|
579
|
+
remove_plugin_managed_mcp=true
|
|
580
|
+
fi
|
|
581
|
+
|
|
582
|
+
# Use manifest for precise removal if available.
|
|
583
|
+
if [ -f "$OMG_MANIFEST" ]; then
|
|
584
|
+
while IFS= read -r entry; do
|
|
585
|
+
[ -n "$entry" ] || continue
|
|
586
|
+
[[ "$entry" == "#"* ]] && continue
|
|
587
|
+
rm -f "$CLAUDE_DIR/$entry"
|
|
588
|
+
done < "$OMG_MANIFEST"
|
|
589
|
+
rm -f "$OMG_MANIFEST"
|
|
590
|
+
fi
|
|
591
|
+
|
|
592
|
+
# Also remove by pattern (covers pre-manifest installs + compat aliases).
|
|
593
|
+
build_omg_hooks_list
|
|
594
|
+
for h in "${OMG_HOOKS[@]}"; do
|
|
595
|
+
rm -f "$CLAUDE_DIR/hooks/$h"
|
|
596
|
+
done
|
|
597
|
+
rm -f "$CLAUDE_DIR/hooks/.omg-version" "$CLAUDE_DIR/hooks/.omg-coexist"
|
|
598
|
+
|
|
599
|
+
# Remove OMG rules and old v3 rule set.
|
|
600
|
+
for r in "$CLAUDE_DIR"/rules/0[0-4]-*.md; do
|
|
601
|
+
[ -f "$r" ] && rm "$r"
|
|
602
|
+
done
|
|
603
|
+
for rule in "${V3_RULES[@]}"; do
|
|
604
|
+
rm -f "$CLAUDE_DIR/rules/$rule"
|
|
605
|
+
done
|
|
606
|
+
|
|
607
|
+
# Remove OMG agents, commands, templates.
|
|
608
|
+
rm -f "$CLAUDE_DIR"/agents/omg-*.md
|
|
609
|
+
if [ -d "$CLAUDE_DIR/commands" ]; then
|
|
610
|
+
while IFS= read -r cmd_path; do
|
|
611
|
+
if [ -n "$cmd_path" ] && is_omg_managed_command_file "$cmd_path"; then
|
|
612
|
+
rm -f "$cmd_path"
|
|
613
|
+
fi
|
|
614
|
+
done < <(find "$CLAUDE_DIR/commands" -maxdepth 1 -type f -name "*.md" 2>/dev/null | sort)
|
|
615
|
+
fi
|
|
616
|
+
[[ "$CLAUDE_DIR/templates/omg" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/templates/omg" >&2; exit 1; }
|
|
617
|
+
rm -rf "$CLAUDE_DIR/templates/omg"
|
|
618
|
+
[[ "$CLAUDE_DIR/omg-runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/omg-runtime" >&2; exit 1; }
|
|
619
|
+
rm -rf "$CLAUDE_DIR/omg-runtime"
|
|
620
|
+
|
|
621
|
+
[[ "$PLUGIN_CACHE_DIR" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $PLUGIN_CACHE_DIR" >&2; exit 1; }
|
|
622
|
+
rm -rf "$PLUGIN_CACHE_DIR"
|
|
623
|
+
[[ "$LEGACY_PLUGIN_CACHE_DIR" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $LEGACY_PLUGIN_CACHE_DIR" >&2; exit 1; }
|
|
624
|
+
rm -rf "$LEGACY_PLUGIN_CACHE_DIR"
|
|
625
|
+
rm -f "$CLAUDE_DIR/hud/omg-hud.mjs"
|
|
626
|
+
unregister_plugin_from_registry "$PLUGIN_REF"
|
|
627
|
+
unregister_plugin_from_registry "$LEGACY_PLUGIN_REF"
|
|
628
|
+
|
|
629
|
+
if $remove_plugin_managed_mcp; then
|
|
630
|
+
local pruned_mcp=0
|
|
631
|
+
pruned_mcp=$(prune_plugin_mcp_from_settings)
|
|
632
|
+
if [ "${pruned_mcp:-0}" -gt 0 ]; then
|
|
633
|
+
echo " ✓ Plugin-managed MCP servers removed from settings.json ($pruned_mcp)"
|
|
634
|
+
fi
|
|
635
|
+
fi
|
|
636
|
+
fi
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
install_plugin_bundle() {
|
|
640
|
+
local plugin_ref="$PLUGIN_REF"
|
|
641
|
+
local plugin_root="$PLUGIN_CACHE_DIR/$VERSION"
|
|
642
|
+
local plugin_manifest_src="$SCRIPT_DIR/.claude-plugin/plugin.json"
|
|
643
|
+
local plugin_manifest_target="$plugin_root/.claude-plugin/plugin.json"
|
|
644
|
+
local plugin_mcp_target="$plugin_root/.mcp.json"
|
|
645
|
+
local hud_src="$SCRIPT_DIR/hud/omg-hud.mjs"
|
|
646
|
+
local hud_target="$CLAUDE_DIR/hud/omg-hud.mjs"
|
|
647
|
+
|
|
648
|
+
echo " Plugin bundle mode enabled: install plugin + MCP + HUD together"
|
|
649
|
+
if $DRY_RUN; then
|
|
650
|
+
echo " (would install plugin bundle under $plugin_root and deploy HUD to $hud_target)"
|
|
651
|
+
echo " (would register plugin in ~/.claude/plugins/installed_plugins.json and enable it in settings.json)"
|
|
652
|
+
echo " (would merge plugin MCP servers into user-level settings.json)"
|
|
653
|
+
return 0
|
|
654
|
+
fi
|
|
655
|
+
|
|
656
|
+
mkdir -p "$plugin_root/.claude-plugin"
|
|
657
|
+
mkdir -p "$CLAUDE_DIR/hud"
|
|
658
|
+
cp "$plugin_manifest_src" "$plugin_manifest_target"
|
|
659
|
+
write_plugin_mcp_file "$plugin_mcp_target" >/dev/null
|
|
660
|
+
cp "$hud_src" "$hud_target"
|
|
661
|
+
mkdir -p "$PLUGIN_CACHE_DIR"
|
|
662
|
+
printf '%s\n' "omg-plugin-bundle-v1" > "$PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
|
|
663
|
+
|
|
664
|
+
unregister_plugin_from_registry "$LEGACY_PLUGIN_REF"
|
|
665
|
+
register_plugin_in_registry "$plugin_ref" "$plugin_root" "$VERSION"
|
|
666
|
+
merge_plugin_mcp_into_settings >/dev/null
|
|
667
|
+
|
|
668
|
+
track_file "plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME/$VERSION/.claude-plugin/plugin.json"
|
|
669
|
+
track_file "plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME/$VERSION/.mcp.json"
|
|
670
|
+
track_file "plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME/$PLUGIN_BUNDLE_MARKER_FILE"
|
|
671
|
+
track_file "hud/omg-hud.mjs"
|
|
672
|
+
echo " ✓ Plugin bundle installed and registered in Claude plugin settings"
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
run_uninstall() {
|
|
676
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
677
|
+
echo " OMG Setup Manager — uninstall"
|
|
678
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
679
|
+
echo ""
|
|
680
|
+
|
|
681
|
+
if $DRY_RUN; then
|
|
682
|
+
echo " *** DRY RUN — no files will be changed ***"
|
|
683
|
+
echo ""
|
|
684
|
+
fi
|
|
685
|
+
|
|
686
|
+
preflight
|
|
687
|
+
|
|
688
|
+
local existing_ver=""
|
|
689
|
+
if [ -f "$CLAUDE_DIR/hooks/.omg-version" ]; then
|
|
690
|
+
existing_ver=$(cat "$CLAUDE_DIR/hooks/.omg-version" 2>/dev/null || echo "")
|
|
691
|
+
fi
|
|
692
|
+
if [ -n "$existing_ver" ]; then
|
|
693
|
+
echo " ✓ Existing OMG install: $existing_ver"
|
|
694
|
+
else
|
|
695
|
+
echo " ~ No .omg-version marker found; uninstall will still remove known OMG files."
|
|
696
|
+
fi
|
|
697
|
+
|
|
698
|
+
if ! $NON_INTERACTIVE && ! $DRY_RUN; then
|
|
699
|
+
read -p "Proceed with uninstall? [y/N] " -n 1 -r
|
|
700
|
+
echo ""
|
|
701
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
702
|
+
echo "Cancelled."
|
|
703
|
+
exit 0
|
|
704
|
+
fi
|
|
705
|
+
fi
|
|
706
|
+
|
|
707
|
+
echo ""
|
|
708
|
+
echo "Uninstall: removing OMG-managed files from $CLAUDE_DIR"
|
|
709
|
+
ensure_backup
|
|
710
|
+
if ! $DRY_RUN; then
|
|
711
|
+
echo " ✓ Backup: $BACKUP_DIR"
|
|
712
|
+
else
|
|
713
|
+
echo " (would backup to $BACKUP_DIR)"
|
|
714
|
+
fi
|
|
715
|
+
remove_omg_files
|
|
716
|
+
if $DRY_RUN; then
|
|
717
|
+
echo " (would remove OMG hooks/rules/agents/commands/templates)"
|
|
718
|
+
else
|
|
719
|
+
echo " ✓ Removed OMG hooks/rules/agents/commands/templates"
|
|
720
|
+
fi
|
|
721
|
+
|
|
722
|
+
echo ""
|
|
723
|
+
echo "Uninstall complete."
|
|
724
|
+
echo " ✓ If plugin bundle was installed, plugin + MCP + HUD were removed together"
|
|
725
|
+
echo "Preserved:"
|
|
726
|
+
echo " - $CLAUDE_DIR/settings.json"
|
|
727
|
+
echo " - project .omg/ data"
|
|
728
|
+
echo " - non-OMG custom files"
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
run_install_like() {
|
|
732
|
+
local existing_ver=""
|
|
733
|
+
local removed=0
|
|
734
|
+
local installed_rules=0
|
|
735
|
+
local installed_hooks=0
|
|
736
|
+
local hook_errors=0
|
|
737
|
+
local installed_agents=0
|
|
738
|
+
local installed_cmds=0
|
|
739
|
+
|
|
740
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
741
|
+
echo " OMG Setup Manager — $ACTION"
|
|
742
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
743
|
+
echo ""
|
|
744
|
+
|
|
745
|
+
if $DRY_RUN; then
|
|
746
|
+
echo " *** DRY RUN — no files will be changed ***"
|
|
747
|
+
echo ""
|
|
748
|
+
fi
|
|
749
|
+
|
|
750
|
+
preflight
|
|
751
|
+
|
|
752
|
+
if [ -f "$CLAUDE_DIR/hooks/.omg-version" ]; then
|
|
753
|
+
existing_ver=$(cat "$CLAUDE_DIR/hooks/.omg-version" 2>/dev/null || echo "")
|
|
754
|
+
fi
|
|
755
|
+
|
|
756
|
+
if [ "$ACTION" = "update" ] && [ -z "$existing_ver" ]; then
|
|
757
|
+
echo " ~ No existing OMG install detected. update will proceed as install."
|
|
758
|
+
fi
|
|
759
|
+
|
|
760
|
+
if [ -n "$existing_ver" ]; then
|
|
761
|
+
echo " ✓ Existing: $existing_ver → target $VERSION"
|
|
762
|
+
else
|
|
763
|
+
echo " ✓ Fresh install"
|
|
764
|
+
fi
|
|
765
|
+
echo " ✓ Standalone mode: OMG-only command surface (no OMC aliases)"
|
|
766
|
+
|
|
767
|
+
if $FRESH_INSTALL; then
|
|
768
|
+
echo ""
|
|
769
|
+
echo "Fresh/reinstall mode: remove OMG files before install."
|
|
770
|
+
if ! $NON_INTERACTIVE && ! $DRY_RUN; then
|
|
771
|
+
read -p "Proceed with fresh cleanup? [y/N] " -n 1 -r
|
|
772
|
+
echo ""
|
|
773
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
774
|
+
echo "Cancelled."
|
|
775
|
+
exit 0
|
|
776
|
+
fi
|
|
777
|
+
fi
|
|
778
|
+
|
|
779
|
+
ensure_backup
|
|
780
|
+
if ! $DRY_RUN; then
|
|
781
|
+
echo " ✓ Backup: $BACKUP_DIR"
|
|
782
|
+
else
|
|
783
|
+
echo " (would backup to $BACKUP_DIR)"
|
|
784
|
+
fi
|
|
785
|
+
remove_omg_files
|
|
786
|
+
if $DRY_RUN; then
|
|
787
|
+
echo " (would remove OMG hooks/rules/agents/commands/templates)"
|
|
788
|
+
else
|
|
789
|
+
echo " ✓ Clean slate ready"
|
|
790
|
+
fi
|
|
791
|
+
existing_ver=""
|
|
792
|
+
fi
|
|
793
|
+
|
|
794
|
+
if [ -n "$existing_ver" ] && ! $FRESH_INSTALL; then
|
|
795
|
+
echo ""
|
|
796
|
+
echo "Step 0/7: Backup existing installation..."
|
|
797
|
+
ensure_backup
|
|
798
|
+
if ! $DRY_RUN; then
|
|
799
|
+
echo " ✓ Backup: $BACKUP_DIR"
|
|
800
|
+
else
|
|
801
|
+
echo " (would backup to $BACKUP_DIR)"
|
|
802
|
+
fi
|
|
803
|
+
fi
|
|
804
|
+
|
|
805
|
+
echo ""
|
|
806
|
+
echo "Step 1/7: Remove deprecated files..."
|
|
807
|
+
|
|
808
|
+
for rule in "${V3_RULES[@]}"; do
|
|
809
|
+
target="$CLAUDE_DIR/rules/$rule"
|
|
810
|
+
if [ -f "$target" ]; then
|
|
811
|
+
! $DRY_RUN && rm "$target"
|
|
812
|
+
echo " - rules/$rule"
|
|
813
|
+
removed=$((removed + 1))
|
|
814
|
+
fi
|
|
815
|
+
done
|
|
816
|
+
|
|
817
|
+
for agent in "${V3_AGENTS_REMOVE[@]}"; do
|
|
818
|
+
target="$CLAUDE_DIR/agents/$agent"
|
|
819
|
+
if [ -f "$target" ]; then
|
|
820
|
+
! $DRY_RUN && rm "$target"
|
|
821
|
+
echo " - agents/$agent (v3 deprecated)"
|
|
822
|
+
removed=$((removed + 1))
|
|
823
|
+
fi
|
|
824
|
+
done
|
|
825
|
+
|
|
826
|
+
for agent in "${OLD_OMG_AGENTS[@]}"; do
|
|
827
|
+
target="$CLAUDE_DIR/agents/$agent"
|
|
828
|
+
if [ -f "$target" ]; then
|
|
829
|
+
if grep -q "OMG\|omg\|circuit.breaker\|escalat" "$target" 2>/dev/null; then
|
|
830
|
+
! $DRY_RUN && rm "$target"
|
|
831
|
+
echo " - agents/$agent (renamed to omg-$agent)"
|
|
832
|
+
removed=$((removed + 1))
|
|
833
|
+
else
|
|
834
|
+
echo " ~ agents/$agent (kept — appears to be non-OMG/custom)"
|
|
835
|
+
fi
|
|
836
|
+
fi
|
|
837
|
+
done
|
|
838
|
+
|
|
839
|
+
for cmd in "${V3_COMMANDS_REMOVE[@]}" "${V4_COMMANDS_REMOVE[@]}"; do
|
|
840
|
+
target="$CLAUDE_DIR/commands/$cmd"
|
|
841
|
+
if [ -f "$target" ]; then
|
|
842
|
+
if is_omg_managed_command_file "$target"; then
|
|
843
|
+
! $DRY_RUN && rm "$target"
|
|
844
|
+
echo " - commands/$cmd (v4 → OMG:$cmd)"
|
|
845
|
+
removed=$((removed + 1))
|
|
846
|
+
else
|
|
847
|
+
echo " ~ commands/$cmd (kept — appears to be non-OMG/custom)"
|
|
848
|
+
fi
|
|
849
|
+
fi
|
|
850
|
+
done
|
|
851
|
+
|
|
852
|
+
if compgen -G "$CLAUDE_DIR/commands/*omc*.md" > /dev/null; then
|
|
853
|
+
for cmd in "$CLAUDE_DIR"/commands/*omc*.md; do
|
|
854
|
+
[ -f "$cmd" ] || continue
|
|
855
|
+
if is_omg_managed_command_file "$cmd"; then
|
|
856
|
+
! $DRY_RUN && rm "$cmd"
|
|
857
|
+
echo " - commands/$(basename "$cmd") (removed OMC command alias)"
|
|
858
|
+
removed=$((removed + 1))
|
|
859
|
+
fi
|
|
860
|
+
done
|
|
861
|
+
fi
|
|
862
|
+
|
|
863
|
+
if [ -d "$CLAUDE_DIR/hooks/__pycache__" ]; then
|
|
864
|
+
! $DRY_RUN && rm -rf "$CLAUDE_DIR/hooks/__pycache__"
|
|
865
|
+
removed=$((removed + 1))
|
|
866
|
+
fi
|
|
867
|
+
! $DRY_RUN && find "$CLAUDE_DIR/hooks/" -name "*.pyc" -delete 2>/dev/null || true
|
|
868
|
+
[ $removed -eq 0 ] && echo " (nothing to remove)" || echo " ✓ Removed $removed deprecated files"
|
|
869
|
+
|
|
870
|
+
echo ""
|
|
871
|
+
echo "Step 2/7: Core Rules → $CLAUDE_DIR/rules/"
|
|
872
|
+
! $DRY_RUN && mkdir -p "$CLAUDE_DIR/rules"
|
|
873
|
+
for f in "$SCRIPT_DIR"/rules/core/*.md; do
|
|
874
|
+
name=$(basename "$f")
|
|
875
|
+
target="$CLAUDE_DIR/rules/$name"
|
|
876
|
+
! $DRY_RUN && cp "$f" "$target"
|
|
877
|
+
installed_rules=$((installed_rules + 1))
|
|
878
|
+
track_file "rules/$name"
|
|
879
|
+
done
|
|
880
|
+
echo " ✓ $installed_rules core rules"
|
|
881
|
+
|
|
882
|
+
echo ""
|
|
883
|
+
echo "Step 3/7: Hooks → $CLAUDE_DIR/hooks/"
|
|
884
|
+
! $DRY_RUN && mkdir -p "$CLAUDE_DIR/hooks"
|
|
885
|
+
for f in "$SCRIPT_DIR"/hooks/*.py; do
|
|
886
|
+
name=$(basename "$f")
|
|
887
|
+
target="$CLAUDE_DIR/hooks/$name"
|
|
888
|
+
if ! $DRY_RUN; then
|
|
889
|
+
install_file "$f" "$target"
|
|
890
|
+
if ! $USE_SYMLINK; then
|
|
891
|
+
chmod +x "$target"
|
|
892
|
+
fi
|
|
893
|
+
fi
|
|
894
|
+
if python3 -c "import py_compile; py_compile.compile('$f', doraise=True)" 2>/dev/null; then
|
|
895
|
+
echo " ✓ $name"
|
|
896
|
+
else
|
|
897
|
+
echo " ❌ $name (SYNTAX ERROR)"
|
|
898
|
+
hook_errors=$((hook_errors + 1))
|
|
899
|
+
ERRORS=$((ERRORS + 1))
|
|
900
|
+
fi
|
|
901
|
+
installed_hooks=$((installed_hooks + 1))
|
|
902
|
+
track_file "hooks/$name"
|
|
903
|
+
done
|
|
904
|
+
! $DRY_RUN && echo "$VERSION" > "$CLAUDE_DIR/hooks/.omg-version"
|
|
905
|
+
echo " ✓ $installed_hooks hooks ($hook_errors errors)"
|
|
906
|
+
|
|
907
|
+
echo ""
|
|
908
|
+
echo "Step 4/7: Agents → $CLAUDE_DIR/agents/"
|
|
909
|
+
! $DRY_RUN && mkdir -p "$CLAUDE_DIR/agents"
|
|
910
|
+
for f in "$SCRIPT_DIR"/agents/*.md; do
|
|
911
|
+
name=$(basename "$f")
|
|
912
|
+
target="$CLAUDE_DIR/agents/$name"
|
|
913
|
+
if ! $DRY_RUN; then
|
|
914
|
+
[ -f "$target" ] && [ -z "$existing_ver" ] && cp "$target" "$target.bak.$BACKUP_TS"
|
|
915
|
+
install_file "$f" "$target"
|
|
916
|
+
fi
|
|
917
|
+
echo " ✓ $name"
|
|
918
|
+
installed_agents=$((installed_agents + 1))
|
|
919
|
+
track_file "agents/$name"
|
|
920
|
+
done
|
|
921
|
+
echo " ✓ $installed_agents agents"
|
|
922
|
+
|
|
923
|
+
echo ""
|
|
924
|
+
echo "Step 5/7: Commands → $CLAUDE_DIR/commands/"
|
|
925
|
+
! $DRY_RUN && mkdir -p "$CLAUDE_DIR/commands"
|
|
926
|
+
for f in "$SCRIPT_DIR"/commands/*.md; do
|
|
927
|
+
name=$(basename "$f")
|
|
928
|
+
if [[ "$name" == *omc* ]]; then
|
|
929
|
+
echo " - /$(basename "$name" .md) (skipped: OMC alias commands are unsupported)"
|
|
930
|
+
continue
|
|
931
|
+
fi
|
|
932
|
+
target="$CLAUDE_DIR/commands/$name"
|
|
933
|
+
if [ -f "$target" ] && ! is_omg_managed_command_file "$target"; then
|
|
934
|
+
echo " ~ /$(basename "$name" .md) (kept existing custom command)"
|
|
935
|
+
continue
|
|
936
|
+
fi
|
|
937
|
+
if ! $DRY_RUN; then
|
|
938
|
+
install_file "$f" "$target"
|
|
939
|
+
if ! $USE_SYMLINK; then
|
|
940
|
+
mark_omg_managed_command_file "$target"
|
|
941
|
+
fi
|
|
942
|
+
fi
|
|
943
|
+
echo " ✓ /$(basename "$name" .md)"
|
|
944
|
+
installed_cmds=$((installed_cmds + 1))
|
|
945
|
+
track_file "commands/$name"
|
|
946
|
+
done
|
|
947
|
+
echo ""
|
|
948
|
+
echo "Step 6/7: Settings + Templates..."
|
|
949
|
+
MERGE="$SCRIPT_DIR/scripts/settings-merge.py"
|
|
950
|
+
TARGET="$CLAUDE_DIR/settings.json"
|
|
951
|
+
SOURCE="$SCRIPT_DIR/settings.json"
|
|
952
|
+
if ! $DRY_RUN; then
|
|
953
|
+
if [ ! -f "$TARGET" ]; then
|
|
954
|
+
cp "$SOURCE" "$TARGET"
|
|
955
|
+
echo " ✓ Created settings.json"
|
|
956
|
+
else
|
|
957
|
+
if [ "$MERGE_POLICY" = "skip" ]; then
|
|
958
|
+
echo " ⊘ Skipped settings merge (--merge-policy=skip)"
|
|
959
|
+
elif [ "$MERGE_POLICY" = "apply" ] || $NON_INTERACTIVE; then
|
|
960
|
+
python3 "$MERGE" "$TARGET" "$SOURCE"
|
|
961
|
+
echo " ✓ Settings merged (auto)"
|
|
962
|
+
else
|
|
963
|
+
echo " Merging settings.json..."
|
|
964
|
+
python3 "$MERGE" "$TARGET" "$SOURCE" --dry-run 2>&1 | head -5 | sed 's/^/ /'
|
|
965
|
+
echo ""
|
|
966
|
+
read -p " Apply merge? [Y/n] " -n 1 -r
|
|
967
|
+
echo ""
|
|
968
|
+
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
|
969
|
+
python3 "$MERGE" "$TARGET" "$SOURCE"
|
|
970
|
+
echo " ✓ Settings merged"
|
|
971
|
+
else
|
|
972
|
+
echo " ⊘ Skipped (manual merge needed)"
|
|
973
|
+
fi
|
|
974
|
+
fi
|
|
975
|
+
fi
|
|
976
|
+
|
|
977
|
+
if $USE_SYMLINK; then
|
|
978
|
+
# In symlink mode, link entire directories for templates
|
|
979
|
+
if [ -e "$CLAUDE_DIR/templates/omg" ] || [ -L "$CLAUDE_DIR/templates/omg" ]; then
|
|
980
|
+
rm -rf "$CLAUDE_DIR/templates/omg"
|
|
981
|
+
fi
|
|
982
|
+
ln -s "$SCRIPT_DIR/templates" "$CLAUDE_DIR/templates/omg"
|
|
983
|
+
# Also link contextual rules
|
|
984
|
+
mkdir -p "$CLAUDE_DIR/templates/omg/contextual-rules"
|
|
985
|
+
for cr in "$SCRIPT_DIR"/rules/contextual/*.md; do
|
|
986
|
+
[ -f "$cr" ] && track_file "templates/omg/contextual-rules/$(basename "$cr")"
|
|
987
|
+
done
|
|
988
|
+
else
|
|
989
|
+
mkdir -p "$CLAUDE_DIR/templates/omg"
|
|
990
|
+
cp "$SCRIPT_DIR"/templates/* "$CLAUDE_DIR/templates/omg/" 2>/dev/null || true
|
|
991
|
+
for t in "$SCRIPT_DIR"/templates/*; do
|
|
992
|
+
[ -f "$t" ] && track_file "templates/omg/$(basename "$t")"
|
|
993
|
+
done
|
|
994
|
+
mkdir -p "$CLAUDE_DIR/templates/omg/contextual-rules"
|
|
995
|
+
cp "$SCRIPT_DIR"/rules/contextual/*.md "$CLAUDE_DIR/templates/omg/contextual-rules/" 2>/dev/null || true
|
|
996
|
+
for cr in "$SCRIPT_DIR"/rules/contextual/*.md; do
|
|
997
|
+
[ -f "$cr" ] && track_file "templates/omg/contextual-rules/$(basename "$cr")"
|
|
998
|
+
done
|
|
999
|
+
fi
|
|
1000
|
+
mkdir -p "$CLAUDE_DIR/templates/omg/state/memory"
|
|
1001
|
+
mkdir -p "$CLAUDE_DIR/templates/omg/state/learnings"
|
|
1002
|
+
mkdir -p "$CLAUDE_DIR/templates/omg/state/ledger"
|
|
1003
|
+
echo " \u2713 State directory templates (memory, learnings, ledger)"
|
|
1004
|
+
echo " \u2713 Templates + contextual rules"
|
|
1005
|
+
|
|
1006
|
+
if $USE_SYMLINK; then
|
|
1007
|
+
# In symlink mode, link runtime directories instead of copying
|
|
1008
|
+
mkdir -p "$CLAUDE_DIR/omg-runtime/scripts"
|
|
1009
|
+
install_file "$SCRIPT_DIR/scripts/omg.py" "$CLAUDE_DIR/omg-runtime/scripts/omg.py"
|
|
1010
|
+
|
|
1011
|
+
[[ "$CLAUDE_DIR/omg-runtime/runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/runtime" >&2; exit 1; }
|
|
1012
|
+
[[ "$CLAUDE_DIR/omg-runtime/hooks" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/hooks" >&2; exit 1; }
|
|
1013
|
+
[[ "$CLAUDE_DIR/omg-runtime/lab" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/lab" >&2; exit 1; }
|
|
1014
|
+
|
|
1015
|
+
rm -rf "$CLAUDE_DIR/omg-runtime/runtime" "$CLAUDE_DIR/omg-runtime/hooks" "$CLAUDE_DIR/omg-runtime/lab"
|
|
1016
|
+
ln -s "$SCRIPT_DIR/runtime" "$CLAUDE_DIR/omg-runtime/runtime"
|
|
1017
|
+
ln -s "$SCRIPT_DIR/hooks" "$CLAUDE_DIR/omg-runtime/hooks"
|
|
1018
|
+
ln -s "$SCRIPT_DIR/lab" "$CLAUDE_DIR/omg-runtime/lab"
|
|
1019
|
+
|
|
1020
|
+
[[ "$CLAUDE_DIR/omg-runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/omg-runtime" >&2; exit 1; }
|
|
1021
|
+
find "$CLAUDE_DIR/omg-runtime" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
|
1022
|
+
find "$CLAUDE_DIR/omg-runtime" -name "*.pyc" -delete 2>/dev/null || true
|
|
1023
|
+
echo " ✓ Portable runtime → $CLAUDE_DIR/omg-runtime (symlinked to $SCRIPT_DIR)"
|
|
1024
|
+
else
|
|
1025
|
+
mkdir -p "$CLAUDE_DIR/omg-runtime/scripts"
|
|
1026
|
+
cp "$SCRIPT_DIR/scripts/omg.py" "$CLAUDE_DIR/omg-runtime/scripts/omg.py"
|
|
1027
|
+
[[ "$CLAUDE_DIR/omg-runtime/runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/omg-runtime/runtime" >&2; exit 1; }
|
|
1028
|
+
rm -rf "$CLAUDE_DIR/omg-runtime/runtime" "$CLAUDE_DIR/omg-runtime/hooks" "$CLAUDE_DIR/omg-runtime/lab"
|
|
1029
|
+
cp -R "$SCRIPT_DIR/runtime" "$CLAUDE_DIR/omg-runtime/"
|
|
1030
|
+
cp -R "$SCRIPT_DIR/hooks" "$CLAUDE_DIR/omg-runtime/"
|
|
1031
|
+
cp -R "$SCRIPT_DIR/lab" "$CLAUDE_DIR/omg-runtime/"
|
|
1032
|
+
[[ "$CLAUDE_DIR/omg-runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/omg-runtime" >&2; exit 1; }
|
|
1033
|
+
find "$CLAUDE_DIR/omg-runtime" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
|
|
1034
|
+
find "$CLAUDE_DIR/omg-runtime" -name "*.pyc" -delete 2>/dev/null || true
|
|
1035
|
+
echo " ✓ Portable runtime → $CLAUDE_DIR/omg-runtime"
|
|
1036
|
+
fi
|
|
1037
|
+
if $INSTALL_AS_PLUGIN; then
|
|
1038
|
+
install_plugin_bundle
|
|
1039
|
+
fi
|
|
1040
|
+
else
|
|
1041
|
+
echo " (would merge settings.json + copy templates + provision portable runtime)"
|
|
1042
|
+
if $INSTALL_AS_PLUGIN; then
|
|
1043
|
+
install_plugin_bundle
|
|
1044
|
+
fi
|
|
1045
|
+
fi
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
echo ""
|
|
1049
|
+
echo "Step 7/7: Reconcile stale files..."
|
|
1050
|
+
reconcile_stale_files
|
|
1051
|
+
write_omg_manifest
|
|
1052
|
+
|
|
1053
|
+
echo ""
|
|
1054
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
1055
|
+
if [ $ERRORS -eq 0 ]; then
|
|
1056
|
+
echo " ✅ OMG v1 ${ACTION} completed successfully"
|
|
1057
|
+
else
|
|
1058
|
+
echo " ⚠ OMG v1 ${ACTION} completed with $ERRORS error(s)"
|
|
1059
|
+
fi
|
|
1060
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
1061
|
+
echo ""
|
|
1062
|
+
echo " Files: $installed_rules rules, $installed_hooks hooks, $installed_agents agents, $installed_cmds commands"
|
|
1063
|
+
echo " Version: $VERSION"
|
|
1064
|
+
if $USE_SYMLINK; then
|
|
1065
|
+
echo " Mode: Symlink (live updates from $SCRIPT_DIR)"
|
|
1066
|
+
echo " Source: $SCRIPT_DIR"
|
|
1067
|
+
elif $FRESH_INSTALL; then
|
|
1068
|
+
echo " Mode: Fresh reinstall"
|
|
1069
|
+
elif [ -n "$existing_ver" ]; then
|
|
1070
|
+
echo " Upgraded: $existing_ver → $VERSION"
|
|
1071
|
+
echo " Backup: $BACKUP_DIR"
|
|
1072
|
+
fi
|
|
1073
|
+
echo ""
|
|
1074
|
+
if $DRY_RUN; then
|
|
1075
|
+
echo " *** DRY RUN — no files were changed ***"
|
|
1076
|
+
echo " Run without --dry-run to apply changes."
|
|
1077
|
+
fi
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
main() {
|
|
1081
|
+
parse_args "$@"
|
|
1082
|
+
prompt_start_action
|
|
1083
|
+
case "$ACTION" in
|
|
1084
|
+
uninstall) run_uninstall ;;
|
|
1085
|
+
install|update|reinstall) run_install_like ;;
|
|
1086
|
+
*)
|
|
1087
|
+
usage
|
|
1088
|
+
exit 1
|
|
1089
|
+
;;
|
|
1090
|
+
esac
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
main "$@"
|