axel-setup 0.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.
Files changed (117) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/CONTRIBUTING.md +58 -0
  3. package/LICENSE +21 -0
  4. package/README.md +518 -0
  5. package/agents/api-design.md +51 -0
  6. package/agents/bughunter.md +136 -0
  7. package/agents/changelog.md +89 -0
  8. package/agents/cleanup.md +126 -0
  9. package/agents/compare-branch.md +35 -0
  10. package/agents/cross-repo.md +97 -0
  11. package/agents/db-check.md +14 -0
  12. package/agents/debug.md +47 -0
  13. package/agents/deploy-check.md +100 -0
  14. package/agents/draft-message.md +19 -0
  15. package/agents/excelsior-coordinator.md +75 -0
  16. package/agents/excelsior-verifier.md +94 -0
  17. package/agents/feature.md +48 -0
  18. package/agents/harness-optimizer.md +40 -0
  19. package/agents/incident.md +48 -0
  20. package/agents/linear-task.md +18 -0
  21. package/agents/onboard.md +24 -0
  22. package/agents/perf.md +44 -0
  23. package/agents/production-validator.md +96 -0
  24. package/agents/review.md +113 -0
  25. package/agents/security-check.md +29 -0
  26. package/agents/sprint-summary.md +15 -0
  27. package/agents/tdd-mainder.md +178 -0
  28. package/agents/test-gen.md +39 -0
  29. package/axel-manifest.json +129 -0
  30. package/bin/axel-setup.js +597 -0
  31. package/bootstrap.sh +1087 -0
  32. package/commands/create-pr.md +13 -0
  33. package/commands/daily.md +182 -0
  34. package/commands/deslop.md +13 -0
  35. package/commands/draft-message.md +23 -0
  36. package/commands/eod-review.md +154 -0
  37. package/commands/execute-prp.md +37 -0
  38. package/commands/generate-prp.md +75 -0
  39. package/commands/multi-repo-feature.md +60 -0
  40. package/commands/roadmap.md +31 -0
  41. package/commands/sprint-status.md +486 -0
  42. package/commands/style.md +68 -0
  43. package/commands/visualize.md +17 -0
  44. package/docs/roadmap/multi-runtime.md +73 -0
  45. package/docs/superpowers/plans/2026-06-12-setup-hardening-roadmap.md +61 -0
  46. package/hooks/desktop-notify.sh +26 -0
  47. package/hooks/enforce-agent-model.jq +14 -0
  48. package/hooks/gsd-context-monitor.js +156 -0
  49. package/hooks/linear-lifecycle-sync.sh +112 -0
  50. package/hooks/memory-dedup.sh +122 -0
  51. package/hooks/memory-extractor.sh +218 -0
  52. package/hooks/post-commit-memory-trigger.sh +16 -0
  53. package/hooks/post-commit-verify.sh +41 -0
  54. package/hooks/post-edit-lint.sh +43 -0
  55. package/hooks/precompact-save-context.sh +124 -0
  56. package/hooks/priority-map-staleness.sh +29 -0
  57. package/hooks/proactive-resolver.sh +104 -0
  58. package/hooks/session-auto-title.sh +165 -0
  59. package/hooks/session-checkpoint.sh +97 -0
  60. package/hooks/session-cost-log.sh +77 -0
  61. package/hooks/session-log-action.sh +36 -0
  62. package/hooks/session-log-prompt.sh +25 -0
  63. package/hooks/session-restore.sh +45 -0
  64. package/hooks/session-save.sh +81 -0
  65. package/hooks/session-summarize.sh +154 -0
  66. package/hooks/validate-commit-format.sh +38 -0
  67. package/hooks/weekly-priority-map-review.sh +143 -0
  68. package/install.sh +46 -0
  69. package/package.json +67 -0
  70. package/scripts/ci/bootstrap-dry-run.sh +40 -0
  71. package/scripts/ci/check.sh +65 -0
  72. package/scripts/posthog-snapshot-loader.sh +112 -0
  73. package/skills/context-budget/SKILL.md +86 -0
  74. package/skills/memory-review/SKILL.md +100 -0
  75. package/skills/model-routing/SKILL.md +70 -0
  76. package/skills/posthog-weekly/SKILL.md +271 -0
  77. package/skills/ui-ux-pro-max/SKILL.md +377 -0
  78. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  79. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  80. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  81. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  82. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  83. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  84. package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  85. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  86. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  87. package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  88. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  89. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  90. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  91. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  92. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  93. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  94. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  95. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  96. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  97. package/skills/ui-ux-pro-max/data/styles.csv +68 -0
  98. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  99. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  100. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  101. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  102. package/skills/ui-ux-pro-max/scripts/core.py +253 -0
  103. package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  104. package/skills/ui-ux-pro-max/scripts/search.py +114 -0
  105. package/templates/AGENTS.runtime.md +17 -0
  106. package/templates/CLAUDE.md +252 -0
  107. package/templates/claude-monitor.plist +35 -0
  108. package/templates/keybindings.json +13 -0
  109. package/templates/merge-settings.jq +53 -0
  110. package/templates/review-upgrades.md +44 -0
  111. package/templates/settings.json +255 -0
  112. package/templates/statusline-command.sh +182 -0
  113. package/tests/fixtures/hooks/events.json +32 -0
  114. package/tools/session-costs-view.sh +128 -0
  115. package/tools/session-dashboard-gen.sh +369 -0
  116. package/tools/session-live.sh +173 -0
  117. package/tools/session-server.js +441 -0
