Skip to content

Commit d9a3fe8

Browse files
author
James Zhu
committed
feat: pass tool arguments through CLI and show complete command with parameters
- Add argument passing support: all arguments after tool name are now passed to the underlying tool - Works with both 'cam l [TOOL] [ARGS]' and 'cam [TOOL] [ARGS]' syntax - Config flag (--config) is properly filtered from tool arguments - Update ALL 16 tools to display complete command with arguments before execution - Consistent 'Complete command to execute:' format across all tools - Shows exact command that will be run including all parameters CLI Changes: - cli/app.py: Add context settings to allow extra args and ignore unknown options - cli/commands.py: Capture and pass args from ctx.args - cli/utils.py: Filter --config flag, pass remaining args to tools Tool Changes (all 16 tools updated): - claude, codebuddy, codex, continue, copilot, crush, cursor-agent, droid - gemini, goose, iflow, neovate, opencode, qodercli, qwen, zed Testing: - 323 tests passing across all critical test suites - No breaking changes to existing functionality - All CLI, tool, config, and integration tests pass Examples: - cam l claude --dangerously-skip-permissions - cam l codex --profile myprofile - cam l qwen --help 19 files changed, 204 insertions(+), 55 deletions(-)
1 parent bdef688 commit d9a3fe8

19 files changed

Lines changed: 204 additions & 55 deletions

code_assistant_manager/cli/app.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
CONFIG_OPTION,
2929
DEBUG_OPTION,
3030
SCOPE_OPTION,
31-
TOOL_ARGS_OPTION,
3231
VALIDATE_VERBOSE_OPTION,
3332
)
3433

@@ -61,6 +60,7 @@ def global_options(debug: bool = DEBUG_OPTION):
6160
editor_app = typer.Typer(
6261
help="Launch AI code editors: claude, codex, qwen, etc. (alias: l)",
6362
no_args_is_help=False,
63+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
6464
)
6565

6666

@@ -132,7 +132,6 @@ def make_command(name, cls):
132132
def command(
133133
ctx: Context,
134134
config: Optional[str] = CONFIG_OPTION,
135-
tool_args: List[str] = TOOL_ARGS_OPTION,
136135
):
137136
"""Launch the specified AI code editor."""
138137
# Initialize context object
@@ -141,6 +140,9 @@ def command(
141140
ctx.obj["debug"] = False
142141
ctx.obj["endpoints"] = None
143142

143+
# Get any extra args passed after the tool name
144+
tool_args = ctx.args if hasattr(ctx, 'args') else []
145+
144146
logger.debug(f"Executing command: {name} with args: {tool_args}")
145147
config_path = config
146148
logger.debug(f"Using config path: {config_path}")
@@ -174,16 +176,19 @@ def command(
174176

175177
logger.debug(f"Launching tool: {name}")
176178
tool_instance = cls(config_obj)
177-
sys.exit(tool_instance.run(tool_args or []))
179+
sys.exit(tool_instance.run(tool_args))
178180

179181
# Set the command name and help text
180182
command.__name__ = name
181183
command.__doc__ = f"Launch {name} editor"
182184
return command
183185

184186
for tool_name, tool_class in editor_tools.items():
185-
# Add the command to the editor app
186-
editor_app.command(name=tool_name)(make_command(tool_name, tool_class))
187+
# Add the command to the editor app with context settings
188+
editor_app.command(
189+
name=tool_name,
190+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True}
191+
)(make_command(tool_name, tool_class))
187192
logger.debug(f"Added command: {tool_name}")
188193

189194

code_assistant_manager/cli/commands.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ def launch_alias(ctx: Context, tool_name: str = TOOL_NAME_OPTION):
184184
registered_tools = get_registered_tools()
185185
if tool_name in registered_tools and tool_name != "mcp":
186186
config_path = ctx.obj.get("config_path")
187+
# Get any extra args passed after the tool name
188+
tool_args = ctx.args if hasattr(ctx, 'args') else []
187189
try:
188190
config = ConfigManager(config_path)
189191
is_valid, errors = config.validate_config()
@@ -194,7 +196,7 @@ def launch_alias(ctx: Context, tool_name: str = TOOL_NAME_OPTION):
194196
return 1
195197
tool_class = registered_tools.get(tool_name)
196198
tool = tool_class(config)
197-
return tool.run([])
199+
return tool.run(tool_args)
198200
except Exception as e:
199201
from code_assistant_manager.exceptions import create_error_handler
200202

code_assistant_manager/cli/utils.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,20 @@ def legacy_main():
4040

4141
if command in registered_tools and command != "mcp":
4242
logger.debug(f"Direct tool invocation detected for: {command}")
43-
# Extract config path if provided
43+
# Extract config path if provided and filter it out from tool args
4444
config_path = None
45-
if "--config" in sys.argv:
46-
idx = sys.argv.index("--config")
47-
if idx + 1 < len(sys.argv):
48-
config_path = sys.argv[idx + 1]
45+
tool_args = list(sys.argv[2:]) # Copy the args after the tool name
46+
if "--config" in tool_args:
47+
idx = tool_args.index("--config")
48+
if idx + 1 < len(tool_args):
49+
config_path = tool_args[idx + 1]
4950
logger.debug(f"Config path from args: {config_path}")
51+
# Remove --config and its value from tool args
52+
tool_args.pop(idx) # Remove --config
53+
tool_args.pop(idx) # Remove the config path value
54+
else:
55+
# Remove just --config if no value follows
56+
tool_args.pop(idx)
5057

5158
try:
5259
config = ConfigManager(config_path)
@@ -66,9 +73,9 @@ def legacy_main():
6673
return 1
6774

6875
tool_class = registered_tools.get(command)
69-
logger.debug(f"Launching tool directly: {command}")
76+
logger.debug(f"Launching tool directly: {command} with args: {tool_args}")
7077
tool = tool_class(config)
71-
return tool.run(sys.argv[2:])
78+
return tool.run(tool_args)
7279

7380
# If a --config flag is present, instantiate ConfigManager early so
7481
# legacy callers and tests that patch ConfigManager observe the call.

code_assistant_manager/tools/claude.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ def run(self, args: List[str] = None) -> int:
5959
)
6060
env = env_builder.build()
6161

