Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/maniple_mcp/cli_backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ def build_args(
*,
dangerously_skip_permissions: bool = False,
settings_file: str | None = None,
plugin_dir: str | list[str] | None = None,
) -> list[str]:
"""
Build the argument list for the CLI command.

Args:
dangerously_skip_permissions: If True, add flag to skip permission prompts
settings_file: Optional path to settings file for hook injection
plugin_dir: Optional path(s) to plugin directory (single string or list)

Returns:
List of command-line arguments (not including the command itself)
Expand Down Expand Up @@ -100,6 +102,7 @@ def build_full_command(
*,
dangerously_skip_permissions: bool = False,
settings_file: str | None = None,
plugin_dir: str | list[str] | None = None,
env_vars: dict[str, str] | None = None,
) -> str:
"""
Expand All @@ -111,6 +114,7 @@ def build_full_command(
Args:
dangerously_skip_permissions: Skip permission prompts
settings_file: Settings file for hook injection
plugin_dir: Optional path(s) to plugin directory (single string or list)
env_vars: Environment variables to prepend

Returns:
Expand All @@ -120,6 +124,7 @@ def build_full_command(
args = self.build_args(
dangerously_skip_permissions=dangerously_skip_permissions,
settings_file=settings_file if self.supports_settings_file() else None,
plugin_dir=plugin_dir,
)

if args:
Expand Down
9 changes: 9 additions & 0 deletions src/maniple_mcp/cli_backends/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,15 @@ def build_args(
*,
dangerously_skip_permissions: bool = False,
settings_file: str | None = None,
plugin_dir: str | list[str] | None = None,
) -> list[str]:
"""
Build Claude CLI arguments.

Args:
dangerously_skip_permissions: Add --dangerously-skip-permissions
settings_file: Path to settings JSON for Stop hook injection
plugin_dir: Path(s) to plugin directory for --plugin-dir (single string or list)

Returns:
List of CLI arguments
Expand All @@ -98,6 +100,13 @@ def build_args(

if dangerously_skip_permissions:
args.append("--dangerously-skip-permissions")

if plugin_dir:
# Support both single string and list of strings
plugin_dirs = [plugin_dir] if isinstance(plugin_dir, str) else plugin_dir
for dir_path in plugin_dirs:
args.append("--plugin-dir")
args.append(dir_path)

# Only add --settings for the default 'claude' command.
# Custom commands like 'happy' have their own session tracking mechanisms.
Expand Down
2 changes: 2 additions & 0 deletions src/maniple_mcp/cli_backends/codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,15 @@ def build_args(
*,
dangerously_skip_permissions: bool = False,
settings_file: str | None = None,
plugin_dir: str | list[str] | None = None,
) -> list[str]:
"""
Build Codex CLI arguments for interactive mode.

Args:
dangerously_skip_permissions: Maps to --dangerously-bypass-approvals-and-sandbox for Codex
settings_file: Ignored - Codex doesn't support settings injection
plugin_dir: Ignored - Codex doesn't support plugin directories

Returns:
List of CLI arguments for interactive mode
Expand Down
3 changes: 3 additions & 0 deletions src/maniple_mcp/iterm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ async def start_agent_in_session(
agent_ready_timeout: float = 30.0,
stop_hook_marker_id: Optional[str] = None,
output_capture_path: Optional[str] = None,
plugin_dir: Optional[str | list[str]] = None,
) -> None:
"""
Start an agent CLI in an existing iTerm2 session.
Expand All @@ -657,6 +658,7 @@ async def start_agent_in_session(
(only used if cli.supports_settings_file() returns True)
output_capture_path: If provided, capture agent's stdout/stderr to this file
using tee. Useful for agents that output JSONL for idle detection.
plugin_dir: Optional path(s) to plugin directory for --plugin-dir flag

Raises:
RuntimeError: If shell not ready or agent fails to start within timeout
Expand All @@ -678,6 +680,7 @@ async def start_agent_in_session(
agent_cmd = cli.build_full_command(
dangerously_skip_permissions=dangerously_skip_permissions,
settings_file=settings_file,
plugin_dir=plugin_dir,
env_vars=env,
)

Expand Down
2 changes: 2 additions & 0 deletions src/maniple_mcp/terminal_backends/iterm.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ async def start_agent_in_session(
agent_ready_timeout: float = 30.0,
stop_hook_marker_id: Optional[str] = None,
output_capture_path: Optional[str] = None,
plugin_dir: Optional[str] = None,
) -> None:
"""Start a CLI agent in an existing terminal session."""
await iterm_utils.start_agent_in_session(
Expand All @@ -208,6 +209,7 @@ async def start_agent_in_session(
agent_ready_timeout=agent_ready_timeout,
stop_hook_marker_id=stop_hook_marker_id,
output_capture_path=output_capture_path,
plugin_dir=plugin_dir,
)

async def find_available_window(
Expand Down
2 changes: 2 additions & 0 deletions src/maniple_mcp/terminal_backends/tmux.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ async def start_agent_in_session(
agent_ready_timeout: float = 30.0,
stop_hook_marker_id: str | None = None,
output_capture_path: str | None = None,
plugin_dir: str | None = None,
) -> None:
"""Start a CLI agent in an existing tmux pane."""
# Ensure the shell is responsive before we send the launch command.
Expand All @@ -490,6 +491,7 @@ async def start_agent_in_session(
agent_cmd = cli.build_full_command(
dangerously_skip_permissions=dangerously_skip_permissions,
settings_file=settings_file,
plugin_dir=plugin_dir,
env_vars=env,
)

Expand Down
3 changes: 3 additions & 0 deletions src/maniple_mcp/tools/spawn_workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class WorkerConfig(TypedDict, total=False):
skip_permissions: bool # Optional: Default False
use_worktree: bool # Optional: Create isolated worktree (default True)
worktree: WorktreeConfig # Optional: Worktree settings (branch/base)
plugin_dir: str | list[str] # Optional: Path(s) to plugin directory for --plugin-dir


def register_tools(mcp: FastMCP, ensure_connection) -> None:
Expand Down Expand Up @@ -676,13 +677,15 @@ async def start_agent_for_worker(index: int) -> None:
skip_permissions = worker_config.get("skip_permissions")
if skip_permissions is None:
skip_permissions = defaults.skip_permissions
plugin_dir = worker_config.get("plugin_dir")
await backend.start_agent_in_session(
handle=session,
cli=cli,
project_path=project_path,
dangerously_skip_permissions=skip_permissions,
env=env,
stop_hook_marker_id=stop_hook_marker_id,
plugin_dir=plugin_dir,
)

await asyncio.gather(*[start_agent_for_worker(i) for i in range(worker_count)])
Expand Down