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
42 changes: 42 additions & 0 deletions src/specify_cli/integrations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

from __future__ import annotations

import os
import re
import shlex
import shutil
from abc import ABC
from dataclasses import dataclass
Expand Down Expand Up @@ -138,6 +140,43 @@ def build_exec_args(
"""
return None

def _apply_extra_args_env_var(self, args: list[str]) -> None:
"""Append `SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS` env-var value to *args*.

Operators can inject extra CLI flags into the spawned agent
subprocess by setting an env var named for the integration key,
e.g. `SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS="--dangerously-skip-permissions"`.
The `INTEGRATION` segment scopes the variable to this subsystem
so it does not collide with other Spec Kit env-var namespaces.
Hyphens in the integration key are replaced with underscores
and the key is uppercased
(e.g. `kiro-cli` → `SPECKIT_INTEGRATION_KIRO_CLI_EXTRA_ARGS`).

Useful in CI / non-interactive contexts where the spawned agent
needs flags that change its prompt-handling behaviour.
Default behaviour (env var unset or whitespace-only) is a no-op
— *args* is unchanged. Multi-token values are parsed via
`shlex.split`.

See issue #2595.
"""
env_name = (
f"SPECKIT_INTEGRATION_{self.key.upper().replace('-', '_')}_EXTRA_ARGS"
)
Comment thread
mnriem marked this conversation as resolved.
Comment thread
mnriem marked this conversation as resolved.
extra = os.environ.get(env_name, "").strip()
if not extra:
return
try:
tokens = shlex.split(extra)
except ValueError as exc:
raise ValueError(
f"{env_name} is not parseable as a POSIX-quoted command line "
f"(value: {extra!r}). shlex reported: {exc}. "
f"Use single or double quotes to group multi-word values, e.g. "
f'{env_name}=\'--flag "value with spaces"\'.'
) from exc
args.extend(tokens)

def build_command_invocation(self, command_name: str, args: str = "") -> str:
"""Build the native slash-command invocation for a Spec Kit command.

Expand Down Expand Up @@ -851,6 +890,7 @@ def build_exec_args(
if not self.config or not self.config.get("requires_cli"):
return None
args = [self.key, "-p", prompt]
self._apply_extra_args_env_var(args)
if model:
args.extend(["--model", model])
if output_json:
Expand Down Expand Up @@ -938,6 +978,7 @@ def build_exec_args(
if not self.config or not self.config.get("requires_cli"):
return None
args = [self.key, "-p", prompt]
self._apply_extra_args_env_var(args)
if model:
args.extend(["-m", model])
if output_json:
Expand Down Expand Up @@ -1356,6 +1397,7 @@ def build_exec_args(
if not self.config or not self.config.get("requires_cli"):
return None
args = [self.key, "-p", prompt]
self._apply_extra_args_env_var(args)
if model:
args.extend(["--model", model])
if output_json:
Expand Down
7 changes: 6 additions & 1 deletion src/specify_cli/integrations/codex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ def build_exec_args(
output_json: bool = True,
) -> list[str] | None:
# Codex uses ``codex exec "prompt"`` for non-interactive mode.
args: list[str] = ["codex", "exec", prompt]
# Use ``self.key`` so the executable name stays coupled to the
# env-var lookup (which also derives from ``self.key``), matching
# the pattern in Devin/Opencode and avoiding drift if the key
# ever changes.
args: list[str] = [self.key, "exec", prompt]
self._apply_extra_args_env_var(args)
if model:
args.extend(["--model", model])
if output_json:
Expand Down
6 changes: 6 additions & 0 deletions src/specify_cli/integrations/copilot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def build_exec_args(
# (default: enabled). The deprecated SPECKIT_ALLOW_ALL_TOOLS
# is also honoured as a fallback.
args = [_copilot_executable(), "-p", prompt]
self._apply_extra_args_env_var(args)
if _allow_all():
args.append("--yolo")
if model:
Expand Down Expand Up @@ -217,6 +218,11 @@ def dispatch_command(
prompt = args or ""

cli_args = [_copilot_executable(), "-p", prompt]
# Honour SPECKIT_INTEGRATION_COPILOT_EXTRA_ARGS for real workflow
# runs. `dispatch_command` builds cli_args inline rather than
# going through `build_exec_args`, so the hook must be invoked
# here too — otherwise the env var is silently ignored.
self._apply_extra_args_env_var(cli_args)
if not skills_mode:
cli_args.extend(["--agent", agent_name])
if _allow_all():
Expand Down
1 change: 1 addition & 0 deletions src/specify_cli/integrations/devin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def build_exec_args(
kept on the integration for tool detection.
"""
args = [self.key, "-p", prompt]
self._apply_extra_args_env_var(args)
if model:
args.extend(["--model", model])
return args
Expand Down
5 changes: 5 additions & 0 deletions src/specify_cli/integrations/opencode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ def build_exec_args(
output_json: bool = True,
) -> list[str] | None:
args = [self.key, "run"]
# Apply operator-injected extra args before the prompt-derived
# --command and the canonical --format/-m flags so Spec Kit's
# later appends remain authoritative under repeated-flag CLI
# semantics.
self._apply_extra_args_env_var(args)

message = prompt
if prompt.startswith("/"):
Expand Down
Loading
Loading