claude-team-mcp 0.3.2__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
@@ -0,0 +1,44 @@
1
+ """
2
+ CLI Backends Module.
3
+
4
+ Provides abstraction layer for different agent CLI tools (Claude Code, Codex, etc.)
5
+ This allows claude-team to orchestrate multiple agent types through a unified interface.
6
+ """
7
+
8
+ from .base import AgentCLI
9
+ from .claude import ClaudeCLI, claude_cli
10
+ from .codex import CodexCLI, codex_cli
11
+
12
+ __all__ = [
13
+ "AgentCLI",
14
+ "ClaudeCLI",
15
+ "claude_cli",
16
+ "CodexCLI",
17
+ "codex_cli",
18
+ "get_cli_backend",
19
+ ]
20
+
21
+
22
+ def get_cli_backend(agent_type: str = "claude") -> AgentCLI:
23
+ """
24
+ Get a CLI backend instance by agent type.
25
+
26
+ Args:
27
+ agent_type: The agent type ("claude" or "codex")
28
+
29
+ Returns:
30
+ An AgentCLI implementation instance
31
+
32
+ Raises:
33
+ ValueError: If the agent type is not supported
34
+ """
35
+ backends = {
36
+ "claude": claude_cli,
37
+ "codex": codex_cli,
38
+ }
39
+
40
+ if agent_type not in backends:
41
+ valid = ", ".join(sorted(backends.keys()))
42
+ raise ValueError(f"Unknown agent type: {agent_type}. Valid types: {valid}")
43
+
44
+ return backends[agent_type]
@@ -0,0 +1,132 @@
1
+ """
2
+ Base protocol for CLI agent backends.
3
+
4
+ Defines the interface that all CLI backends (Claude, Codex, etc.) must implement.
5
+ This abstraction allows claude-team to orchestrate different agent CLIs.
6
+ """
7
+
8
+ from abc import abstractmethod
9
+ from typing import Literal, Protocol, runtime_checkable
10
+
11
+
12
+ @runtime_checkable
13
+ class AgentCLI(Protocol):
14
+ """
15
+ Protocol defining the interface for agent CLI backends.
16
+
17
+ Each implementation encapsulates the CLI-specific details:
18
+ - Command and arguments
19
+ - Ready detection patterns
20
+ - Idle/completion detection method
21
+ - Settings/hook injection support
22
+ """
23
+
24
+ @property
25
+ @abstractmethod
26
+ def engine_id(self) -> str:
27
+ """
28
+ Unique identifier for this CLI engine (e.g., "claude", "codex").
29
+
30
+ Used for configuration, logging, and distinguishing between backends.
31
+ """
32
+ ...
33
+
34
+ @abstractmethod
35
+ def command(self) -> str:
36
+ """
37
+ Return the CLI executable name or path.
38
+
39
+ Examples: "claude", "codex", "/usr/local/bin/custom-agent"
40
+ """
41
+ ...
42
+
43
+ @abstractmethod
44
+ def build_args(
45
+ self,
46
+ *,
47
+ dangerously_skip_permissions: bool = False,
48
+ settings_file: str | None = None,
49
+ ) -> list[str]:
50
+ """
51
+ Build the argument list for the CLI command.
52
+
53
+ Args:
54
+ dangerously_skip_permissions: If True, add flag to skip permission prompts
55
+ settings_file: Optional path to settings file for hook injection
56
+
57
+ Returns:
58
+ List of command-line arguments (not including the command itself)
59
+ """
60
+ ...
61
+
62
+ @abstractmethod
63
+ def ready_patterns(self) -> list[str]:
64
+ """
65
+ Return patterns that indicate the CLI is ready for input.
66
+
67
+ These patterns are searched for in terminal output to detect when
68
+ the agent has started and is ready to receive prompts.
69
+
70
+ Returns:
71
+ List of strings to search for in terminal output
72
+ """
73
+ ...
74
+
75
+ @abstractmethod
76
+ def idle_detection_method(self) -> Literal["stop_hook", "jsonl_stream", "none"]:
77
+ """
78
+ Return the method used to detect when the agent finishes responding.
79
+
80
+ - "stop_hook": Uses a Stop hook that fires when the agent completes
81
+ - "jsonl_stream": Monitors JSONL output for completion markers
82
+ - "none": No idle detection available (must use timeouts)
83
+
84
+ Returns:
85
+ The detection method identifier
86
+ """
87
+ ...
88
+
89
+ @abstractmethod
90
+ def supports_settings_file(self) -> bool:
91
+ """
92
+ Return whether this CLI supports --settings flag for hook injection.
93
+
94
+ If False, build_args() should ignore the settings_file parameter.
95
+ """
96
+ ...
97
+
98
+ def build_full_command(
99
+ self,
100
+ *,
101
+ dangerously_skip_permissions: bool = False,
102
+ settings_file: str | None = None,
103
+ env_vars: dict[str, str] | None = None,
104
+ ) -> str:
105
+ """
106
+ Build the complete command string including env vars.
107
+
108
+ This is a convenience method that combines command(), build_args(),
109
+ and optional environment variables into a single shell command string.
110
+
111
+ Args:
112
+ dangerously_skip_permissions: Skip permission prompts
113
+ settings_file: Settings file for hook injection
114
+ env_vars: Environment variables to prepend
115
+
116
+ Returns:
117
+ Complete command string ready for shell execution
118
+ """
119
+ cmd = self.command()
120
+ args = self.build_args(
121
+ dangerously_skip_permissions=dangerously_skip_permissions,
122
+ settings_file=settings_file if self.supports_settings_file() else None,
123
+ )
124
+
125
+ if args:
126
+ cmd = f"{cmd} {' '.join(args)}"
127
+
128
+ if env_vars:
129
+ env_exports = " ".join(f"{k}={v}" for k, v in env_vars.items())
130
+ cmd = f"{env_exports} {cmd}"
131
+
132
+ return cmd
@@ -0,0 +1,110 @@
1
+ """
2
+ Claude Code CLI backend.
3
+
4
+ Implements the AgentCLI protocol for Claude Code CLI.
5
+ This preserves the existing behavior from iterm_utils.py.
6
+ """
7
+
8
+ import os
9
+ from typing import Literal
10
+
11
+ from .base import AgentCLI
12
+
13
+
14
+ class ClaudeCLI(AgentCLI):
15
+ """
16
+ Claude Code CLI implementation.
17
+
18
+ Supports:
19
+ - --dangerously-skip-permissions flag
20
+ - --settings flag for Stop hook injection
21
+ - Ready detection via TUI patterns (robot banner, '>' prompt, 'tokens' status)
22
+ - Idle detection via Stop hook markers in JSONL
23
+ """
24
+
25
+ @property
26
+ def engine_id(self) -> str:
27
+ """Return 'claude' as the engine identifier."""
28
+ return "claude"
29
+
30
+ def command(self) -> str:
31
+ """
32
+ Return the Claude CLI command.
33
+
34
+ Respects CLAUDE_TEAM_COMMAND environment variable for overrides
35
+ (e.g., "happy" wrapper).
36
+ """
37
+ return os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
38
+
39
+ def build_args(
40
+ self,
41
+ *,
42
+ dangerously_skip_permissions: bool = False,
43
+ settings_file: str | None = None,
44
+ ) -> list[str]:
45
+ """
46
+ Build Claude CLI arguments.
47
+
48
+ Args:
49
+ dangerously_skip_permissions: Add --dangerously-skip-permissions
50
+ settings_file: Path to settings JSON for Stop hook injection
51
+
52
+ Returns:
53
+ List of CLI arguments
54
+ """
55
+ args: list[str] = []
56
+
57
+ if dangerously_skip_permissions:
58
+ args.append("--dangerously-skip-permissions")
59
+
60
+ # Only add --settings for the default 'claude' command.
61
+ # Custom commands like 'happy' have their own session tracking mechanisms.
62
+ # See HAPPY_INTEGRATION_RESEARCH.md for full analysis.
63
+ if settings_file and self._is_default_command():
64
+ args.append("--settings")
65
+ args.append(settings_file)
66
+
67
+ return args
68
+
69
+ def ready_patterns(self) -> list[str]:
70
+ """
71
+ Return patterns indicating Claude TUI is ready.
72
+
73
+ These patterns appear in Claude's startup:
74
+ - '>' prompt indicates input ready
75
+ - 'tokens' in status bar
76
+ - Parts of the robot ASCII art banner
77
+ """
78
+ return [
79
+ ">", # Input prompt
80
+ "tokens", # Status bar
81
+ "Claude Code v", # Version line in banner
82
+ "▐▛███▜▌", # Top of robot head
83
+ "▝▜█████▛▘", # Middle of robot
84
+ ]
85
+
86
+ def idle_detection_method(self) -> Literal["stop_hook", "jsonl_stream", "none"]:
87
+ """
88
+ Claude uses Stop hook for idle detection.
89
+
90
+ A Stop hook writes a marker to the JSONL when Claude finishes responding.
91
+ """
92
+ return "stop_hook"
93
+
94
+ def supports_settings_file(self) -> bool:
95
+ """
96
+ Claude supports --settings for hook injection.
97
+
98
+ Only returns True for the default 'claude' command.
99
+ Custom wrappers may have their own settings mechanisms.
100
+ """
101
+ return self._is_default_command()
102
+
103
+ def _is_default_command(self) -> bool:
104
+ """Check if using the default 'claude' command (not a custom wrapper)."""
105
+ cmd = os.environ.get("CLAUDE_TEAM_COMMAND", "claude")
106
+ return cmd == "claude"
107
+
108
+
109
+ # Singleton instance for convenience
110
+ claude_cli = ClaudeCLI()
@@ -0,0 +1,110 @@
1
+ """
2
+ OpenAI Codex CLI backend.
3
+
4
+ Implements the AgentCLI protocol for OpenAI's Codex CLI.
5
+ This is a basic implementation - full integration will be done in later tasks.
6
+
7
+ Codex CLI reference: https://github.com/openai/codex
8
+ """
9
+
10
+ import os
11
+ from typing import Literal
12
+
13
+ from .base import AgentCLI
14
+
15
+
16
+ class CodexCLI(AgentCLI):
17
+ """
18
+ OpenAI Codex CLI implementation.
19
+
20
+ Note: This is a basic structure. Full Codex integration (ready detection,
21
+ idle detection, etc.) will be implemented in later tasks (cic-f7w.3+).
22
+
23
+ Codex CLI characteristics:
24
+ - Uses `codex` command
25
+ - Has --full-auto flag for non-interactive mode
26
+ - No known Stop hook equivalent (may need JSONL streaming or timeouts)
27
+ """
28
+
29
+ @property
30
+ def engine_id(self) -> str:
31
+ """Return 'codex' as the engine identifier."""
32
+ return "codex"
33
+
34
+ def command(self) -> str:
35
+ """
36
+ Return the Codex CLI command.
37
+
38
+ Respects CLAUDE_TEAM_CODEX_COMMAND environment variable for overrides
39
+ (e.g., "happy codex" wrapper).
40
+ """
41
+ return os.environ.get("CLAUDE_TEAM_CODEX_COMMAND", "codex")
42
+
43
+ def build_args(
44
+ self,
45
+ *,
46
+ dangerously_skip_permissions: bool = False,
47
+ settings_file: str | None = None,
48
+ ) -> list[str]:
49
+ """
50
+ Build Codex CLI arguments for interactive mode.
51
+
52
+ Args:
53
+ dangerously_skip_permissions: Maps to --full-auto for Codex
54
+ settings_file: Ignored - Codex doesn't support settings injection
55
+
56
+ Returns:
57
+ List of CLI arguments for interactive mode
58
+ """
59
+ args: list[str] = []
60
+
61
+ # Codex uses --dangerously-bypass-approvals-and-sandbox for autonomous operation
62
+ # (--full-auto doesn't work through happy wrapper)
63
+ if dangerously_skip_permissions:
64
+ args.append("--dangerously-bypass-approvals-and-sandbox")
65
+
66
+ # Note: settings_file is ignored - Codex doesn't support this
67
+ # Idle detection uses session file polling instead
68
+
69
+ return args
70
+
71
+
72
+ def ready_patterns(self) -> list[str]:
73
+ """
74
+ Return patterns indicating Codex CLI is ready for input.
75
+
76
+ Codex in interactive mode shows status bar when ready.
77
+ Updated for Codex CLI v0.80.0 behavior.
78
+ """
79
+ return [
80
+ "context left", # Status bar shows "100% context left"
81
+ "for shortcuts", # Status bar shows "? for shortcuts"
82
+ "What can I help you with?", # Legacy prompt (older versions)
83
+ "codex>", # Alternative prompt pattern
84
+ "»", # Codex uses this prompt symbol
85
+ "Waiting for messages", # Happy codex wrapper
86
+ "Codex Agent Running", # Happy codex status bar
87
+ ]
88
+
89
+ def idle_detection_method(self) -> Literal["stop_hook", "jsonl_stream", "none"]:
90
+ """
91
+ Codex idle detection method.
92
+
93
+ Codex writes session files to ~/.codex/sessions/YYYY/MM/DD/.
94
+ The idle_detection module polls these files for agent_message
95
+ events which indicate the agent has finished responding.
96
+ """
97
+ return "jsonl_stream"
98
+
99
+ def supports_settings_file(self) -> bool:
100
+ """
101
+ Codex doesn't support --settings for hook injection.
102
+
103
+ Alternative completion detection methods will be needed.
104
+ """
105
+ return False
106
+
107
+
108
+
109
+ # Singleton instance for convenience
110
+ codex_cli = CodexCLI()
@@ -57,19 +57,22 @@ def format_badge_text(
57
57
  name: str,
58
58
  bead: Optional[str] = None,
59
59
  annotation: Optional[str] = None,
60
+ agent_type: Optional[str] = None,
60
61
  max_annotation_length: int = 30,
61
62
  ) -> str:
