Skip to content

Commit c7fb101

Browse files
authored
Merge pull request #47 from VibePod/issue-43
Add ikwid mode for agent autoaproval
2 parents 6cb5897 + 97a42bb commit c7fb101

4 files changed

Lines changed: 206 additions & 0 deletions

File tree

src/vibepod/commands/run.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,13 @@ def run(
228228
help="Enable image pasting via X11 clipboard (requires DISPLAY to be set)",
229229
),
230230
] = False,
231+
ikwid: Annotated[
232+
bool,
233+
typer.Option(
234+
"--ikwid",
235+
help="I Know What I'm Doing: enable auto-approval / skip permission prompts",
236+
),
237+
] = False,
231238
) -> None:
232239
"""Start an agent container."""
233240
config = get_config()
@@ -290,6 +297,13 @@ def run(
290297
raise typer.Exit(1) from exc
291298
entrypoint = _init_entrypoint(init_commands)
292299

300+
if ikwid:
301+
if spec.ikwid_args:
302+
info(f"IKWID mode: appending {spec.ikwid_args} to {selected_agent} command")
303+
command = list(command or []) + spec.ikwid_args
304+
else:
305+
warning(f"IKWID mode not supported for agent '{selected_agent}', ignoring")
306+
293307
config_dir = agent_config_dir(selected_agent)
294308
config_dir.mkdir(parents=True, exist_ok=True)
295309

src/vibepod/core/agents.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class AgentSpec:
2121
extra_env: dict[str, str]
2222
platform: str | None = None
2323
run_as_host_user: bool = False
24+
ikwid_args: list[str] | None = None
2425

2526

2627
AGENT_SPECS: dict[str, AgentSpec] = {
@@ -32,6 +33,7 @@ class AgentSpec:
3233
["claude"],
3334
"/claude",
3435
{"CLAUDE_CONFIG_DIR": "/claude"},
36+
ikwid_args=["--dangerously-skip-permissions"],
3537
),
3638
"gemini": AgentSpec(
3739
"gemini",
@@ -88,6 +90,7 @@ class AgentSpec:
8890
["codex"],
8991
"/config",
9092
{"HOME": "/config"},
93+
ikwid_args=["--full-auto"],
9194
),
9295
}
9396

tests/test_agents.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ def test_resolve_agent_name_accepts_short_and_full_forms() -> None:
5454
assert resolve_agent_name("unknown") is None
5555

5656

57+
def test_claude_spec_has_ikwid_args() -> None:
58+
spec = get_agent_spec("claude")
59+
assert spec.ikwid_args == ["--dangerously-skip-permissions"]
60+
61+
62+
def test_codex_spec_has_ikwid_args() -> None:
63+
spec = get_agent_spec("codex")
64+
assert spec.ikwid_args == ["--full-auto"]
65+
66+
67+
def test_unsupported_agents_have_no_ikwid_args() -> None:
68+
for agent in ("gemini", "opencode", "devstral", "auggie", "copilot"):
69+
spec = get_agent_spec(agent)
70+
assert spec.ikwid_args is None, f"{agent} should not have ikwid_args"
71+
72+
5773
def test_get_agent_shortcut_known_agent() -> None:
5874
expected_by_agent = {agent: shortcut for shortcut, agent in AGENT_SHORTCUTS.items()}
5975
assert set(expected_by_agent.keys()) == set(SUPPORTED_AGENTS)

