From a9a0a53d0a398959ae0d11879869fbb6b8910a56 Mon Sep 17 00:00:00 2001 From: Arjun Date: Mon, 3 Aug 2026 18:39:23 +0100 Subject: [PATCH] feat: support ZO-scoped Claude settings at ~/.zo/settings.json Avoid requiring users to edit their global ~/.claude/settings.json to enable agent teams. If ~/.zo/settings.json exists (overridable via ZO_CLAUDE_SETTINGS), the wrapper passes it to every spawned Claude session via --settings, in both the tmux and headless launch paths. setup.sh now accepts the flag in either location and its auto-fixer writes the ZO-scoped file instead of modifying global settings. Co-Authored-By: Claude Fable 5 --- setup.sh | 64 ++++++++++++++++++++++------------------------- src/zo/wrapper.py | 22 ++++++++++++++++ 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/setup.sh b/setup.sh index b84c8a2..385d768 100755 --- a/setup.sh +++ b/setup.sh @@ -60,19 +60,17 @@ else fixable "claude" fi -# 4. Agent teams enabled +# 4. Agent teams enabled — ZO-scoped file preferred, global settings accepted echo -e "${DIM}Checking agent teams...${RESET}" SETTINGS_FILE="$HOME/.claude/settings.json" -if [[ -f "$SETTINGS_FILE" ]]; then - if grep -q "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" "$SETTINGS_FILE" 2>/dev/null; then - pass "Agent teams enabled in global settings" - else - fail "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS not set in $SETTINGS_FILE" - fixable "agent-teams-env" - fi +ZO_SETTINGS_FILE="${ZO_CLAUDE_SETTINGS:-$HOME/.zo/settings.json}" +if grep -q "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" "$ZO_SETTINGS_FILE" 2>/dev/null; then + pass "Agent teams enabled in ZO settings ($ZO_SETTINGS_FILE)" +elif grep -q "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" "$SETTINGS_FILE" 2>/dev/null; then + pass "Agent teams enabled in global settings" else - fail "Global settings not found at $SETTINGS_FILE" - fixable "global-settings" + fail "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS not set in $ZO_SETTINGS_FILE or $SETTINGS_FILE" + fixable "agent-teams-env" fi # 5. Project settings.json @@ -208,17 +206,34 @@ fix_item() { echo -e " ${RED}✗${RESET} Claude CLI install failed — try manually: curl -fsSL https://claude.ai/install.sh | bash" return 1 ;; - global-settings) - echo -e " ${AMBER}→${RESET} Creating global settings at $SETTINGS_FILE..." - mkdir -p "$HOME/.claude" - cat > "$SETTINGS_FILE" << 'SETTINGS_EOF' + agent-teams-env) + echo -e " ${AMBER}→${RESET} Enabling agent teams in $ZO_SETTINGS_FILE..." + mkdir -p "$(dirname "$ZO_SETTINGS_FILE")" + if [[ -f "$ZO_SETTINGS_FILE" ]]; then + # Merge into existing ZO settings without clobbering other keys + if python3 -c " +import json +with open('$ZO_SETTINGS_FILE') as f: + data = json.load(f) +data.setdefault('env', {})['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] = '1' +with open('$ZO_SETTINGS_FILE', 'w') as f: + json.dump(data, f, indent=2) + f.write('\n') +" 2>/dev/null; then + echo -e " ${GREEN}✓${RESET} Agent teams env merged into ZO settings" + return 0 + fi + echo -e " ${RED}✗${RESET} Failed to update $ZO_SETTINGS_FILE — add manually" + return 1 + fi + cat > "$ZO_SETTINGS_FILE" << 'SETTINGS_EOF' { "env": { "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" } } SETTINGS_EOF - echo -e " ${GREEN}✓${RESET} Global settings created with agent teams enabled" + echo -e " ${GREEN}✓${RESET} ZO settings created with agent teams enabled" return 0 ;; zo-cli) @@ -247,25 +262,6 @@ SETTINGS_EOF echo -e " ${RED}✗${RESET} zo symlink created but not working — check PATH includes ~/.local/bin" return 1 ;; - agent-teams-env) - echo -e " ${AMBER}→${RESET} Adding agent teams env to $SETTINGS_FILE..." - # Use python to merge the env key into existing settings - if python3 -c " -import json, sys -with open('$SETTINGS_FILE') as f: - data = json.load(f) -data.setdefault('env', {})['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] = '1' -with open('$SETTINGS_FILE', 'w') as f: - json.dump(data, f, indent=2) - f.write('\n') -" 2>/dev/null; then - echo -e " ${GREEN}✓${RESET} Agent teams env added to global settings" - return 0 - else - echo -e " ${RED}✗${RESET} Failed to update settings — add manually" - return 1 - fi - ;; esac } diff --git a/src/zo/wrapper.py b/src/zo/wrapper.py index fe00402..53b5043 100644 --- a/src/zo/wrapper.py +++ b/src/zo/wrapper.py @@ -55,6 +55,20 @@ re.compile(r"too many requests", re.IGNORECASE), ] +_ZO_USER_SETTINGS = Path.home() / ".zo" / "settings.json" + + +def zo_user_settings_path() -> Path | None: + """Return the ZO-scoped Claude settings file, if present. + + ``~/.zo/settings.json`` (override via ``ZO_CLAUDE_SETTINGS``) is passed + to every spawned Claude session via ``--settings``, so ZO-specific + config such as ``CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`` never has to + live in the user's global ``~/.claude/settings.json``. + """ + path = Path(os.environ.get("ZO_CLAUDE_SETTINGS") or _ZO_USER_SETTINGS) + return path if path.is_file() else None + class LifecycleWrapper: """Manages the lifecycle of a Claude Code lead orchestrator session. @@ -229,11 +243,16 @@ def _launch_tmux( env_prefix = "" for k, v in (extra_env or {}).items(): env_prefix += f'{k}={shlex.quote(v)} ' + settings_flag = "" + zo_settings = zo_user_settings_path() + if zo_settings: + settings_flag = f' --settings {shlex.quote(str(zo_settings))}' interactive_cmd = ( f'{env_prefix}' f'{shlex.quote(claude_abs)}' f' --model {shlex.quote(model)}' f' --max-turns {max_turns}' + f'{settings_flag}' f'{add_dir_flags}' ) subprocess.run( @@ -439,6 +458,9 @@ def _launch_headless( "--max-turns", str(max_turns), "--add-dir", cwd, ] + zo_settings = zo_user_settings_path() + if zo_settings: + cmd.extend(["--settings", str(zo_settings)]) if bypass_permissions: from zo.permissions_overlay import ensure_bypass_disclaimer_accepted # --dangerously-skip-permissions / bypass mode refuses to start