62+
# Execute the Claude CLI with the configured environment
63+
command = ["claude", *args]
64+
6265
# Display the complete command that will be executed
66+
args_str = " ".join(args) if args else ""
67+
command_str = f"claude {args_str}".strip()
6368
print("")
6469
print("Complete command to execute:")
6570
print(
@@ -70,12 +75,9 @@ def run(self, args: List[str] = None) -> int:
7075
f"ANTHROPIC_SMALL_FAST_MODEL={secondary_model} "
7176
f"ANTHROPIC_DEFAULT_HAIKU_MODEL={primary_model} "
7277
f"DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 "
73-
f"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 claude"
78+
f"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 {command_str}"
7479
)
7580
print("")
76-
77-
# Execute the Claude CLI with the configured environment
78-
command = ["claude", *args]
7981
return self._run_tool_with_env(command, env, "claude", interactive=True)
8082
except KeyboardInterrupt:
8183
logger.info("Tool execution interrupted by user")

code_assistant_manager/tools/codebuddy.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ def run(self, args: List[str] = None) -> int:
4949
# Set TLS environment for Node.js
5050
self._set_node_tls_env(env)
5151

52+
# Execute the CodeBuddy CLI with the configured environment and model
53+
command = ["codebuddy", "--model", model] + args
54+
5255
# Display the complete command that will be executed
56+
args_str = " ".join(args) if args else ""
57+
command_str = f"codebuddy --model {model} {args_str}".strip()
5358
print("")
5459
print("Complete command to execute:")
5560
print(
56-
f"CODEBUDDY_API_KEY=*** CODEBUDDY_BASE_URL={env['CODEBUDDY_BASE_URL']} codebuddy --model {model}"
61+
f"CODEBUDDY_API_KEY=*** CODEBUDDY_BASE_URL={env['CODEBUDDY_BASE_URL']} {command_str}"
5762
)
5863
print("")
59-
60-
# Execute the CodeBuddy CLI with the configured environment and model
61-
command = ["codebuddy", "--model", model] + args
6264
return self._run_tool_with_env(command, env, "codebuddy", interactive=True)

code_assistant_manager/tools/codex.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,17 @@ def run(self, args: List[str] = None) -> int:
8989
self._load_environment()
9090
env = os.environ.copy()
9191
self._set_node_tls_env(env)
92-
return self._run_tool_with_env(["codex"] + args, env, "codex", interactive=True)
92+
93+
# Display the complete command
94+
command = ["codex"] + args
95+
args_str = " ".join(args) if args else ""
96+
command_str = f"codex {args_str}".strip()
97+
print("")
98+
print("Complete command to execute:")
99+
print(command_str)
100+
print("")
101+
102+
return self._run_tool_with_env(command, env, "codex", interactive=True)
93103