tests/test_run.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,179 @@ def test_auto_pull_per_agent_none_falls_back_to_global(monkeypatch, tmp_path: Pa
535535
assert len(stub.pulled) == 1
536536

537537

538+
def test_ikwid_appends_args_for_claude(monkeypatch, tmp_path: Path) -> None:
539+
"""--ikwid appends --dangerously-skip-permissions to claude command."""
540+
captured: dict = {}
541+
542+
class _CapturingDockerManager:
543+
def ensure_network(self, name: str) -> None:
544+
pass
545+
546+
def networks_with_running_containers(self) -> list[str]:
547+
return []
548+
549+
def pull_image(self, image: str) -> None:
550+
pass
551+
552+
def ensure_proxy(self, **kwargs) -> None: # type: ignore[no-untyped-def]
553+
pass
554+
555+
def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def]
556+
captured.update(kwargs)
557+
container = type(
558+
"_Container",
559+
(),
560+
{
561+
"name": "vibepod-claude-test",
562+
"id": "abc123",
563+
"status": "running",
564+
"attrs": {"NetworkSettings": {"Networks": {}}},
565+
"reload": lambda self: None,
566+
"labels": {},
567+
"logs": lambda self, **kw: b"",
568+
},
569+
)()
570+
return container
571+
572+
monkeypatch.setattr(run_cmd, "get_config", lambda: _make_config())
573+
monkeypatch.setattr(run_cmd, "DockerManager", _CapturingDockerManager)
574+
575+
run_cmd.run(agent="claude", workspace=tmp_path, detach=True, ikwid=True)
576+
577+
assert captured["command"] == ["claude", "--dangerously-skip-permissions"]
578+
579+
580+
def test_ikwid_appends_args_for_codex(monkeypatch, tmp_path: Path) -> None:
581+
"""--ikwid appends --full-auto to codex command."""
582+
captured: dict = {}
583+
584+
class _CapturingDockerManager:
585+
def ensure_network(self, name: str) -> None:
586+
pass
587+
588+
def networks_with_running_containers(self) -> list[str]:
589+
return []
590+
591+
def pull_image(self, image: str) -> None:
592+
pass
593+
594+
def ensure_proxy(self, **kwargs) -> None: # type: ignore[no-untyped-def]
595+
pass
596+
597+
def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def]
598+
captured.update(kwargs)
599+
container = type(
600+
"_Container",
601+
(),
602+
{
603+
"name": "vibepod-codex-test",
604+
"id": "abc123",
605+
"status": "running",
606+
"attrs": {"NetworkSettings": {"Networks": {}}},
607+
"reload": lambda self: None,
608+
"labels": {},
609+
"logs": lambda self, **kw: b"",
610+
},
611+
)()
612+
return container
613+
614+
cfg = _make_config()
615+
cfg["agents"]["codex"] = {"env": {}, "init": []}
616+
monkeypatch.setattr(run_cmd, "get_config", lambda: cfg)
617+
monkeypatch.setattr(run_cmd, "DockerManager", _CapturingDockerManager)
618+
619+
run_cmd.run(agent="codex", workspace=tmp_path, detach=True, ikwid=True)
620+
621+
assert captured["command"] == ["codex", "--full-auto"]
622+
623+
624+
def test_ikwid_ignored_for_unsupported_agent(monkeypatch, tmp_path: Path) -> None:
625+
"""--ikwid logs warning and proceeds for agents without ikwid_args."""
626+
captured: dict = {}
627+
628+
class _CapturingDockerManager:
629+
def ensure_network(self, name: str) -> None:
630+
pass
631+
632+
def networks_with_running_containers(self) -> list[str]:
633+
return []
634+
635+
def pull_image(self, image: str) -> None:
636+
pass
637+
638+
def ensure_proxy(self, **kwargs) -> None: # type: ignore[no-untyped-def]
639+
pass
640+
641+
def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def]
642+
captured.update(kwargs)
643+
container = type(
644+
"_Container",
645+
(),
646+
{
647+
"name": "vibepod-gemini-test",
648+
"id": "abc123",
649+
"status": "running",
650+
"attrs": {"NetworkSettings": {"Networks": {}}},
651+
"reload": lambda self: None,
652+
"labels": {},
653+
"logs": lambda self, **kw: b"",
654+
},
655+
)()
656+
return container
657+
658+
cfg = _make_config()
659+
cfg["agents"]["gemini"] = {"env": {}, "init": []}
660+
monkeypatch.setattr(run_cmd, "get_config", lambda: cfg)
661+
monkeypatch.setattr(run_cmd, "DockerManager", _CapturingDockerManager)
662+
663+
run_cmd.run(agent="gemini", workspace=tmp_path, detach=True, ikwid=True)
664+
665+
# Command should be unchanged (no ikwid args appended)
666+
assert captured["command"] == ["gemini"]
667+
668+
669+
def test_ikwid_false_does_not_modify_command(monkeypatch, tmp_path: Path) -> None:
670+
"""Without --ikwid, command is unchanged."""
671+
captured: dict = {}
672+
673+
class _CapturingDockerManager:
674+
def ensure_network(self, name: str) -> None:
675+
pass
676+
677+
def networks_with_running_containers(self) -> list[str]:
678+
return []
679+
680+
def pull_image(self, image: str) -> None:
681+
pass
682+
683+
def ensure_proxy(self, **kwargs) -> None: # type: ignore[no-untyped-def]
684+
pass
685+
686+
def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def]
687+
captured.update(kwargs)
688+
container = type(
689+
"_Container",
690+
(),
691+
{
692+
"name": "vibepod-claude-test",
693+
"id": "abc123",
694+
"status": "running",
695+
"attrs": {"NetworkSettings": {"Networks": {}}},
696+
"reload": lambda self: None,
697+
"labels": {},
698+
"logs": lambda self, **kw: b"",
699+
},
700+
)()
701+
return container
702+
703+
monkeypatch.setattr(run_cmd, "get_config", lambda: _make_config())
704+
monkeypatch.setattr(run_cmd, "DockerManager", _CapturingDockerManager)
705+
706+
run_cmd.run(agent="claude", workspace=tmp_path, detach=True, ikwid=False)
707+
708+
assert captured["command"] == ["claude"]
709+
710+
538711
def test_run_accepts_short_agent_name(monkeypatch, tmp_path: Path) -> None:
539712
class _UnavailableDockerManager:
540713
def __init__(self) -> None:

0 commit comments

Comments
 (0)