62
63
  """
63
64
  Format badge text with bead/name on first line, annotation on second.
64
65
 
65
66
  Creates a multi-line string suitable for iTerm2 badge display:
66
- - Line 1: bead ID if provided, otherwise worker name
67
+ - Line 1: Agent type prefix (if not "claude") + bead ID (if provided) or worker name
67
68
  - Line 2: annotation (if provided), truncated if too long
68
69
 
69
70
  Args:
70
71
  name: Worker name (used if bead not provided)
71
72
  bead: Optional bead/issue ID (e.g., "cic-3dj")
72
73
  annotation: Optional task annotation
74
+ agent_type: Optional agent type ("claude" or "codex"). If "codex",
75
+ adds a prefix to the first line.
73
76
  max_annotation_length: Maximum length for annotation line (default 30)
74
77
 
75
78
  Returns:
@@ -90,10 +93,22 @@ def format_badge_text(
90
93
 
91
94
  >>> format_badge_text("Groucho", annotation="a very long annotation here", max_annotation_length=20)
92
95
  'Groucho\\na very long annot...'
96
+
97
+ >>> format_badge_text("Groucho", agent_type="codex")
98
+ '[Codex] Groucho'
99
+
100
+ >>> format_badge_text("Groucho", "cic-3dj", agent_type="codex")
101
+ '[Codex] cic-3dj'
93
102
  """
94
103
  # First line: bead if provided, otherwise name
95
104
  first_line = bead if bead else name
96
105
 
106
+ # Add agent type prefix for non-Claude agents
107
+ if agent_type and agent_type != "claude":
108
+ # Capitalize the agent type for display (e.g., "codex" -> "Codex")
109
+ type_display = agent_type.capitalize()
110
+ first_line = f"[{type_display}] {first_line}"
111
+
97
112
  # Second line: annotation if provided, with truncation
98
113
  if annotation:
99
114
  if len(annotation) > max_annotation_length: