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
29 changes: 4 additions & 25 deletions src/kai/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,29 +932,10 @@ def _cmd_config() -> None:
# the canonical file from a no-longer-current staging artifact.
users_yaml_staging_path: str | None = None

if users_yaml_exists:
# Summarize the existing config without modifying it. Reads
# go through `_read_users_yaml_text` so the protected
# `/etc/kai/users.yaml` path works via sudo cat. Empty
# summary on unreadable / malformed - the summary is
# cosmetic, the downstream codex predicate is what matters.
summary = ""
raw_yaml = _read_users_yaml_text(users_yaml_path)
if raw_yaml is not None:
try:
data = yaml.safe_load(raw_yaml)
except yaml.YAMLError:
data = None
if isinstance(data, dict) and isinstance(data.get("users"), list):
entries = data["users"]
n_users = len(entries)
n_admins = sum(1 for e in entries if isinstance(e, dict) and str(e.get("role", "")).lower() == "admin")
summary = (
f" ({n_users} user{'s' if n_users != 1 else ''}, {n_admins} admin{'s' if n_admins != 1 else ''})"
)
print(f" users.yaml already configured{summary}.")
print(" To modify users, edit users.yaml directly or use Telegram commands.")
else:
# When the canonical users.yaml already exists the wizard leaves it
# untouched (edit it directly or use the Telegram commands), so the
# admin-identity prompts below run only on a first-time install.
if not users_yaml_exists:
while True:
admin_telegram_id = _prompt(
"Admin Telegram ID",
Expand Down Expand Up @@ -1662,8 +1643,6 @@ def _default_model_prefill() -> str:
# timeout. Both fire on every wizard run. The per-user
# `pr_review` toggle lives in users.yaml (or /github reviews).
print("-- PR review agent --")
print(" Per-user PR review toggle lives in users.yaml ('pr_review')")
print(" or via /github reviews on|off.")

# Global cooldown always prompts: any opted-in user can drive
# reviews, so the cooldown must be configurable for any install
Expand Down
29 changes: 16 additions & 13 deletions tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -1265,15 +1265,17 @@ def _redirect_staging(monkeypatch, tmp_path):
def _simulate_existing_etc_users_yaml(monkeypatch, content):
"""Simulate the canonical users.yaml already existing with the given content.

Used by tests that want to exercise the wizard's "skip user
prompts; summarize existing config" branch without touching the
real `/etc/kai/`. Patches the path's existence check AND the
sudo-cat reader that the wizard goes through (`Path.exists` for
the existence gate; `_read_users_yaml_text` for the body the
wizard prints a summary of). The existence patch compares
against the live `kai.install.USERS_YAML` attribute rather than
a hardcoded path, so it stacks on top of the autouse
`_isolate_users_yaml` redirect from conftest.
Used by tests that want the wizard to take the existing-canonical
path (skip the user-creation prompts, leave the file untouched)
without touching the real `/etc/kai/`. Patches the path's
existence check AND the sudo-cat reader the wizard goes through
(`Path.exists` for the existence gate; `_read_users_yaml_text`
for the body the wizard's later per-user scans parse, e.g.
`_users_yaml_goose_providers` / `_users_yaml_agent_backends`).
The existence patch compares against the live
`kai.install.USERS_YAML` attribute rather than a hardcoded path,
so it stacks on top of the autouse `_isolate_users_yaml` redirect
from conftest.
"""
_real_exists = Path.exists

Expand Down Expand Up @@ -1808,7 +1810,7 @@ def test_reads_existing_defaults(self, tmp_path, monkeypatch, capsys):
conf_path.write_text(json.dumps(existing))

# Pretend /etc/kai/users.yaml is already in place; the wizard
# then summarizes it and skips the user-creation prompts.
# then skips the user-creation prompts and leaves it untouched.
existing_users_yaml = "users:\n - telegram_id: 999\n name: existing\n role: admin\n"
self._simulate_existing_etc_users_yaml(monkeypatch, existing_users_yaml)

Expand All @@ -1826,10 +1828,11 @@ def test_reads_existing_defaults(self, tmp_path, monkeypatch, capsys):
# Should preserve existing values when user accepts defaults
assert conf["install_dir"] == "/custom/path"
assert conf["env"]["TELEGRAM_BOT_TOKEN"] == "existing-token"
# Summary path printed; the wizard never staged a new users.yaml
# so no top-level staging key was written.
# With a canonical users.yaml already present the wizard skips the
# user-creation prompts entirely and never stages a new file, so
# no admin prompt is shown and no top-level staging key is written.
output = capsys.readouterr().out
assert "already configured" in output
assert "Admin Telegram ID" not in output
assert "users_yaml_staging_path" not in conf

def test_validates_required_fields(self):
Expand Down
Loading