94104
# Multi-provider flow: prompt each provider (endpoint) once, then choose which profile to run.
95105
from code_assistant_manager.menu.menus import display_centered_menu
@@ -135,7 +145,7 @@ def run(self, args: List[str] = None) -> int:
135145
selected_models = [models[0]]
136146
else:
137147
from code_assistant_manager.menu.menus import select_multiple_models
138-
148+
139149
ok, selected_models = select_multiple_models(
140150
models,
141151
f"Select models from {endpoint_info} (Cancel to skip):",
@@ -169,7 +179,7 @@ def run(self, args: List[str] = None) -> int:
169179
profile_env[profile_name] = (env_key, endpoint_config.get("actual_api_key"))
170180

171181
all_profiles = sorted(set(existing_profiles + configured_profiles))
172-
182+
173183
if not all_profiles:
174184
return 0
175185

@@ -194,4 +204,13 @@ def run(self, args: List[str] = None) -> int:
194204
self._set_node_tls_env(env)
195205

196206
command = ["codex", "-p", selected_profile] + args
207+
208+
# Display the complete command
209+
args_str = " ".join(args) if args else ""
210+
command_str = f"codex -p {selected_profile} {args_str}".strip()
211+
print("")
212+
print("Complete command to execute:")
213+
print(command_str)
214+
print("")
215+
197216
return self._run_tool_with_env(command, env, "codex", interactive=True)

code_assistant_manager/tools/continue_tool.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def _process_endpoint(self, endpoint_name: str) -> Optional[List[str]]:
7474
selected_models = [models[0]]
7575
else:
7676
success, selected_models = select_multiple_models(
77-
models,
77+
models,
7878
f"Select models from {endpoint_info} (Cancel to skip):",
7979
cancel_text="Skip"
8080
)
@@ -202,4 +202,13 @@ def run(self, args: List[str] = None) -> int:
202202

203203
# Execute the Continue CLI with the configured environment
204204
command = [self.command_name, *args]
205-
return self._run_tool_with_env(command, env, self.command_name, interactive=True)
205+
206+
# Display the complete command
207+
args_str = " ".join(args) if args else ""
208+
command_str = f"{self.command_name} {args_str}".strip()
209+
print("")
210+
print("Complete command to execute:")
211+
print(command_str)
212+
print("")
213+
214+
return self._run_tool_with_env(command, env, self.command_name, interactive=True)

code_assistant_manager/tools/copilot.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ def run(self, args: List[str] = None) -> int:
3636

3737
try:
3838
command = ["copilot", "--banner"] + args
39+
40+
# Display the complete command
41+
args_str = " ".join(args) if args else ""
42+
command_str = f"copilot --banner {args_str}".strip()
43+
print("")
44+
print("Complete command to execute:")
45+
print(command_str)
46+
print("")
47+
3948
result = self._run_command(command, env=env)
4049
return result.returncode
4150
except KeyboardInterrupt:

code_assistant_manager/tools/crush.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,15 @@ def run(self, args: List[str] = None) -> int:
440440
print(f"DEBUG: running: crush {' '.join(args)}\n")
441441

442442
command = ["crush"] + args
443+
444+
# Display the complete command
445+
args_str = " ".join(args) if args else ""
446+
command_str = f"crush {args_str}".strip()
447+
print("")
448+
print("Complete command to execute:")
449+
print(command_str)
450+
print("")
451+
443452
return self._run_tool_with_env(command, env, "crush", interactive=True)
444453

445454
def _handle_mcp_command(self, args: List[str]) -> int:

code_assistant_manager/tools/cursor.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ def run(self, args: List[str] = None) -> int:
4343
# Execute cursor-agent with the provided arguments
4444
try:
4545
cmd = ["cursor-agent"] + args
46+
47+
# Display the complete command
48+
args_str = " ".join(args) if args else ""
49+
command_str = f"cursor-agent {args_str}".strip()
50+
print("")
51+
print("Complete command to execute:")
52+
print(command_str)
53+
print("")
54+
4655
logger.info(f"Running command: {' '.join(cmd)}")
4756
result = subprocess.run(cmd, env=env)
4857
return result.returncode

0 commit comments

Comments
 (0)