package/bootstrap.sh ADDED
@@ -0,0 +1,1087 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================================
3
+ # AXEL Onboarding Bootstrap — Claude Code Team Configuration
4
+ # Autonomous eXcelsior Engineering Layer
5
+ #
6
+ # ADDITIVE MODE: This script ONLY adds new things. It never overwrites
7
+ # existing files, hooks, memory, settings, or CLAUDE.md. Your personal
8
+ # configuration and memory are preserved completely.
9
+ #
10
+ # Usage: bash bootstrap.sh [--user-name "Tu Nombre"] [--dry-run]
11
+ #
12
+ # What this does:
13
+ # 1. Backs up your existing ~/.claude/ config (safety net)
14
+ # 2. ADDS hooks that don't already exist (never overwrites)
15
+ # 3. ADDS commands/skills/agents that don't already exist
16
+ # 4. Installs plugins you don't have yet (skips already-installed)
17
+ # 5. MERGES new hook wiring + features into your existing settings.json
18
+ # 6. Sets up memory directory structure (preserves all existing memory)
19
+ # 7. Offers a team CLAUDE.md only if you don't have one
20
+ #
21
+ # Note: GSD (get-shit-done) is NOT vendored by AXEL. Install/update it via its
22
+ # own installer (npx get-shit-done-cc); AXEL consumes the live GSD skills.
23
+ #
24
+ # Prerequisites:
25
+ # - Claude Code CLI installed (claude --version)
26
+ # - Node.js >= 18 (for hook scripts)
27
+ # - jq (for JSON processing in hooks and settings merge)
28
+ # - python3 (for some hook scripts)
29
+ # ============================================================================
30
+
31
+ set -euo pipefail
32
+
33
+ # --- Configuration ---
34
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
35
+ CLAUDE_DIR="$HOME/.claude"
36
+ CODEX_DIR="${CODEX_HOME:-$HOME/.codex}"
37
+ BACKUP_DIR="$CLAUDE_DIR/backups/pre-axel-$(date +%Y%m%d_%H%M%S)"
38
+ DRY_RUN=false
39
+ TARGET="claude"
40
+ OUTPUT_DIR=""
41
+ USER_NAME=""
42
+ USER_CONTEXT=""
43
+ ASSISTANT_LANGUAGE=""
44
+ ENABLE_POSTHOG=false
45
+ POSTHOG_PROJECT_CONTEXT=""
46
+ PROFILE="core"
47
+ SKIP_PLUGINS=false
48
+ SKIP_MONITOR=false
49
+ SKIP_KEYBINDINGS=false
50
+ SKIP_CLAUDE_MD=false
51
+ SKIP_GSD=false
52
+ NO_LAUNCHD=false
53
+
54
+ # Counters
55
+ HOOKS_ADDED=0
56
+ HOOKS_UPGRADED=0
57
+ CMDS_ADDED=0
58
+ CMDS_UPGRADED=0
59
+ AGENTS_ADDED=0
60
+ AGENTS_UPGRADED=0
61
+ SKILLS_ADDED=0
62
+ SKILLS_SKIPPED=0
63
+ SCRIPTS_ADDED=0
64
+ SCRIPTS_SKIPPED=0
65
+ MONITOR_SKIPPED=0
66
+ GSD_SKIPPED=0
67
+ KEYBINDINGS_SKIPPED=0
68
+ CLAUDE_MD_SKIPPED=0
69
+ UPGRADES_DIR="$CLAUDE_DIR/axel-upgrades"
70
+
71
+ # Colors
72
+ RED='\033[0;31m'
73
+ GREEN='\033[0;32m'
74
+ YELLOW='\033[0;33m'
75
+ BLUE='\033[0;34m'
76
+ BOLD='\033[1m'
77
+ DIM='\033[2m'
78
+ RESET='\033[0m'
79
+
80
+ log() { printf "${GREEN}[AXEL]${RESET} %s\n" "$1"; }
81
+ warn() { printf "${YELLOW}[WARN]${RESET} %s\n" "$1"; }
82
+ error() { printf "${RED}[ERROR]${RESET} %s\n" "$1" >&2; }
83
+ info() { printf "${BLUE}[INFO]${RESET} %s\n" "$1"; }
84
+ skip() { printf "${DIM} skip: %s (already up to date)${RESET}\n" "$1"; }
85
+ upgrade() { printf "${YELLOW} upgrade: %s (saved for review)${RESET}\n" "$1"; }
86
+
87
+ # --- Parse arguments ---
88
+ while [[ $# -gt 0 ]]; do
89
+ case $1 in
90
+ --user-name) USER_NAME="$2"; shift 2 ;;
91
+ --user-context) USER_CONTEXT="$2"; shift 2 ;;
92
+ --language) ASSISTANT_LANGUAGE="$2"; shift 2 ;;
93
+ --enable-posthog) ENABLE_POSTHOG=true; shift ;;
94
+ --posthog-context) POSTHOG_PROJECT_CONTEXT="$2"; shift 2 ;;
95
+ --profile) PROFILE="$2"; shift 2 ;;
96
+ --target) TARGET="$2"; shift 2 ;;
97
+ --output) OUTPUT_DIR="$2"; shift 2 ;;
98
+ --skip-plugins) SKIP_PLUGINS=true; shift ;;
99
+ --skip-monitor) SKIP_MONITOR=true; shift ;;
100
+ --skip-keybindings) SKIP_KEYBINDINGS=true; shift ;;
101
+ --skip-claude-md) SKIP_CLAUDE_MD=true; shift ;;
102
+ --skip-gsd) SKIP_GSD=true; shift ;;
103
+ --no-launchd) NO_LAUNCHD=true; shift ;;
104
+ --dry-run) DRY_RUN=true; shift ;;
105
+ -h|--help)
106
+ echo "Usage: bash bootstrap.sh [options]"
107
+ echo ""
108
+ echo "This script is ADDITIVE — it only adds new things, never overwrites."
109
+ echo "Your existing memory, settings, hooks, and CLAUDE.md are preserved."
110
+ echo ""
111
+ echo "Options:"
112
+ echo " --user-name NAME Your name (used in session summaries)"
113
+ echo " --user-context TEXT Short self-description used in hook prompts,"
114
+ echo " e.g. 'Tech Lead at Acme, backend-focused'"
115
+ echo " --language LANG Language the assistant should respond in inside"
116
+ echo " hook-generated prompts (e.g. 'spanish', 'english')"
117
+ echo " --enable-posthog Install the /posthog-weekly skill and the"
118
+ echo " PostHog snapshot loader. Off by default — only"
119
+ echo " enable if your team uses the PostHog MCP."
120
+ echo " --posthog-context TEXT Required with --enable-posthog. Short product"
121
+ echo " description used in the analytical prompt,"
122
+ echo " e.g. 'Acme ATS — recruiting platform with AI"
123
+ echo " sourcing'. Helps the agent frame findings."
124
+ echo " --profile NAME Install profile: core, personal, team-safe,"
125
+ echo " minimal, ci, or full. Defaults to core."
126
+ echo " --target NAME Runtime target: claude, codex, or generic."
127
+ echo " Defaults to claude."
128
+ echo " --output DIR Export directory for --target generic"
129
+ echo " --skip-plugins Do not install Claude marketplace plugins"
130
+ echo " --skip-monitor Do not install usage monitor tools"
131
+ echo " --skip-keybindings Do not install keybindings.json"
132
+ echo " --skip-claude-md Do not create ~/CLAUDE.md"
133
+ echo " --skip-gsd Do not run the external GSD installer"
134
+ echo " --no-launchd Do not install or load the macOS launchd agent"
135
+ echo " --dry-run Show what would be done without doing it"
136
+ exit 0
137
+ ;;
138
+ *) error "Unknown option: $1"; exit 1 ;;
139
+ esac
140
+ done
141
+
142
+ case "$PROFILE" in
143
+ default) PROFILE="core" ;;
144
+ core|personal|team-safe|minimal|ci|full) ;;
145
+ *) error "Unknown profile: $PROFILE"; exit 1 ;;
146
+ esac
147
+
148
+ case "$TARGET" in
149
+ default) TARGET="claude" ;;
150
+ claude|codex|generic) ;;
151
+ *) error "Unknown target: $TARGET"; exit 1 ;;
152
+ esac
153
+
154
+ if [ "$TARGET" = "generic" ] && [ -z "$OUTPUT_DIR" ]; then
155
+ error "--output is required when using --target generic"
156
+ exit 1
157
+ fi
158
+
159
+ case "$PROFILE" in
160
+ core)
161
+ SKIP_PLUGINS=true
162
+ SKIP_MONITOR=true
163
+ SKIP_KEYBINDINGS=true
164
+ SKIP_GSD=true
165
+ NO_LAUNCHD=true
166
+ ;;
167
+ minimal)
168
+ SKIP_PLUGINS=true
169
+ SKIP_MONITOR=true
170
+ SKIP_KEYBINDINGS=true
171
+ ;;
172
+ ci)
173
+ SKIP_PLUGINS=true
174
+ SKIP_MONITOR=true
175
+ SKIP_KEYBINDINGS=true
176
+ SKIP_CLAUDE_MD=true
177
+ SKIP_GSD=true
178
+ NO_LAUNCHD=true
179
+ ;;
180
+ esac
181
+
182
+ # --- Prerequisites check ---
183
+ log "Checking prerequisites..."
184
+
185
+ check_cmd() {
186
+ if ! command -v "$1" &>/dev/null; then
187
+ error "$1 is required but not installed."
188
+ return 1
189
+ fi
190
+ }
191
+
192
+ MISSING=0
193
+ if [ "$TARGET" = "claude" ]; then
194
+ check_cmd "claude" || MISSING=1
195
+ fi
196
+ check_cmd "node" || MISSING=1
197
+ check_cmd "jq" || MISSING=1
198
+ check_cmd "python3" || MISSING=1
199
+
200
+ if [ "$MISSING" -eq 1 ]; then
201
+ error "Install missing prerequisites and try again."
202
+ exit 1
203
+ fi
204
+
205
+ NODE_VER=$(node --version | sed 's/v//' | cut -d. -f1)
206
+ if [ "$NODE_VER" -lt 18 ]; then
207
+ error "Node.js >= 18 required (found v$NODE_VER)"
208
+ exit 1
209
+ fi
210
+
211
+ log "All prerequisites OK"
212
+
213
+ # --- Prompt for user name if not provided ---
214
+ if [ -z "$USER_NAME" ]; then
215
+ printf "${BOLD}Enter your name (for session summaries): ${RESET}"
216
+ read -r USER_NAME
217
+ if [ -z "$USER_NAME" ]; then
218
+ USER_NAME=$(whoami)
219
+ warn "Using system username: $USER_NAME"
220
+ fi
221
+ fi
222
+
223
+ # --- Defaults for user context and language (never prompt, just fall back) ---
224
+ if [ -z "$USER_CONTEXT" ]; then
225
+ USER_CONTEXT="a software engineer"
226
+ fi
227
+ if [ -z "$ASSISTANT_LANGUAGE" ]; then
228
+ ASSISTANT_LANGUAGE="english"
229
+ fi
230
+ info "Hook prompts will address you as: $USER_CONTEXT (responses in $ASSISTANT_LANGUAGE)"
231
+
232
+ # --- PostHog gating: only install the skill if --enable-posthog AND a context ---
233
+ if $ENABLE_POSTHOG && [ -z "$POSTHOG_PROJECT_CONTEXT" ]; then
234
+ POSTHOG_PROJECT_CONTEXT="a SaaS product (no detailed context provided — pass --posthog-context to refine)"
235
+ warn "--enable-posthog is set but --posthog-context is empty. Using a generic placeholder."
236
+ fi
237
+ if $ENABLE_POSTHOG; then
238
+ info "PostHog integration: ENABLED. Will install /posthog-weekly skill + snapshot loader."
239
+ info " Project context: $POSTHOG_PROJECT_CONTEXT"
240
+ fi
241
+ info "Install target: $TARGET"
242
+
243
+ # --- Dry run guard ---
244
+ run() {
245
+ if $DRY_RUN; then
246
+ info "[DRY RUN] $*"
247
+ else
248
+ "$@"
249
+ fi
250
+ }
251
+
252
+ settings_for_profile() {
253
+ local src="$1"
254
+ local dest="$2"
255
+
256
+ if [ "$PROFILE" = "personal" ] || [ "$PROFILE" = "full" ]; then
257
+ cp "$src" "$dest"
258
+ return
259
+ fi
260
+
261
+ jq '
262
+ .permissions.defaultMode = "acceptEdits" |
263
+ .permissions.allow = ((.permissions.allow // []) | map(select(. != "Bash(*)"))) |
264
+ .skipDangerousModePermissionPrompt = false
265
+ ' "$src" > "$dest"
266
+ }
267
+
268
+ write_processed_skill() {
269
+ local src="$1"
270
+ local dest="$2"
271
+
272
+ sed \
273
+ -e "s|{{POSTHOG_PROJECT_CONTEXT}}|$POSTHOG_PROJECT_CONTEXT|g" \
274
+ -e "s|{{ASSISTANT_LANGUAGE}}|$ASSISTANT_LANGUAGE|g" \
275
+ -e "s|{{USER_NAME}}|$USER_NAME|g" \
276
+ -e "s|{{USER_CONTEXT}}|$USER_CONTEXT|g" \
277
+ "$src" > "$dest"
278
+ }
279
+
280
+ copy_skill_file() {
281
+ local src="$1"
282
+ local dest="$2"
283
+ local rel="$3"
284
+
285
+ if $DRY_RUN; then
286
+ info "[DRY RUN] Would add skill asset: $rel"
287
+ return
288
+ fi
289
+
290
+ mkdir -p "$(dirname "$dest")"
291
+ if [ "$(basename "$src")" = "SKILL.md" ]; then
292
+ write_processed_skill "$src" "$dest"
293
+ else
294
+ cp -p "$src" "$dest"
295
+ fi
296
+ }
297
+
298
+ propose_skill_upgrade() {
299
+ local src="$1"
300
+ local dest="$2"
301
+ local rel="$3"
302
+ local skill_name="$4"
303
+ local proposed
304
+
305
+ proposed=$(mktemp)
306
+ if [ "$(basename "$src")" = "SKILL.md" ]; then
307
+ write_processed_skill "$src" "$proposed"
308
+ else
309
+ cp "$src" "$proposed"
310
+ fi
311
+
312
+ if [ ! -f "$dest" ] || ! diff -q "$proposed" "$dest" >/dev/null 2>&1; then
313
+ if ! $DRY_RUN; then
314
+ mkdir -p "$UPGRADES_DIR/skills/$skill_name/$(dirname "$rel")"
315
+ cp "$proposed" "$UPGRADES_DIR/skills/$skill_name/$rel"
316
+ else
317
+ info "[DRY RUN] Would propose skill asset upgrade: $skill_name/$rel"
318
+ fi
319
+ upgrade "skill: $skill_name/$rel"
320
+ SKILLS_UPGRADED=$((SKILLS_UPGRADED + 1))
321
+ fi
322
+
323
+ rm -f "$proposed"
324
+ }
325
+
326
+ # Helper: copy file; if destination exists AND differs, save AXEL version for review
327
+ add_or_upgrade() {
328
+ local src="$1"
329
+ local dest="$2"
330
+ local label="$3"
331
+ local added_var="$4"
332
+ local upgraded_var="$5"
333
+ local upgrade_subdir="$6" # e.g., "hooks", "commands", "agents"
334
+
335
+ if [ -f "$dest" ]; then
336
+ # Check if files differ (content-based, ignoring whitespace)
337
+ if ! diff -q "$src" "$dest" >/dev/null 2>&1; then
338
+ # Different content — save AXEL version as upgrade proposal
339
+ if $DRY_RUN; then
340
+ info "[DRY RUN] Would propose upgrade: $label"
341
+ else
342
+ mkdir -p "$UPGRADES_DIR/$upgrade_subdir"
343
+ cp "$src" "$UPGRADES_DIR/$upgrade_subdir/$(basename "$dest")"
344
+ fi
345
+ upgrade "$label"
346
+ eval "$upgraded_var=\$((\$$upgraded_var + 1))"
347
+ fi
348
+ # Same content — nothing to do
349
+ else
350
+ if $DRY_RUN; then
351
+ info "[DRY RUN] Would add: $label"
352
+ else
353
+ cp "$src" "$dest"
354
+ fi
355
+ eval "$added_var=\$((\$$added_var + 1))"
356
+ fi
357
+ }
358
+
359
+ runtime_root_for_target() {
360
+ case "$TARGET" in
361
+ codex) printf "%s" "$CODEX_DIR" ;;
362
+ generic) printf "%s" "$OUTPUT_DIR" ;;
363
+ *) printf "%s" "$CLAUDE_DIR" ;;
364
+ esac
365
+ }
366
+
367
+ copy_runtime_asset() {
368
+ local src="$1"
369
+ local dest="$2"
370
+ local label="$3"
371
+ local category="$4"
372
+ local upgrades_dir="$5"
373
+ local proposed=""
374
+ local compare_src="$src"
375
+
376
+ if [ "$(basename "$src")" = "SKILL.md" ]; then
377
+ proposed=$(mktemp)
378
+ write_processed_skill "$src" "$proposed"
379
+ compare_src="$proposed"
380
+ fi
381
+
382
+ if [ -f "$dest" ]; then
383
+ if ! diff -q "$compare_src" "$dest" >/dev/null 2>&1; then
384
+ if $DRY_RUN; then
385
+ info "[DRY RUN] Would propose upgrade: $label"
386
+ else
387
+ mkdir -p "$upgrades_dir/$category/$(dirname "$label")"
388
+ cp "$compare_src" "$upgrades_dir/$category/$label"
389
+ fi
390
+ upgrade "$label"
391
+ RUNTIME_UPGRADED=$((RUNTIME_UPGRADED + 1))
392
+ fi
393
+ else
394
+ if $DRY_RUN; then
395
+ info "[DRY RUN] Would add: $label"
396
+ else
397
+ mkdir -p "$(dirname "$dest")"
398
+ cp "$compare_src" "$dest"
399
+ fi
400
+ RUNTIME_ADDED=$((RUNTIME_ADDED + 1))
401
+ fi
402
+
403
+ if [ -n "$proposed" ]; then
404
+ rm -f "$proposed"
405
+ fi
406
+ }
407
+
408
+ write_manifest() {
409
+ local dest_dir="$1"
410
+
411
+ if $DRY_RUN; then
412
+ info "[DRY RUN] Would install axel-manifest.json with target: $TARGET, profile: $PROFILE"
413
+ return
414
+ fi
415
+
416
+ jq \
417
+ --arg profile "$PROFILE" \
418
+ --arg target "$TARGET" \
419
+ --arg installedRoot "$dest_dir" \
420
+ --argjson skipPlugins "$SKIP_PLUGINS" \
421
+ --argjson skipMonitor "$SKIP_MONITOR" \
422
+ --argjson skipKeybindings "$SKIP_KEYBINDINGS" \
423
+ --argjson skipClaudeMd "$SKIP_CLAUDE_MD" \
424
+ --argjson skipGsd "$SKIP_GSD" \
425
+ --argjson noLaunchd "$NO_LAUNCHD" \
426
+ --argjson enablePosthog "$ENABLE_POSTHOG" \
427
+ '. + {
428
+ profile: $profile,
429
+ target: $target,
430
+ installedRoot: $installedRoot,
431
+ enabled: {
432
+ "enable-posthog": $enablePosthog
433
+ },
434
+ skipped: {
435
+ "skip-plugins": $skipPlugins,
436
+ "skip-monitor": $skipMonitor,
437
+ "skip-keybindings": $skipKeybindings,
438
+ "skip-claude-md": $skipClaudeMd,
439
+ "skip-gsd": $skipGsd,
440
+ "no-launchd": $noLaunchd
441
+ }
442
+ }' \
443
+ "$SCRIPT_DIR/axel-manifest.json" > "$dest_dir/axel-manifest.json"
444
+ }
445
+
446
+ current_upgrade_path() {
447
+ local root_label="$1"
448
+ local category="$2"
449
+ local rel="$3"
450
+
451
+ case "$category" in
452
+ instructions) printf "%s/%s" "$root_label" "$rel" ;;
453
+ *) printf "%s/%s/%s" "$root_label" "$category" "$rel" ;;
454
+ esac
455
+ }
456
+
457
+ write_upgrade_review() {
458
+ local upgrades_dir="$1"
459
+ local root_label="$2"
460
+ local target_name="$3"
461
+ local category
462
+ local current_path
463
+ local rel
464
+
465
+ log "Generating upgrade review prompt..."
466
+ mkdir -p "$upgrades_dir"
467
+ cp "$SCRIPT_DIR/templates/review-upgrades.md" "$upgrades_dir/REVIEW.md"
468
+
469
+ cat > "$upgrades_dir/MANIFEST.md" << MANIFEST_EOF
470
+ # AXEL Upgrade Manifest
471
+
472
+ Generated: $(date +%Y-%m-%d\ %H:%M)
473
+ Target: $target_name
474
+
475
+ ## Files to review
476
+
477
+ These files already existed on your system but the AXEL package has improved versions.
478
+ Your agent should compare and merge the best parts of each file before anything is applied.
479
+
480
+ MANIFEST_EOF
481
+
482
+ for category in hooks commands agents skills scripts instructions tools templates; do
483
+ if [ -d "$upgrades_dir/$category" ]; then
484
+ echo "### $category" >> "$upgrades_dir/MANIFEST.md"
485
+ while IFS= read -r f; do
486
+ rel="${f#"$upgrades_dir/$category/"}"
487
+ current_path=$(current_upgrade_path "$root_label" "$category" "$rel")
488
+ echo "- \`$rel\`: upgrade at \`$upgrades_dir/$category/$rel\`, current at \`$current_path\`" >> "$upgrades_dir/MANIFEST.md"
489
+ done < <(find "$upgrades_dir/$category" -type f | sort)
490
+ echo "" >> "$upgrades_dir/MANIFEST.md"
491
+ fi
492
+ done
493
+ }
494
+
495
+ install_portable_target() {
496
+ local runtime_root
497
+ local upgrades_dir
498
+
499
+ runtime_root=$(runtime_root_for_target)
500
+ upgrades_dir="$runtime_root/axel-upgrades"
501
+ RUNTIME_ADDED=0
502
+ RUNTIME_UPGRADED=0
503
+
504
+ log "Installing AXEL portable runtime assets..."
505
+ info "Runtime root: $runtime_root"
506
+ info "Claude-only hooks, settings, plugins, keybindings, launchd, and GSD installers are skipped for target: $TARGET"
507
+
508
+ for dir in skills agents commands scripts; do
509
+ run mkdir -p "$runtime_root/$dir"
510
+ done
511
+
512
+ copy_runtime_asset \
513
+ "$SCRIPT_DIR/templates/AGENTS.runtime.md" \
514
+ "$runtime_root/AGENTS.md" \
515
+ "AGENTS.md" \
516
+ "instructions" \
517
+ "$upgrades_dir"
518
+
519
+ if [ -d "$SCRIPT_DIR/skills" ]; then
520
+ for skill_dir in "$SCRIPT_DIR/skills/"*/; do
521
+ [ -d "$skill_dir" ] || continue
522
+ SKILL_NAME=$(basename "$skill_dir")
523
+ if [ "$SKILL_NAME" = "posthog-weekly" ] && ! $ENABLE_POSTHOG; then
524
+ info " skip: posthog-weekly (use --enable-posthog to install)"
525
+ continue
526
+ fi
527
+
528
+ while IFS= read -r -d '' skill_file; do
529
+ rel="${skill_file#"$skill_dir"}"
530
+ copy_runtime_asset \
531
+ "$skill_file" \
532
+ "$runtime_root/skills/$SKILL_NAME/$rel" \
533
+ "skills/$SKILL_NAME/$rel" \
534
+ "skills" \
535
+ "$upgrades_dir"
536
+ done < <(find "$skill_dir" -type f ! -path '*/__pycache__/*' ! -name '*.pyc' -print0)
537
+ done
538
+ fi
539
+
540
+ for agent_file in "$SCRIPT_DIR/agents/"*.md; do
541
+ [ -f "$agent_file" ] || continue
542
+ copy_runtime_asset \
543
+ "$agent_file" \
544
+ "$runtime_root/agents/$(basename "$agent_file")" \
545
+ "agents/$(basename "$agent_file")" \
546
+ "agents" \
547
+ "$upgrades_dir"
548
+ done
549
+
550
+ for cmd_file in "$SCRIPT_DIR/commands/"*.md; do
551
+ [ -f "$cmd_file" ] || continue
552
+ copy_runtime_asset \
553
+ "$cmd_file" \
554
+ "$runtime_root/commands/$(basename "$cmd_file")" \
555
+ "commands/$(basename "$cmd_file")" \
556
+ "commands" \
557
+ "$upgrades_dir"
558
+ done
559
+
560
+ if [ -d "$SCRIPT_DIR/scripts" ]; then
561
+ for script_file in "$SCRIPT_DIR/scripts/"*.sh; do
562
+ [ -f "$script_file" ] || continue
563
+ BASE_SCRIPT=$(basename "$script_file")
564
+ if [ "$BASE_SCRIPT" = "posthog-snapshot-loader.sh" ] && ! $ENABLE_POSTHOG; then
565
+ info " skip: $BASE_SCRIPT (use --enable-posthog to install)"
566
+ continue
567
+ fi
568
+ copy_runtime_asset \
569
+ "$script_file" \
570
+ "$runtime_root/scripts/$BASE_SCRIPT" \
571
+ "scripts/$BASE_SCRIPT" \
572
+ "scripts" \
573
+ "$upgrades_dir"
574
+ if ! $DRY_RUN; then
575
+ chmod +x "$runtime_root/scripts/$BASE_SCRIPT"
576
+ fi
577
+ done
578
+ fi
579
+
580
+ write_manifest "$runtime_root"
581
+
582
+ if [ "$RUNTIME_UPGRADED" -gt 0 ] && ! $DRY_RUN; then
583
+ write_upgrade_review "$upgrades_dir" "$runtime_root" "$TARGET"
584
+ fi
585
+
586
+ echo ""
587
+ printf "${GREEN}${BOLD}============================================${RESET}\n"
588
+ printf "${GREEN}${BOLD} AXEL Portable Runtime Complete!${RESET}\n"
589
+ printf "${GREEN}${BOLD}============================================${RESET}\n"
590
+ echo ""
591
+ info "Target: $TARGET"
592
+ info "Profile: $PROFILE"
593
+ info "Runtime root: $runtime_root"
594
+ info "Files added: $RUNTIME_ADDED"
595
+ info "Upgrades available: $RUNTIME_UPGRADED"
596
+ info "Claude Code remains the default target. This target installs portable AXEL assets only."
597
+ echo ""
598
+ }
599
+
600
+ if [ "$TARGET" != "claude" ]; then
601
+ install_portable_target
602
+ exit 0
603
+ fi
604
+
605
+ # --- Backup existing config (safety net, always) ---
606
+ if [ -d "$CLAUDE_DIR" ] && [ -f "$CLAUDE_DIR/settings.json" ]; then
607
+ log "Creating safety backup at $BACKUP_DIR"
608
+ run mkdir -p "$BACKUP_DIR"
609
+ for item in settings.json settings.local.json keybindings.json; do
610
+ [ -f "$CLAUDE_DIR/$item" ] && run cp "$CLAUDE_DIR/$item" "$BACKUP_DIR/$item"
611
+ done
612
+ [ -d "$CLAUDE_DIR/hooks" ] && run cp -r "$CLAUDE_DIR/hooks" "$BACKUP_DIR/hooks"
613
+ fi
614
+
615
+ # --- Create directory structure (mkdir -p is already additive) ---
616
+ log "Ensuring directory structure..."
617
+ for dir in hooks commands agents skills memory memory/decisions sessions sessions/checkpoints logs; do
618
+ run mkdir -p "$CLAUDE_DIR/$dir"
619
+ done
620
+
621
+ # ============================================================================
622
+ # 1. HOOKS — Only add new ones, never overwrite existing
623
+ # ============================================================================
624
+ log "Adding hooks..."
625
+
626
+ for hook_file in "$SCRIPT_DIR/hooks/"*; do
627
+ [ -f "$hook_file" ] || continue
628
+ BASENAME=$(basename "$hook_file")
629
+ DEST="$CLAUDE_DIR/hooks/$BASENAME"
630
+
631
+ # Hooks need placeholder substitution, so we use a temp file for comparison.
632
+ # Keep the sed list in sync with any new {{PLACEHOLDER}} added to hook files.
633
+ PROCESSED=$(mktemp)
634
+ sed \
635
+ -e "s|{{USER_NAME}}|$USER_NAME|g" \
636
+ -e "s|{{USER_CONTEXT}}|$USER_CONTEXT|g" \
637
+ -e "s|{{ASSISTANT_LANGUAGE}}|$ASSISTANT_LANGUAGE|g" \
638
+ "$hook_file" > "$PROCESSED"
639
+
640
+ if [ -f "$DEST" ]; then
641
+ if ! diff -q "$PROCESSED" "$DEST" >/dev/null 2>&1; then
642
+ if ! $DRY_RUN; then
643
+ mkdir -p "$UPGRADES_DIR/hooks"
644
+ cp "$PROCESSED" "$UPGRADES_DIR/hooks/$BASENAME"
645
+ fi
646
+ upgrade "$BASENAME"
647
+ HOOKS_UPGRADED=$((HOOKS_UPGRADED + 1))
648
+ fi
649
+ else
650
+ if ! $DRY_RUN; then
651
+ cp "$PROCESSED" "$DEST"
652
+ chmod +x "$DEST"
653
+ else
654
+ info "[DRY RUN] Would add hook: $BASENAME"
655
+ fi
656
+ HOOKS_ADDED=$((HOOKS_ADDED + 1))
657
+ fi
658
+ rm -f "$PROCESSED"
659
+ done
660
+
661
+ log "Hooks: $HOOKS_ADDED new, $HOOKS_UPGRADED upgrades available"
662
+
663
+ # ============================================================================
664
+ # 2. COMMANDS — Only add new ones
665
+ # ============================================================================
666
+ log "Adding commands..."
667
+
668
+ for cmd_file in "$SCRIPT_DIR/commands/"*.md; do
669
+ [ -f "$cmd_file" ] || continue
670
+ BASENAME=$(basename "$cmd_file")
671
+ add_or_upgrade "$cmd_file" "$CLAUDE_DIR/commands/$BASENAME" "$BASENAME" "CMDS_ADDED" "CMDS_UPGRADED" "commands"
672
+ done
673
+
674
+ # GSD (get-shit-done) is intentionally NOT vendored by AXEL. It ships and
675
+ # updates through its own installer, so bundling a frozen copy only caused
676
+ # version + command-format skew (the colon-form /gsd: commands were removed
677
+ # upstream in favor of /gsd- skills). AXEL consumes the live GSD skills/agents.
678
+ if [ -d "$CLAUDE_DIR/skills" ] && ls "$CLAUDE_DIR"/skills/gsd-* >/dev/null 2>&1; then
679
+ skip "GSD (managed by its own installer)"
680
+ else
681
+ info "GSD not detected — install it separately: npx get-shit-done-cc@latest --claude --global"
682
+ fi
683
+
684
+ log "Commands: $CMDS_ADDED new, $CMDS_UPGRADED upgrades"
685
+
686
+ # ============================================================================
687
+ # 3. AGENTS — Only add new ones
688
+ # ============================================================================
689
+ log "Adding agents..."
690
+
691
+ for agent_file in "$SCRIPT_DIR/agents/"*.md; do
692
+ [ -f "$agent_file" ] || continue
693
+ BASENAME=$(basename "$agent_file")
694
+ add_or_upgrade "$agent_file" "$CLAUDE_DIR/agents/$BASENAME" "$BASENAME" "AGENTS_ADDED" "AGENTS_UPGRADED" "agents"
695
+ done
696
+
697
+ log "Agents: $AGENTS_ADDED new, $AGENTS_UPGRADED upgrades available"
698
+
699
+ # ============================================================================
700
+ # 4. SKILLS — Only add new skill directories
701
+ # ============================================================================
702
+ log "Adding skills..."
703
+
704
+ SKILLS_ADDED=0
705
+ SKILLS_UPGRADED=0
706
+ if [ -d "$SCRIPT_DIR/skills" ]; then
707
+ for skill_dir in "$SCRIPT_DIR/skills/"*/; do
708
+ [ -d "$skill_dir" ] || continue
709
+ SKILL_NAME=$(basename "$skill_dir")
710
+
711
+ # Gate optional skills behind feature flags
712
+ if [ "$SKILL_NAME" = "posthog-weekly" ] && ! $ENABLE_POSTHOG; then
713
+ info " skip: posthog-weekly (use --enable-posthog to install)"
714
+ SKILLS_SKIPPED=$((SKILLS_SKIPPED + 1))
715
+ continue
716
+ fi
717
+
718
+ if [ -d "$CLAUDE_DIR/skills/$SKILL_NAME" ]; then
719
+ while IFS= read -r -d '' skill_file; do
720
+ rel="${skill_file#"$skill_dir"}"
721
+ dest="$CLAUDE_DIR/skills/$SKILL_NAME/$rel"
722
+ propose_skill_upgrade "$skill_file" "$dest" "$rel" "$SKILL_NAME"
723
+ done < <(find "$skill_dir" -type f ! -path '*/__pycache__/*' ! -name '*.pyc' -print0)
724
+ else
725
+ run mkdir -p "$CLAUDE_DIR/skills/$SKILL_NAME"
726
+ while IFS= read -r -d '' skill_file; do
727
+ rel="${skill_file#"$skill_dir"}"
728
+ dest="$CLAUDE_DIR/skills/$SKILL_NAME/$rel"
729
+ copy_skill_file "$skill_file" "$dest" "$SKILL_NAME/$rel"
730
+ done < <(find "$skill_dir" -type f ! -path '*/__pycache__/*' ! -name '*.pyc' -print0)
731
+ SKILLS_ADDED=$((SKILLS_ADDED + 1))
732
+ fi
733
+ done
734
+ fi
735
+
736
+ # ============================================================================
737
+ # 4b. SCRIPTS — Helper bash scripts called by skills/commands (PostHog, etc.)
738
+ # ============================================================================
739
+ if [ -d "$SCRIPT_DIR/scripts" ]; then
740
+ log "Adding helper scripts..."
741
+ run mkdir -p "$CLAUDE_DIR/scripts"
742
+ for script_file in "$SCRIPT_DIR/scripts/"*.sh; do
743
+ [ -f "$script_file" ] || continue
744
+ BASE_SCRIPT=$(basename "$script_file")
745
+
746
+ # Gate optional helpers behind feature flags (mirror skill gating)
747
+ if [ "$BASE_SCRIPT" = "posthog-snapshot-loader.sh" ] && ! $ENABLE_POSTHOG; then
748
+ info " skip: $BASE_SCRIPT (use --enable-posthog to install)"
749
+ SCRIPTS_SKIPPED=$((SCRIPTS_SKIPPED + 1))
750
+ continue
751
+ fi
752
+
753
+ DEST_SCRIPT="$CLAUDE_DIR/scripts/$BASE_SCRIPT"
754
+ if [ -f "$DEST_SCRIPT" ]; then
755
+ if ! diff -q "$script_file" "$DEST_SCRIPT" >/dev/null 2>&1; then
756
+ if ! $DRY_RUN; then
757
+ mkdir -p "$UPGRADES_DIR/scripts"
758
+ cp "$script_file" "$UPGRADES_DIR/scripts/$BASE_SCRIPT"
759
+ fi
760
+ upgrade "script: $BASE_SCRIPT"
761
+ fi
762
+ else
763
+ run cp "$script_file" "$DEST_SCRIPT"
764
+ run chmod +x "$DEST_SCRIPT"
765
+ info " added: scripts/$BASE_SCRIPT"
766
+ SCRIPTS_ADDED=$((SCRIPTS_ADDED + 1))
767
+ fi
768
+ done
769
+ fi
770
+
771
+ # ============================================================================
772
+ # 5. PLUGINS — Install only those not already present
773
+ # ============================================================================
774
+ log "Installing plugins (skipping already-installed)..."
775
+
776
+ PLUGINS=(
777
+ "frontend-design"
778
+ "context7"
779
+ "ruby-lsp"
780
+ "typescript-lsp"
781
+ "pyright-lsp"
782
+ "code-simplifier"
783
+ "hookify"
784
+ "claude-md-management"
785
+ "commit-commands"
786
+ "pr-review-toolkit"
787
+ )
788
+
789
+ PLUGINS_ADDED=0
790
+ PLUGINS_SKIPPED=0
791
+
792
+ if $SKIP_PLUGINS; then
793
+ PLUGINS_SKIPPED=${#PLUGINS[@]}
794
+ info "Plugins skipped by profile or --skip-plugins"
795
+ else
796
+ # Check which plugins are already installed
797
+ INSTALLED_PLUGINS=""
798
+ if [ -f "$CLAUDE_DIR/plugins/installed_plugins.json" ]; then
799
+ INSTALLED_PLUGINS=$(cat "$CLAUDE_DIR/plugins/installed_plugins.json")
800
+ fi
801
+
802
+ for plugin in "${PLUGINS[@]}"; do
803
+ PLUGIN_KEY="${plugin}@claude-plugins-official"
804
+ if echo "$INSTALLED_PLUGINS" | grep -q "\"$PLUGIN_KEY\"" 2>/dev/null; then
805
+ skip "plugin: $plugin"
806
+ PLUGINS_SKIPPED=$((PLUGINS_SKIPPED + 1))
807
+ else
808
+ if $DRY_RUN; then
809
+ info "[DRY RUN] Would install plugin: $plugin"
810
+ else
811
+ log " Installing $plugin..."
812
+ claude plugins install "$plugin" 2>/dev/null || warn "Failed to install $plugin (may need manual install)"
813
+ fi
814
+ PLUGINS_ADDED=$((PLUGINS_ADDED + 1))
815
+ fi
816
+ done
817
+ fi
818
+
819
+ log "Plugins: $PLUGINS_ADDED added, $PLUGINS_SKIPPED already installed"
820
+
821
+ # ============================================================================
822
+ # 6. SETTINGS.JSON — MERGE, never replace
823
+ # ============================================================================
824
+ log "Merging settings.json (preserving all existing config)..."
825
+
826
+ AXEL_SETTINGS="$SCRIPT_DIR/templates/settings.json"
827
+ EXISTING_SETTINGS="$CLAUDE_DIR/settings.json"
828
+ AXEL_SETTINGS_PROFILED=$(mktemp)
829
+ settings_for_profile "$AXEL_SETTINGS" "$AXEL_SETTINGS_PROFILED"
830
+
831
+ if $DRY_RUN; then
832
+ info "[DRY RUN] Would merge AXEL settings into existing settings.json"
833
+ elif [ ! -f "$EXISTING_SETTINGS" ]; then
834
+ # No existing settings — just copy ours
835
+ cp "$AXEL_SETTINGS_PROFILED" "$EXISTING_SETTINGS"
836
+ log " Created new settings.json (no existing config found)"
837
+ else
838
+ # Deep merge using jq filter file (avoids shell escape issues)
839
+ # Strategy: existing always wins for scalars; arrays/objects get unioned
840
+ MERGE_FILTER="$SCRIPT_DIR/templates/merge-settings.jq"
841
+ MERGED_FILE=$(mktemp)
842
+
843
+ if jq -s -f "$MERGE_FILTER" "$EXISTING_SETTINGS" "$AXEL_SETTINGS_PROFILED" > "$MERGED_FILE" 2>/dev/null; then
844
+ # Validate the output is valid JSON before replacing
845
+ if jq '.' "$MERGED_FILE" >/dev/null 2>&1; then
846
+ mv "$MERGED_FILE" "$EXISTING_SETTINGS"
847
+ log " Settings merged successfully (your existing config preserved)"
848
+ else
849
+ rm -f "$MERGED_FILE"
850
+ warn "Settings merge produced invalid JSON — original preserved"
851
+ cp "$AXEL_SETTINGS_PROFILED" "$CLAUDE_DIR/settings.axel-template.json"
852
+ fi
853
+ else
854
+ rm -f "$MERGED_FILE"
855
+ warn "Settings merge failed — your original settings.json is untouched"
856
+ warn "AXEL settings saved to: $CLAUDE_DIR/settings.axel-template.json"
857
+ cp "$AXEL_SETTINGS_PROFILED" "$CLAUDE_DIR/settings.axel-template.json"
858
+ fi
859
+ fi
860
+ rm -f "$AXEL_SETTINGS_PROFILED"
861
+
862
+ # ============================================================================
863
+ # 6b. AXEL MANIFEST — Machine-readable install inventory for doctor/tests
864
+ # ============================================================================
865
+ log "Installing AXEL manifest..."
866
+ write_manifest "$CLAUDE_DIR"
867
+
868
+ # ============================================================================
869
+ # 7. STATUSLINE — Add only if not present
870
+ # ============================================================================
871
+ if [ ! -f "$CLAUDE_DIR/statusline-command.sh" ]; then
872
+ log "Adding statusline script..."
873
+ run cp "$SCRIPT_DIR/templates/statusline-command.sh" "$CLAUDE_DIR/statusline-command.sh"
874
+ run chmod +x "$CLAUDE_DIR/statusline-command.sh"
875
+ else
876
+ skip "statusline-command.sh"
877
+ fi
878
+
879
+ # ============================================================================
880
+ # 7b. USAGE MONITOR — session cost log + live dashboard + web server
881
+ # ============================================================================
882
+ log "Setting up usage monitor..."
883
+
884
+ MONITOR_TOOLS=(session-server.js session-live.sh session-dashboard-gen.sh session-costs-view.sh)
885
+ MONITOR_ADDED=0
886
+ if $SKIP_MONITOR; then
887
+ MONITOR_SKIPPED=${#MONITOR_TOOLS[@]}
888
+ info "Usage monitor skipped by profile or --skip-monitor"
889
+ else
890
+ # Ensure tools dir exists
891
+ run mkdir -p "$CLAUDE_DIR/tools" "$CLAUDE_DIR/logs"
892
+
893
+ for tool in "${MONITOR_TOOLS[@]}"; do
894
+ src="$SCRIPT_DIR/tools/$tool"
895
+ dest="$CLAUDE_DIR/tools/$tool"
896
+ [ -f "$src" ] || continue
897
+ if [ ! -f "$dest" ]; then
898
+ if ! $DRY_RUN; then cp "$src" "$dest" && chmod +x "$dest"; else info "[DRY RUN] Would add tool: $tool"; fi
899
+ MONITOR_ADDED=$((MONITOR_ADDED + 1))
900
+ else
901
+ skip "tools/$tool"
902
+ fi
903
+ done
904
+ fi
905
+
906
+ # Install launchd service (macOS only) — auto-starts web monitor on login
907
+ if ! $SKIP_MONITOR && [[ "$OSTYPE" == "darwin"* ]]; then
908
+ PLIST_LABEL="com.${USERNAME:-$(whoami)}.claude-monitor"
909
+ PLIST_DEST="$HOME/Library/LaunchAgents/${PLIST_LABEL}.plist"
910
+ PLIST_SRC="$SCRIPT_DIR/templates/claude-monitor.plist"
911
+ NODE_BIN=$(which node 2>/dev/null || echo "/usr/local/bin/node")
912
+
913
+ if [ ! -f "$PLIST_DEST" ]; then
914
+ if $NO_LAUNCHD; then
915
+ info "launchd agent skipped by --no-launchd or profile"
916
+ elif ! $DRY_RUN; then
917
+ mkdir -p "$(dirname "$PLIST_DEST")"
918
+ sed -e "s|{{USERNAME}}|${USERNAME:-$(whoami)}|g" \
919
+ -e "s|{{HOME}}|$HOME|g" \
920
+ -e "s|{{NODE_PATH}}|$NODE_BIN|g" \
921
+ "$PLIST_SRC" > "$PLIST_DEST"
922
+ launchctl load "$PLIST_DEST" 2>/dev/null && \
923
+ log " Usage monitor started at http://localhost:9119" || \
924
+ warn " launchd load failed — run manually: node ~/.claude/tools/session-server.js"
925
+ else
926
+ info "[DRY RUN] Would install launchd agent: $PLIST_LABEL"
927
+ fi
928
+ else
929
+ skip "launchd agent: $PLIST_LABEL"
930
+ fi
931
+ fi
932
+
933
+ log "Usage monitor: $MONITOR_ADDED tools added | Dashboard: http://localhost:9119"
934
+
935
+ # ============================================================================
936
+ # 8. KEYBINDINGS — Merge, not replace
937
+ # ============================================================================
938
+ if $SKIP_KEYBINDINGS; then
939
+ KEYBINDINGS_SKIPPED=1
940
+ info "keybindings.json skipped by profile or --skip-keybindings"
941
+ elif [ ! -f "$CLAUDE_DIR/keybindings.json" ]; then
942
+ log "Adding keybindings..."
943
+ run cp "$SCRIPT_DIR/templates/keybindings.json" "$CLAUDE_DIR/keybindings.json"
944
+ else
945
+ skip "keybindings.json (preserving your custom bindings)"
946
+ fi
947
+
948
+ # ============================================================================
949
+ # 9. MEMORY SYSTEM — Only ensure structure exists, NEVER touch content
950
+ # ============================================================================
951
+ log "Ensuring memory system structure..."
952
+
953
+ run mkdir -p "$CLAUDE_DIR/memory/decisions"
954
+
955
+ if [ ! -f "$CLAUDE_DIR/memory/MEMORY.md" ]; then
956
+ if ! $DRY_RUN; then
957
+ cat > "$CLAUDE_DIR/memory/MEMORY.md" << 'EOF'
958
+ # Memory Index
959
+
960
+ This file is automatically maintained. Each entry points to a memory file.
961
+ Memory types: user, feedback, project, reference.
962
+ EOF
963
+ else
964
+ info "[DRY RUN] Would create MEMORY.md index"
965
+ fi
966
+ log " MEMORY.md index is missing"
967
+ else
968
+ MEMORY_COUNT=$(find "$CLAUDE_DIR/memory" -maxdepth 1 -type f -name '*.md' ! -name MEMORY.md | wc -l | tr -d ' ')
969
+ log " Memory intact: $MEMORY_COUNT existing memories preserved"
970
+ fi
971
+
972
+ # ============================================================================
973
+ # 10. TEAM CLAUDE.MD — Offer only if not present
974
+ # ============================================================================
975
+ if $SKIP_CLAUDE_MD; then
976
+ CLAUDE_MD_SKIPPED=1
977
+ info "~/CLAUDE.md skipped by profile or --skip-claude-md"
978
+ elif [ ! -f "$HOME/CLAUDE.md" ]; then
979
+ log "Creating starter CLAUDE.md..."
980
+ run cp "$SCRIPT_DIR/templates/CLAUDE.md" "$HOME/CLAUDE.md"
981
+ if $DRY_RUN; then
982
+ log " Would create ~/CLAUDE.md"
983
+ else
984
+ log " Created ~/CLAUDE.md — customize it for your workflow"
985
+ fi
986
+ else
987
+ CLAUDE_MD_LINES=$(wc -l < "$HOME/CLAUDE.md" | tr -d ' ')
988
+ log " ~/CLAUDE.md preserved ($CLAUDE_MD_LINES lines)"
989
+ fi
990
+
991
+ # ============================================================================
992
+ # 11. GSD (Get Shit Done) package
993
+ # ============================================================================
994
+ if $SKIP_GSD; then
995
+ GSD_SKIPPED=1
996
+ info "GSD external installer skipped by profile or --skip-gsd"
997
+ elif [ -d "$CLAUDE_DIR/get-shit-done" ]; then
998
+ log "GSD already installed, skipping"
999
+ else
1000
+ log "Installing GSD (Get Shit Done) package..."
1001
+ if $DRY_RUN; then
1002
+ info "[DRY RUN] Would install get-shit-done-cc via npx"
1003
+ else
1004
+ npx -y get-shit-done-cc@latest 2>/dev/null && log "GSD installed" || warn "GSD install failed — install manually: npx get-shit-done-cc@latest"
1005
+ fi
1006
+ fi
1007
+
1008
+ # ============================================================================
1009
+ # 12. UPGRADE REVIEW PROMPT — Generate if there are upgrades to review
1010
+ # ============================================================================
1011
+ TOTAL_UPGRADES=$((HOOKS_UPGRADED + CMDS_UPGRADED + AGENTS_UPGRADED + SKILLS_UPGRADED))
1012
+
1013
+ if [ "$TOTAL_UPGRADES" -gt 0 ] && ! $DRY_RUN; then
1014
+ write_upgrade_review "$UPGRADES_DIR" "$HOME/.claude" "$TARGET"
1015
+ fi
1016
+
1017
+ # ============================================================================
1018
+ # DONE — Summary
1019
+ # ============================================================================
1020
+ echo ""
1021
+ printf "${GREEN}${BOLD}============================================${RESET}\n"
1022
+ printf "${GREEN}${BOLD} AXEL Onboarding Complete!${RESET}\n"
1023
+ printf "${GREEN}${BOLD}============================================${RESET}\n"
1024
+ echo ""
1025
+ log "All changes are ADDITIVE — nothing was overwritten or deleted."
1026
+ info "Install profile: $PROFILE"
1027
+ info "Install target: $TARGET"
1028
+ if $DRY_RUN; then
1029
+ info "Safety backup would be at: $BACKUP_DIR"
1030
+ else
1031
+ log "Safety backup at: $BACKUP_DIR"
1032
+ fi
1033
+ echo ""
1034
+
1035
+ printf "${BOLD}What was added (new files):${RESET}\n"
1036
+ info " Hooks: $HOOKS_ADDED new"
1037
+ info " Commands: $CMDS_ADDED new"
1038
+ info " Agents: $AGENTS_ADDED new"
1039
+ info " Skills: $SKILLS_ADDED new"
1040
+ info " Plugins: $PLUGINS_ADDED new ($PLUGINS_SKIPPED already installed)"
1041
+ info " Scripts: $SCRIPTS_ADDED new ($SCRIPTS_SKIPPED skipped)"
1042
+ info " Monitor: $MONITOR_ADDED tools ($MONITOR_SKIPPED skipped) | http://localhost:9119"
1043
+ info " GSD: $GSD_SKIPPED skipped"
1044
+ info " Keybinds: $KEYBINDINGS_SKIPPED skipped"
1045
+ info " CLAUDE.md:$CLAUDE_MD_SKIPPED skipped"
1046
+ echo ""
1047
+
1048
+ if [ "$TOTAL_UPGRADES" -gt 0 ]; then
1049
+ printf "${YELLOW}${BOLD}Upgrades available: $TOTAL_UPGRADES files have improved versions${RESET}\n"
1050
+ info " Hooks: $HOOKS_UPGRADED | Commands: $CMDS_UPGRADED"
1051
+ info " Agents: $AGENTS_UPGRADED | Skills: $SKILLS_UPGRADED"
1052
+ echo ""
1053
+ printf "${BOLD}To review and apply upgrades, run this in Claude Code:${RESET}\n"
1054
+ echo ""
1055
+ printf " ${GREEN}Read the file ~/.claude/axel-upgrades/REVIEW.md and follow its instructions${RESET}\n"
1056
+ echo ""
1057
+ info "Your agent will compare each file, explain what's better,"
1058
+ info "and let you decide what to merge. Nothing changes without your approval."
1059
+ echo ""
1060
+ fi
1061
+
1062
+ printf "${BOLD}What was preserved:${RESET}\n"
1063
+ info " Your existing memory (all files intact)"
1064
+ info " Your existing settings (merged, not replaced)"
1065
+ info " Your existing CLAUDE.md"
1066
+ info " Your existing hooks, commands, and agents"
1067
+ echo ""
1068
+
1069
+ info "Next steps:"
1070
+ info " 1. Restart Claude Code to load AXEL updates"
1071
+ if $SKIP_GSD; then
1072
+ TRY_COMMANDS="/daily, /style"
1073
+ else
1074
+ TRY_COMMANDS="/daily, /style, /gsd-help"
1075
+ fi
1076
+ if [ "$TOTAL_UPGRADES" -gt 0 ]; then
1077
+ info " 2. Review upgrades: paste the command above into Claude Code"
1078
+ info " 3. Try: $TRY_COMMANDS"
1079
+ else
1080
+ info " 2. Try: $TRY_COMMANDS"
1081
+ fi
1082
+ if [ "$PROFILE" = "core" ]; then
1083
+ info " AXEL installed public safe defaults. Use --profile personal for fuller local automation."
1084
+ else
1085
+ info " AXEL will continue learning your personal preferences"
1086
+ fi
1087
+ echo ""