-
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·553 lines (510 loc) · 19.2 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·553 lines (510 loc) · 19.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
#!/bin/bash
# Director Mode Lite - Installation Script
# Guidance-first installation with native Claude Code, Codex, and Grok adapters.
#
# Usage: ./install.sh [--update] [--wizard] [--cli all|claude|codex|grok]
# [--hooks guide|none|automation] [target-dir]
# --update Overwrite distributed files (agents/skills/hooks + .self-evolving-loop
# scaffolding) instead of skipping existing ones. CLAUDE.md is never
# overwritten. target-dir defaults to the current directory.
# --cli Install native adapters for one CLI or all three (default: all).
# --hooks none (zero-interference default), guide (non-blocking Claude /
# Codex context), or automation (legacy blocking Claude hooks,
# explicit opt-in). Default: none.
# --no-hooks Alias for --hooks none.
# --wizard Interactive CLI and guidance setup. Requires a TTY; otherwise
# falls back to the guidance-first defaults.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Parse flags + optional target dir.
UPDATE_MODE=0
WIZARD_MODE=0
CLI_TARGETS="all"
HOOK_MODE="none"
TARGET_DIR="."
_target_set=0
while [[ $# -gt 0 ]]; do
case "$1" in
--update)
UPDATE_MODE=1
;;
--wizard)
WIZARD_MODE=1
;;
--cli)
[[ $# -ge 2 ]] || { echo "Error: --cli requires a value" >&2; exit 2; }
CLI_TARGETS="$2"
shift
;;
--cli=*)
CLI_TARGETS="${1#*=}"
;;
--hooks)
[[ $# -ge 2 ]] || { echo "Error: --hooks requires a value" >&2; exit 2; }
HOOK_MODE="$2"
shift
;;
--hooks=*)
HOOK_MODE="${1#*=}"
;;
--no-hooks)
HOOK_MODE="none"
;;
-h|--help)
sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
--*)
echo "Error: unknown option: $1" >&2
exit 2
;;
*)
if [[ $_target_set -eq 0 ]]; then
TARGET_DIR="$1"
_target_set=1
else
echo "Error: only one target directory may be provided" >&2
exit 2
fi
;;
esac
shift
done
case "$CLI_TARGETS" in
all|claude|codex|grok) ;;
*) echo "Error: --cli must be all, claude, codex, or grok" >&2; exit 2 ;;
esac
case "$HOOK_MODE" in
guide|none|automation) ;;
*) echo "Error: --hooks must be guide, none, or automation" >&2; exit 2 ;;
esac
BACKUP_DIR=""
echo "Director Mode Lite Installer"
echo "============================"
if [[ $UPDATE_MODE -eq 1 ]]; then
echo "Mode: update (overwrite distributed files)"
else
echo "Mode: install (skip existing files)"
fi
echo "CLIs: $CLI_TARGETS"
echo "Hooks: $HOOK_MODE"
echo ""
# Shared files are the default; active hooks are not. Legacy Stop-hook
# automation remains available only through an explicit choice.
ENABLE_STOP_AUTOLOOP=0
ENABLE_EVOLVING_LOOP=0
ENABLE_CHANGELOG=0
ENABLE_VALIDATOR=0
PROJECT_TYPE="unspecified"
if [[ "$HOOK_MODE" == "automation" ]]; then
ENABLE_STOP_AUTOLOOP=1
ENABLE_CHANGELOG=1
ENABLE_VALIDATOR=1
fi
run_setup_wizard() {
echo "Setup Wizard"
echo "------------"
echo ""
echo "What best describes this project?"
echo " 1) Web app / API service"
echo " 2) CLI tool / library"
echo " 3) Exploring or prototyping"
echo " 4) Dogfooding Director Mode itself"
read -r -p "Choice [1-4, default 1]: " proj_choice
case "$proj_choice" in
2) PROJECT_TYPE="cli-or-library" ;;
3) PROJECT_TYPE="exploring" ;;
4) PROJECT_TYPE="dogfooding" ;;
*) PROJECT_TYPE="web-or-api" ;;
esac
echo ""
echo "Which CLI adapters should be installed?"
echo " 1) Claude Code + Codex CLI + Grok Build (recommended)"
echo " 2) Claude Code"
echo " 3) Codex CLI"
echo " 4) Grok Build"
read -r -p "Choice [1-4, default 1]: " cli_choice
case "$cli_choice" in
2) CLI_TARGETS="claude" ;;
3) CLI_TARGETS="codex" ;;
4) CLI_TARGETS="grok" ;;
*) CLI_TARGETS="all" ;;
esac
echo ""
echo "Setup style:"
echo " 1) Guidance only, no active hooks (recommended; zero interference)"
echo " 2) Add a non-blocking SessionStart context hook for Claude/Codex"
echo " 3) Legacy Claude automation (blocking Stop hook; explicit opt-in)"
read -r -p "Choice [1-3, default 1]: " mode_choice
case "$mode_choice" in
2)
HOOK_MODE="guide"
;;
3)
HOOK_MODE="automation"
ENABLE_STOP_AUTOLOOP=1
ENABLE_CHANGELOG=1
ENABLE_VALIDATOR=1
;;
*)
HOOK_MODE="none"
ENABLE_STOP_AUTOLOOP=0
ENABLE_CHANGELOG=0
ENABLE_VALIDATOR=0
;;
esac
echo ""
}
if [[ $WIZARD_MODE -eq 1 ]]; then
# DML_WIZARD_FORCE lets tests feed prompt answers over a piped stdin,
# where -t 0 is false even though a script deliberately provided answers.
if [[ -t 0 || -n "${DML_WIZARD_FORCE:-}" ]]; then
run_setup_wizard
else
echo "Warning: --wizard requires an interactive terminal (no TTY on stdin)."
echo " Falling back to zero-hook guidance for all three CLIs."
echo ""
fi
fi
# Check dependencies
MISSING_DEPS=0
if ! command -v python3 &>/dev/null; then
echo "Error: python3 is required for native adapters and the session relay."
echo " Install: brew install python3 (macOS) or apt install python3 (Linux)"
exit 1
fi
if [[ "$HOOK_MODE" == "automation" ]] && ! command -v jq &>/dev/null; then
echo "Warning: jq not found. Some hooks may not work correctly."
echo " Install: brew install jq (macOS) or apt install jq (Linux)"
MISSING_DEPS=1
fi
if [[ $MISSING_DEPS -gt 0 ]]; then
echo ""
fi
# Check target directory
if [[ ! -d "$TARGET_DIR" ]]; then
echo "Error: Target directory does not exist: $TARGET_DIR"
exit 1
fi
TARGET_DIR="$(cd "$TARGET_DIR" && pwd -P)"
backup_base="$TARGET_DIR/.claude-backup-$(date +%Y%m%d-%H%M%S)"
BACKUP_DIR="$backup_base"
backup_suffix=1
while [[ -e "$BACKUP_DIR" || -L "$BACKUP_DIR" ]]; do
BACKUP_DIR="$backup_base-$backup_suffix"
backup_suffix=$((backup_suffix + 1))
done
# Snapshot which package paths already exist. Complete uninstall later removes
# only files this installation actually created and that remain unmodified.
OWNERSHIP_BEGIN_ARGS=(
begin
--source "$SCRIPT_DIR"
--target "$TARGET_DIR"
--cli "$CLI_TARGETS"
--hooks "$HOOK_MODE"
)
if [[ $UPDATE_MODE -eq 1 ]]; then
OWNERSHIP_BEGIN_ARGS+=(--update)
fi
python3 "$SCRIPT_DIR/scripts/install-ownership.py" "${OWNERSHIP_BEGIN_ARGS[@]}"
# Backup existing .claude directory
if [[ -d "$TARGET_DIR/.claude" ]]; then
echo "Detected existing .claude directory, creating backup..."
cp -r "$TARGET_DIR/.claude" "$BACKUP_DIR"
echo " Backup location: $BACKUP_DIR"
echo ""
fi
# An explicit guidance-only update is also a mode migration. Remove only hook
# registrations whose commands point at Director Mode paths, then remove only
# manifest-owned hook assets whose recorded digest is unchanged. User hooks and
# locally modified Director hook files are preserved.
if [[ $UPDATE_MODE -eq 1 && "$HOOK_MODE" == "none" ]]; then
echo "Removing Director Mode hooks for guidance-only update..."
python3 "$SCRIPT_DIR/scripts/director-hooks.py" prune --target "$TARGET_DIR"
if [[ -f "$TARGET_DIR/.director-mode/install-ownership.json" ]]; then
python3 "$SCRIPT_DIR/scripts/install-ownership.py" remove \
--target "$TARGET_DIR" --hooks-only
else
echo " No ownership manifest; preserved hook assets and removed registrations only."
fi
echo ""
fi
# Ensure .claude directory exists
mkdir -p "$TARGET_DIR/.claude"
# Copy agents (skip existing, or overwrite in --update mode)
echo "Installing agents..."
mkdir -p "$TARGET_DIR/.claude/agents"
for file in "$SCRIPT_DIR/agents/"*.md; do
if [[ -f "$file" ]]; then
filename=$(basename "$file")
if [[ -f "$TARGET_DIR/.claude/agents/$filename" ]]; then
if [[ $UPDATE_MODE -eq 1 ]]; then
cp -f "$file" "$TARGET_DIR/.claude/agents/"
echo " Updated: agents/$filename"
else
echo " Skipped (exists): agents/$filename"
fi
else
cp "$file" "$TARGET_DIR/.claude/agents/"
echo " Installed: agents/$filename"
fi
fi
done
# Copy skills (skip existing, or overwrite in --update mode)
echo "Installing skills..."
mkdir -p "$TARGET_DIR/.claude/skills"
for dir in "$SCRIPT_DIR/skills/"*/; do
if [[ -d "$dir" ]]; then
dirname=$(basename "$dir")
skill_target="$TARGET_DIR/.claude/skills/$dirname"
if [[ -d "$skill_target" ]]; then
if [[ $UPDATE_MODE -eq 1 ]]; then
# Merge the shipped files instead of replacing the directory;
# user-added references, scripts, and notes remain untouched.
cp -R "${dir%/}/." "$skill_target/"
echo " Updated: skills/$dirname/"
else
echo " Skipped (exists): skills/$dirname/"
fi
else
mkdir -p "$skill_target"
cp -R "${dir%/}/." "$skill_target/"
echo " Installed: skills/$dirname/"
fi
fi
done
manifest_owns() {
local relative="$1"
python3 "$SCRIPT_DIR/scripts/install-ownership.py" owns \
--target "$TARGET_DIR" --path "$relative" >/dev/null 2>&1
}
# Legacy hook automation is intentionally absent from a guidance-first install.
# The portable installer below owns the optional advisory hook.
if [[ "$HOOK_MODE" == "automation" ]]; then
echo "Installing legacy Claude automation hooks..."
mkdir -p "$TARGET_DIR/.claude/hooks"
# List of all hook scripts to install
# Note: log-bash-event.sh replaces separate log-test-result.sh and log-commit.sh
# to avoid stdin conflicts (only one hook can read stdin)
HOOK_SCRIPTS=(
"_lib-changelog.sh"
"auto-loop-stop.sh"
"log-bash-event.sh"
"log-file-change.sh"
"pre-tool-validator.sh"
)
for hook in "${HOOK_SCRIPTS[@]}"; do
if [[ -f "$SCRIPT_DIR/hooks/$hook" ]]; then
hook_relative=".claude/hooks/$hook"
hook_target="$TARGET_DIR/$hook_relative"
if [[ -e "$hook_target" ]] && ! manifest_owns "$hook_relative"; then
echo " Warning: Preserved user-owned hook conflict: $hook_relative"
continue
fi
cp -f "$SCRIPT_DIR/hooks/$hook" "$hook_target"
chmod +x "$hook_target"
echo " Installed: hooks/$hook"
fi
done
# Remove deprecated/renamed hooks
DEPRECATED_HOOKS=(
"log-commit.sh"
"log-test-result.sh"
"changelog-logger.sh"
)
for deprecated in "${DEPRECATED_HOOKS[@]}"; do
deprecated_relative=".claude/hooks/$deprecated"
deprecated_target="$TARGET_DIR/$deprecated_relative"
if [[ -e "$deprecated_target" ]]; then
if manifest_owns "$deprecated_relative"; then
rm -f "$deprecated_target"
echo " Removed deprecated: hooks/$deprecated"
else
echo " Warning: Preserved user-owned deprecated hook conflict: $deprecated_relative"
fi
fi
done
# Handle settings.local.json merging (Claude Code reads hooks from here)
# IMPORTANT: This preserves all existing user settings and only adds/merges hooks
# Uses $CLAUDE_PROJECT_DIR for portable paths (resolved at runtime by Claude Code)
echo "Configuring hooks in settings.local.json..."
# Use Python to safely merge hooks into existing settings
if TARGET_DIR="$TARGET_DIR" SCRIPT_DIR="$SCRIPT_DIR" \
ENABLE_STOP_AUTOLOOP="$ENABLE_STOP_AUTOLOOP" ENABLE_EVOLVING_LOOP="$ENABLE_EVOLVING_LOOP" \
ENABLE_CHANGELOG="$ENABLE_CHANGELOG" ENABLE_VALIDATOR="$ENABLE_VALIDATOR" \
python3 -c "
import json
import os
target_dir = os.path.abspath(os.environ['TARGET_DIR'])
settings_file = os.path.join(target_dir, '.claude', 'settings.local.json')
# Read existing settings (or create empty)
existing = {}
if os.path.exists(settings_file):
with open(settings_file, 'r') as f:
existing = json.load(f)
# Read source hooks (uses \$CLAUDE_PROJECT_DIR for portable paths)
source_file = os.path.join(os.environ['SCRIPT_DIR'], 'hooks', 'settings-hooks.json')
with open(source_file, 'r') as f:
source = json.load(f)
existing.setdefault('hooks', {})
enable_stop = os.environ.get('ENABLE_STOP_AUTOLOOP', '1') == '1'
enable_post = os.environ.get('ENABLE_CHANGELOG', '1') == '1'
enable_pre = os.environ.get('ENABLE_VALIDATOR', '1') == '1'
enable_evolving = os.environ.get('ENABLE_EVOLVING_LOOP', '0') == '1'
if enable_stop and 'Stop' in source.get('hooks', {}) and 'Stop' not in existing['hooks']:
existing['hooks']['Stop'] = source['hooks']['Stop']
print(' Merged: Stop hook (Auto-Loop TDD continuation)')
if enable_post and 'PostToolUse' in source.get('hooks', {}) and 'PostToolUse' not in existing['hooks']:
existing['hooks']['PostToolUse'] = source['hooks']['PostToolUse']
print(' Merged: PostToolUse hooks (changelog)')
if enable_pre and 'PreToolUse' in source.get('hooks', {}) and 'PreToolUse' not in existing['hooks']:
existing['hooks']['PreToolUse'] = source['hooks']['PreToolUse']
print(' Merged: PreToolUse hooks (file validation)')
# Evolving-Loop hooks ship separately under .self-evolving-loop/ and are only
# merged here when explicitly requested (wizard automation level 2) — see
# the evolving-loop skill's 'Hook-Driven Continuation' section for the
# equivalent manual activation snippet.
if enable_evolving:
evolving_source_file = os.path.join(os.environ['SCRIPT_DIR'], '.self-evolving-loop', 'hooks', 'settings-hooks.json')
if os.path.exists(evolving_source_file):
with open(evolving_source_file, 'r') as f:
evolving_source = json.load(f)
merged_any = False
for event, entries in evolving_source.get('hooks', {}).items():
existing_list = existing['hooks'].setdefault(event, [])
for entry in entries:
if entry not in existing_list:
existing_list.append(entry)
merged_any = True
if merged_any:
print(' Merged: Evolving-Loop hooks (self-evolution continuation)')
# Add plansDirectory setting
if 'plansDirectory' not in existing:
existing['plansDirectory'] = '.claude/plans'
print(' Added: plansDirectory setting')
# Write settings
os.makedirs(os.path.dirname(settings_file), exist_ok=True)
with open(settings_file, 'w') as f:
json.dump(existing, f, indent=2)
print(' settings.local.json configured')
"; then
: # Success, Python already printed status
else
echo " Warning: Could not auto-configure hooks (exit code: $?)"
echo " Please manually add hooks from: $SCRIPT_DIR/hooks/settings-hooks.json"
fi
# Copy .self-evolving-loop scaffolding (evolving-loop hooks + templates, opt-in)
# Only hooks/ and templates/ are distributed; state dirs are created at runtime.
# The Stop hook is only merged into settings.local.json above when
# ENABLE_EVOLVING_LOOP=1 (--wizard automation level 2); otherwise it stays
# opt-in — see the evolving-loop skill's "Hook-Driven Continuation" section
# for the manual activation snippet.
echo "Installing .self-evolving-loop scaffolding..."
SEL_SRC="$SCRIPT_DIR/.self-evolving-loop"
if [[ -d "$SEL_SRC" ]]; then
for sub in hooks templates; do
mkdir -p "$TARGET_DIR/.self-evolving-loop/$sub"
for file in "$SEL_SRC/$sub/"*; do
[[ -f "$file" ]] || continue
filename=$(basename "$file")
dest="$TARGET_DIR/.self-evolving-loop/$sub/$filename"
if [[ -f "$dest" ]] && [[ $UPDATE_MODE -eq 0 ]]; then
echo " Skipped (exists): .self-evolving-loop/$sub/$filename"
else
if [[ -f "$dest" ]]; then
cp -f "$file" "$dest"
echo " Updated: .self-evolving-loop/$sub/$filename"
else
cp "$file" "$dest"
echo " Installed: .self-evolving-loop/$sub/$filename"
fi
if [[ "$filename" == *.sh ]]; then
chmod +x "$dest"
fi
fi
done
done
fi
else
echo "Skipping legacy Auto-Loop, changelog, validator, and evolving-loop hooks."
fi
# Copy CLAUDE.md template (if target doesn't have one)
if [[ ! -f "$TARGET_DIR/CLAUDE.md" ]] && [[ -f "$SCRIPT_DIR/docs/CLAUDE-TEMPLATE.md" ]]; then
echo "Copying CLAUDE.md template..."
cp "$SCRIPT_DIR/docs/CLAUDE-TEMPLATE.md" "$TARGET_DIR/CLAUDE.md"
fi
# Install the shared guidance, portable relay, and native adapters. Optional
# guide hooks are registered only for CLIs that can consume their context.
PORTABLE_ARGS=(
--source "$SCRIPT_DIR"
--target "$TARGET_DIR"
--cli "$CLI_TARGETS"
--hooks "$HOOK_MODE"
)
if [[ $UPDATE_MODE -eq 1 ]]; then
PORTABLE_ARGS+=(--update)
fi
echo "Installing cross-CLI guidance and adapters..."
python3 "$SCRIPT_DIR/scripts/install-portable.py" "${PORTABLE_ARGS[@]}"
python3 "$SCRIPT_DIR/scripts/install-ownership.py" finalize --target "$TARGET_DIR"
echo ""
if [[ $UPDATE_MODE -eq 1 ]]; then
echo "Update complete!"
else
echo "Installation complete!"
fi
echo ""
echo "Installed:"
echo " - .director-mode/ (shared guidance + portable session relay)"
echo " - director-open (native full-capability launcher)"
echo " - .claude/skills/ (Claude Code + Grok-compatible skills)"
echo " - .claude/agents/ (14 specialized agents)"
if [[ "$CLI_TARGETS" == "all" || "$CLI_TARGETS" == "codex" ]]; then
echo " - .agents/skills/ (Codex-compatible shared skills)"
echo " - .codex/agents/ (14 generated Codex agent adapters)"
fi
if [[ "$CLI_TARGETS" == "all" || "$CLI_TARGETS" == "grok" ]]; then
echo " - .grok/agents/ (14 generated Grok agent adapters)"
fi
echo ""
echo "Guidance and automation:"
if [[ "$HOOK_MODE" == "guide" ]]; then
echo " - Claude/Codex context hook ON (never denies; no Grok inert hook)"
elif [[ "$HOOK_MODE" == "none" ]]; then
echo " - Active hooks off (zero-interference default)"
fi
if [[ $ENABLE_STOP_AUTOLOOP -eq 1 ]]; then
echo " - Legacy Auto-Loop hook ON (explicit opt-in)"
else
echo " - Legacy Auto-Loop hook off"
fi
if [[ $ENABLE_EVOLVING_LOOP -eq 1 ]]; then
echo " - Evolving-Loop Stop hook ON (self-evolution continuation)"
else
echo " - Evolving-Loop Stop hook off (opt-in — see /evolving-loop)"
fi
if [[ $ENABLE_CHANGELOG -eq 1 ]]; then
echo " - Changelog hooks ON"
else
echo " - Changelog hooks off"
fi
if [[ $ENABLE_VALIDATOR -eq 1 ]]; then
echo " - Pre-write validator hook ON"
else
echo " - Pre-write validator hook off"
fi
if [[ $WIZARD_MODE -eq 1 && -t 0 ]]; then
echo " (project type: $PROJECT_TYPE — re-run ./install.sh --update --wizard anytime to change)"
fi
echo ""
if [[ -d "$BACKUP_DIR" ]]; then
echo "Backup location: $BACKUP_DIR"
echo ""
fi
echo "Get started:"
echo " cd $TARGET_DIR"
echo " claude | codex | grok"
echo " .director-mode/bin/director-open claude|codex|grok # trusted full access"
echo " Read .director-mode/GUIDANCE.md"
echo " Use the director-mode or session-relay skill"
echo ""