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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,10 @@ called out explicitly under **Changed** or **Removed**.
a stale warning that step ids rotate on every update is gone.
- **The variable-condition docs no longer call `client_tag_field_id` a
constant.** It varies by business. The docs say where to get it.
- **`kizen init` no longer prints the stored API key when it re-prompts for an
existing profile.** The prompt showed the key as its default, so it landed in
scrollback, screen shares and recordings. It now reads `API_KEY (Enter keeps
the stored key):`, and pressing Enter still keeps it.

- **`activate`, `deactivate`, `move`, `steps` and `roundtrip --execute` no
longer fail with HTTP 400 on automations with an Email Received trigger or
Expand Down
7 changes: 7 additions & 0 deletions src/kizen_builder/cli/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,14 @@ def _ask(
"no default" sentinel is Ellipsis, not `None` or `""`, so the `default`
kwarg is omitted rather than passed through — passing `None` would make
Rich hand back `None` on a bare Enter, bypassing `choices` entirely.

A `password` default is never shown: Rich prints the default in the
prompt even when it hides the typed input, which would put a stored
secret in scrollback. Bare Enter still returns it.
"""
prompt_cls = _EnvironmentPrompt if choices else Prompt
if password and default:
label = f"{label} [prompt.default](Enter keeps the stored key)[/]"
try:
if default is None:
return prompt_cls.ask(
Expand All @@ -187,6 +193,7 @@ def _ask(
password=password,
choices=choices,
case_sensitive=False,
show_default=not password,
)
except EOFError:
if default:
Expand Down
45 changes: 45 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2678,6 +2678,51 @@ def test_init_banner_shows_tagline_only_on_tiny_terminal(monkeypatch, tmp_path):
assert profiles.get_profile("banner-tiny") is not None


@pytest.mark.parametrize("stdin", ["\n\n\n\n", ""], ids=["bare-enter", "eof"])
def test_init_reprompt_hides_stored_api_key(monkeypatch, tmp_path, stdin):
"""Rich prints a prompt's default even with `password=True`, so re-running
`init` used to show the stored key in the terminal."""
from kizen_builder import config, profiles

config.set_profile_override(None)
profiles.write_profile(
profiles.ProfileCreds(
name="delta",
api_key="FAKEKEY123",
business_id="DDDD",
user_id="user1",
base_url="https://app.go.kizen.com",
)
)
monkeypatch.chdir(tmp_path)
result = runner.invoke(
cli.app, ["init", "--profile", "delta", "--skip-validation"], input=stdin
)
assert result.exit_code == 0, result.output
assert "FAKEKEY123" not in result.output
assert "Y123" not in result.output
assert "API_KEY (Enter keeps the stored key):" in result.output
assert "BUSINESS_ID (DDDD):" in result.output

stored = profiles.get_profile("delta")
assert stored is not None and stored.api_key == "FAKEKEY123"


def test_init_api_key_prompt_has_no_empty_default(monkeypatch, tmp_path):
from kizen_builder import config

config.set_profile_override(None)
monkeypatch.chdir(tmp_path)
result = runner.invoke(
cli.app,
["init", "--profile", "epsilon", "--skip-validation"],
input="apikey\nEEEE\nuser1\ngo\n",
)
assert result.exit_code == 0, result.output
assert "API_KEY: " in result.output
assert "API_KEY ()" not in result.output


def test_init_environment_picker_resolves_named_host(monkeypatch, tmp_path):
from kizen_builder import config, profiles

Expand Down
Loading