claude-team-mcp 0.3.1__tar.gz → 0.3.2__tar.gz
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.
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/beads.db-shm +0 -0
- claude_team_mcp-0.3.2/.beads/beads.db-wal +0 -0
- claude_team_mcp-0.3.2/HAPPY_INTEGRATION_RESEARCH.md +187 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/PKG-INFO +1 -1
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/pyproject.toml +1 -1
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/iterm_utils.py +10 -1
- claude_team_mcp-0.3.2/tests/test_iterm_utils.py +136 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/uv.lock +1 -1
- claude_team_mcp-0.3.1/.beads/beads.db-wal +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/.gitignore +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/.local_version +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/README.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/beads.db +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/config.yaml +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/daemon.lock +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/daemon.log +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/daemon.pid +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/deletions.jsonl +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/deletions.jsonl.migrated +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/issues.jsonl +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.beads/metadata.json +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.claude/settings.local.json +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.claude-plugin/marketplace.json +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.claude-plugin/plugin.json +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.gitattributes +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.gitignore +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/.mcp.json +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/CLAUDE.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/Makefile +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/NOTES.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/README.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/commands/check-workers.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/commands/cleanup-worktrees.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/commands/merge-worker.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/commands/pr-worker.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/commands/spawn-workers.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/commands/team-summary.md +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/scripts/install-commands.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/settings.json +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/__init__.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/__main__.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/colors.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/formatting.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/idle_detection.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/names.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/profile.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/registry.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/server.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/session_state.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/subprocess_cache.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/__init__.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/adopt_worker.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/annotate_worker.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/bd_help.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/check_idle_workers.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/close_workers.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/discover_workers.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/examine_worker.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/list_workers.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/list_worktrees.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/message_workers.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/read_worker_logs.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/spawn_workers.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/wait_idle_workers.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/utils/__init__.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/utils/constants.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/utils/errors.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/utils/worktree_detection.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/worker_prompt.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/worktree.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/tests/__init__.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/tests/test_colors.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/tests/test_formatting.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/tests/test_idle_detection.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/tests/test_names.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/tests/test_registry.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/tests/test_session_state.py +0 -0
- {claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/tests/test_worker_prompt.py +0 -0
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Happy Integration Research
|
|
2
|
+
|
|
3
|
+
## Problem Statement
|
|
4
|
+
|
|
5
|
+
When `CLAUDE_TEAM_COMMAND=happy` is set, sessions spawn and work in iTerm2 but Happy mobile shows them as empty. Running `happy` directly from terminal works fine.
|
|
6
|
+
|
|
7
|
+
## Root Cause: `--settings` Flag Conflict
|
|
8
|
+
|
|
9
|
+
### How claude-team spawns sessions
|
|
10
|
+
|
|
11
|
+
In `src/claude_team_mcp/iterm_utils.py:545-552`:
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
claude_cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
|
|
15
|
+
if dangerously_skip_permissions:
|
|
16
|
+
claude_cmd += " --dangerously-skip-permissions"
|
|
17
|
+
if stop_hook_marker_id:
|
|
18
|
+
settings_file = build_stop_hook_settings_file(stop_hook_marker_id)
|
|
19
|
+
claude_cmd += f" --settings {settings_file}"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This creates a command like:
|
|
23
|
+
```
|
|
24
|
+
happy --dangerously-skip-permissions --settings ~/.claude/claude-team-settings/worker-xxx.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Claude-team's settings file** (`~/.claude/claude-team-settings/worker-xxx.json`):
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"hooks": {
|
|
31
|
+
"Stop": [{
|
|
32
|
+
"hooks": [{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "echo [worker-done:xxx]"
|
|
35
|
+
}]
|
|
36
|
+
}]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### How Happy processes the command
|
|
42
|
+
|
|
43
|
+
When Happy receives `--settings <file>`, it treats it as an unknown arg and passes it through to Claude via `options.claudeArgs`. Then in `dist/index-B3gQr6vs.mjs:311-314`:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
if (opts.claudeArgs) {
|
|
47
|
+
args.push(...opts.claudeArgs); // Includes claude-team's --settings
|
|
48
|
+
}
|
|
49
|
+
args.push("--settings", opts.hookSettingsPath); // Happy's own --settings
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Happy's settings file** (`~/.happy/tmp/hooks/session-hook-<pid>.json`):
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"hooks": {
|
|
56
|
+
"SessionStart": [{
|
|
57
|
+
"matcher": "*",
|
|
58
|
+
"hooks": [{
|
|
59
|
+
"type": "command",
|
|
60
|
+
"command": "node \".../session_hook_forwarder.cjs\" <port>"
|
|
61
|
+
}]
|
|
62
|
+
}]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### The Conflict
|
|
68
|
+
|
|
69
|
+
Claude receives TWO `--settings` flags:
|
|
70
|
+
```
|
|
71
|
+
claude ... --settings <claude-team-file> --settings <happy-file>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Claude likely only uses the **first** `--settings` file (standard CLI behavior), meaning:
|
|
75
|
+
- Claude has the **Stop hook** (claude-team's) - for idle detection
|
|
76
|
+
- Claude does **NOT** have the **SessionStart hook** (Happy's) - for session tracking
|
|
77
|
+
|
|
78
|
+
### Why Happy mobile shows empty sessions
|
|
79
|
+
|
|
80
|
+
Happy's session tracking relies on the `SessionStart` hook:
|
|
81
|
+
1. Hook fires when Claude starts
|
|
82
|
+
2. Hook script POSTs session ID to Happy's local hook server (port varies per session)
|
|
83
|
+
3. Happy daemon receives session ID and updates session metadata
|
|
84
|
+
4. Mobile app displays session
|
|
85
|
+
|
|
86
|
+
Without the SessionStart hook firing, Happy never learns the Claude session ID, so the session appears empty in the mobile app.
|
|
87
|
+
|
|
88
|
+
## Key Files
|
|
89
|
+
|
|
90
|
+
| File | Purpose |
|
|
91
|
+
|------|---------|
|
|
92
|
+
| `src/claude_team_mcp/iterm_utils.py:459-500` | `build_stop_hook_settings_file()` creates Stop hook settings |
|
|
93
|
+
| `src/claude_team_mcp/iterm_utils.py:545-552` | Command building that adds `--settings` |
|
|
94
|
+
| Happy `dist/index-B3gQr6vs.mjs:311-314` | Args processing that adds Happy's `--settings` |
|
|
95
|
+
| Happy `dist/index-B3gQr6vs.mjs:4928-4953` | `generateHookSettingsFile()` creates SessionStart hook settings |
|
|
96
|
+
| Happy `dist/index-B3gQr6vs.mjs:4861-4927` | Hook server that receives session notifications |
|
|
97
|
+
| Happy `scripts/session_hook_forwarder.cjs` | Script executed by SessionStart hook |
|
|
98
|
+
|
|
99
|
+
## Solution Options
|
|
100
|
+
|
|
101
|
+
### Option A: Don't add `--settings` when using Happy
|
|
102
|
+
|
|
103
|
+
When `CLAUDE_TEAM_COMMAND=happy`, skip adding the `--settings` flag entirely:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
claude_cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
|
|
107
|
+
if dangerously_skip_permissions:
|
|
108
|
+
claude_cmd += " --dangerously-skip-permissions"
|
|
109
|
+
|
|
110
|
+
# Only add stop hook settings when NOT using happy
|
|
111
|
+
if stop_hook_marker_id and claude_cmd == "claude":
|
|
112
|
+
settings_file = build_stop_hook_settings_file(stop_hook_marker_id)
|
|
113
|
+
claude_cmd += f" --settings {settings_file}"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Tradeoff:** Loses idle detection for Happy-spawned workers. Claude-team would need an alternative mechanism.
|
|
117
|
+
|
|
118
|
+
### Option B: Merge hooks into one settings file
|
|
119
|
+
|
|
120
|
+
Create a combined settings file that includes BOTH hooks:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
def build_merged_settings_file(marker_id: str, happy_port: int) -> str:
|
|
124
|
+
settings = {
|
|
125
|
+
"hooks": {
|
|
126
|
+
"Stop": [{
|
|
127
|
+
"hooks": [{
|
|
128
|
+
"type": "command",
|
|
129
|
+
"command": f"echo [worker-done:{marker_id}]"
|
|
130
|
+
}]
|
|
131
|
+
}],
|
|
132
|
+
"SessionStart": [{
|
|
133
|
+
"matcher": "*",
|
|
134
|
+
"hooks": [{
|
|
135
|
+
"type": "command",
|
|
136
|
+
"command": f"node /path/to/session_hook_forwarder.cjs {happy_port}"
|
|
137
|
+
}]
|
|
138
|
+
}]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
# ...
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Tradeoff:** Requires knowing Happy's hook server port, which is dynamic and only known after Happy starts its hook server.
|
|
145
|
+
|
|
146
|
+
### Option C: Coordinate with Happy's hook mechanism
|
|
147
|
+
|
|
148
|
+
Have claude-team discover Happy's hook port and integrate with it, or use Happy's daemon API directly for session tracking.
|
|
149
|
+
|
|
150
|
+
**Tradeoff:** Complex integration, tight coupling with Happy internals.
|
|
151
|
+
|
|
152
|
+
### Option D: Request Happy feature - external settings support
|
|
153
|
+
|
|
154
|
+
Ask Happy to support inheriting/merging external `--settings` files instead of overwriting:
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
// In Happy's args processing
|
|
158
|
+
if (arg === "--settings") {
|
|
159
|
+
options.externalSettings = args[++i]; // Store for later merging
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// When building Claude args
|
|
163
|
+
const mergedSettings = mergeSettings(opts.externalSettings, opts.hookSettingsPath);
|
|
164
|
+
args.push("--settings", mergedSettings);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Tradeoff:** Requires Happy changes, but cleanest solution.
|
|
168
|
+
|
|
169
|
+
## Recommended Approach
|
|
170
|
+
|
|
171
|
+
**Short-term (Option A):** Detect when using Happy and skip the `--settings` flag. Accept the loss of Stop-hook-based idle detection. Use an alternative like polling the JSONL for assistant message patterns.
|
|
172
|
+
|
|
173
|
+
**Long-term (Option D):** Request Happy add support for merging external settings files, or document how to integrate with Happy's session tracking API directly.
|
|
174
|
+
|
|
175
|
+
## Testing Verification
|
|
176
|
+
|
|
177
|
+
To verify the root cause:
|
|
178
|
+
1. Run `happy` directly from terminal - check that SessionStart hook fires (session shows in mobile)
|
|
179
|
+
2. Run via claude-team with `CLAUDE_TEAM_COMMAND=happy` - SessionStart hook should NOT fire
|
|
180
|
+
3. Temporarily remove claude-team's `--settings` flag and respawn - session should appear in mobile
|
|
181
|
+
|
|
182
|
+
## Additional Notes
|
|
183
|
+
|
|
184
|
+
- Happy's daemon runs in background and must be started for session tracking
|
|
185
|
+
- Happy auto-starts daemon if not running (line 6469-6478)
|
|
186
|
+
- Session metadata includes `startedBy: "terminal"` vs `"daemon"` - may affect visibility
|
|
187
|
+
- Happy's hook server port is ephemeral (assigned at runtime)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-team-mcp
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: MCP server for managing multiple Claude Code sessions via iTerm2
|
|
5
5
|
Project-URL: Homepage, https://github.com/Martian-Engineering/claude-team
|
|
6
6
|
Project-URL: Repository, https://github.com/Martian-Engineering/claude-team
|
|
@@ -545,9 +545,18 @@ async def start_claude_in_session(
|
|
|
545
545
|
# Build claude command with flags
|
|
546
546
|
# Allow overriding the claude command via environment variable (e.g., "happy")
|
|
547
547
|
claude_cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
|
|
548
|
+
is_default_claude_command = claude_cmd == "claude"
|
|
549
|
+
|
|
548
550
|
if dangerously_skip_permissions:
|
|
549
551
|
claude_cmd += " --dangerously-skip-permissions"
|
|
550
|
-
|
|
552
|
+
|
|
553
|
+
# Only add --settings for the default 'claude' command.
|
|
554
|
+
# Custom commands like 'happy' have their own session tracking mechanisms
|
|
555
|
+
# (e.g., Happy uses a SessionStart hook for mobile app integration).
|
|
556
|
+
# Adding our --settings flag conflicts with theirs because Claude only
|
|
557
|
+
# uses the first --settings file, breaking their session tracking.
|
|
558
|
+
# See HAPPY_INTEGRATION_RESEARCH.md for full analysis.
|
|
559
|
+
if stop_hook_marker_id and is_default_claude_command:
|
|
551
560
|
settings_file = build_stop_hook_settings_file(stop_hook_marker_id)
|
|
552
561
|
claude_cmd += f" --settings {settings_file}"
|
|
553
562
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Tests for iterm_utils module."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from claude_team_mcp.iterm_utils import build_stop_hook_settings_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestClaudeCommandBuilding:
|
|
12
|
+
"""Tests for Claude command building logic in start_claude_in_session.
|
|
13
|
+
|
|
14
|
+
These tests verify the --settings flag behavior based on CLAUDE_TEAM_COMMAND.
|
|
15
|
+
The actual start_claude_in_session function is async and requires iTerm2,
|
|
16
|
+
so we test the command building logic by examining the key conditions.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def test_default_claude_command_gets_settings(self):
|
|
20
|
+
"""Default 'claude' command should get --settings flag for idle detection."""
|
|
21
|
+
# Simulate the logic from start_claude_in_session
|
|
22
|
+
with patch.dict(os.environ, {}, clear=False):
|
|
23
|
+
# Remove CLAUDE_TEAM_COMMAND if present
|
|
24
|
+
os.environ.pop("CLAUDE_TEAM_COMMAND", None)
|
|
25
|
+
|
|
26
|
+
claude_cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
|
|
27
|
+
is_default_claude_command = claude_cmd == "claude"
|
|
28
|
+
|
|
29
|
+
assert claude_cmd == "claude"
|
|
30
|
+
assert is_default_claude_command is True
|
|
31
|
+
|
|
32
|
+
# With a stop_hook_marker_id, --settings should be added
|
|
33
|
+
stop_hook_marker_id = "test-marker-123"
|
|
34
|
+
if stop_hook_marker_id and is_default_claude_command:
|
|
35
|
+
settings_file = build_stop_hook_settings_file(stop_hook_marker_id)
|
|
36
|
+
claude_cmd += f" --settings {settings_file}"
|
|
37
|
+
|
|
38
|
+
assert "--settings" in claude_cmd
|
|
39
|
+
assert "test-marker-123" in settings_file
|
|
40
|
+
|
|
41
|
+
def test_custom_command_skips_settings(self):
|
|
42
|
+
"""Custom commands like 'happy' should NOT get --settings flag.
|
|
43
|
+
|
|
44
|
+
Custom commands have their own session tracking mechanisms.
|
|
45
|
+
Adding --settings conflicts with them (e.g., Happy's SessionStart hook).
|
|
46
|
+
"""
|
|
47
|
+
with patch.dict(os.environ, {"CLAUDE_TEAM_COMMAND": "happy"}):
|
|
48
|
+
claude_cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
|
|
49
|
+
is_default_claude_command = claude_cmd == "claude"
|
|
50
|
+
|
|
51
|
+
assert claude_cmd == "happy"
|
|
52
|
+
assert is_default_claude_command is False
|
|
53
|
+
|
|
54
|
+
# With a stop_hook_marker_id, --settings should NOT be added
|
|
55
|
+
stop_hook_marker_id = "test-marker-123"
|
|
56
|
+
if stop_hook_marker_id and is_default_claude_command:
|
|
57
|
+
settings_file = build_stop_hook_settings_file(stop_hook_marker_id)
|
|
58
|
+
claude_cmd += f" --settings {settings_file}"
|
|
59
|
+
|
|
60
|
+
assert "--settings" not in claude_cmd
|
|
61
|
+
|
|
62
|
+
def test_custom_command_with_path_skips_settings(self):
|
|
63
|
+
"""Custom commands specified as paths should also skip --settings."""
|
|
64
|
+
with patch.dict(os.environ, {"CLAUDE_TEAM_COMMAND": "/usr/local/bin/happy"}):
|
|
65
|
+
claude_cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
|
|
66
|
+
is_default_claude_command = claude_cmd == "claude"
|
|
67
|
+
|
|
68
|
+
assert claude_cmd == "/usr/local/bin/happy"
|
|
69
|
+
assert is_default_claude_command is False
|
|
70
|
+
|
|
71
|
+
stop_hook_marker_id = "test-marker-123"
|
|
72
|
+
if stop_hook_marker_id and is_default_claude_command:
|
|
73
|
+
settings_file = build_stop_hook_settings_file(stop_hook_marker_id)
|
|
74
|
+
claude_cmd += f" --settings {settings_file}"
|
|
75
|
+
|
|
76
|
+
assert "--settings" not in claude_cmd
|
|
77
|
+
|
|
78
|
+
def test_dangerously_skip_permissions_still_added(self):
|
|
79
|
+
"""--dangerously-skip-permissions should be added regardless of command."""
|
|
80
|
+
# Test with default claude
|
|
81
|
+
with patch.dict(os.environ, {}, clear=False):
|
|
82
|
+
os.environ.pop("CLAUDE_TEAM_COMMAND", None)
|
|
83
|
+
claude_cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
|
|
84
|
+
|
|
85
|
+
dangerously_skip_permissions = True
|
|
86
|
+
if dangerously_skip_permissions:
|
|
87
|
+
claude_cmd += " --dangerously-skip-permissions"
|
|
88
|
+
|
|
89
|
+
assert "--dangerously-skip-permissions" in claude_cmd
|
|
90
|
+
|
|
91
|
+
# Test with custom command
|
|
92
|
+
with patch.dict(os.environ, {"CLAUDE_TEAM_COMMAND": "happy"}):
|
|
93
|
+
claude_cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
|
|
94
|
+
|
|
95
|
+
dangerously_skip_permissions = True
|
|
96
|
+
if dangerously_skip_permissions:
|
|
97
|
+
claude_cmd += " --dangerously-skip-permissions"
|
|
98
|
+
|
|
99
|
+
assert "--dangerously-skip-permissions" in claude_cmd
|
|
100
|
+
assert claude_cmd == "happy --dangerously-skip-permissions"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestBuildStopHookSettingsFile:
|
|
104
|
+
"""Tests for the stop hook settings file builder."""
|
|
105
|
+
|
|
106
|
+
def test_creates_valid_settings_file(self):
|
|
107
|
+
"""Settings file should contain Stop hook with marker."""
|
|
108
|
+
import json
|
|
109
|
+
from pathlib import Path
|
|
110
|
+
|
|
111
|
+
marker_id = "test-abc123"
|
|
112
|
+
settings_path = build_stop_hook_settings_file(marker_id)
|
|
113
|
+
|
|
114
|
+
# Verify file exists and is valid JSON
|
|
115
|
+
settings_file = Path(settings_path)
|
|
116
|
+
assert settings_file.exists()
|
|
117
|
+
|
|
118
|
+
content = json.loads(settings_file.read_text())
|
|
119
|
+
assert "hooks" in content
|
|
120
|
+
assert "Stop" in content["hooks"]
|
|
121
|
+
|
|
122
|
+
# Verify marker is in the command
|
|
123
|
+
stop_hooks = content["hooks"]["Stop"]
|
|
124
|
+
assert len(stop_hooks) > 0
|
|
125
|
+
command = stop_hooks[0]["hooks"][0]["command"]
|
|
126
|
+
assert marker_id in command
|
|
127
|
+
|
|
128
|
+
def test_settings_file_in_expected_location(self):
|
|
129
|
+
"""Settings file should be in ~/.claude/claude-team-settings/."""
|
|
130
|
+
from pathlib import Path
|
|
131
|
+
|
|
132
|
+
marker_id = "location-test-456"
|
|
133
|
+
settings_path = build_stop_hook_settings_file(marker_id)
|
|
134
|
+
|
|
135
|
+
expected_dir = Path.home() / ".claude" / "claude-team-settings"
|
|
136
|
+
assert settings_path.startswith(str(expected_dir))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/annotate_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/check_idle_workers.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/discover_workers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/message_workers.py
RENAMED
|
File without changes
|
{claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/read_worker_logs.py
RENAMED
|
File without changes
|
|
File without changes
|
{claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/tools/wait_idle_workers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_team_mcp-0.3.1 → claude_team_mcp-0.3.2}/src/claude_team_mcp/utils/worktree_detection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|