feat(#83): offer move-to-env on args rows; explain the gate instead of an empty menu
CI / Lint (ruff) (pull_request) Successful in 19s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 24s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 29s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 29s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 31s
CI / Catalog signature (pull_request) Successful in 22s

Two things surfaced testing the GUI:

1. Args rows showed the secret warning but no move action -- the args
   editor is a free-text widget, not a table, and was deliberately left out
   of the first cut. Wired it up: ArgsEdit gains a context menu that offers
   "Move to environment variable…" on exactly the args that look like a
   credential. New pure core: secret_arg_indices (which args are secrets,
   mirroring args_secret_warning per-index) and suggested_env_var_for_arg
   (default var name from the preceding flag -- `--api-key <secret>` ->
   API_KEY, else SECRET). The move replaces that one arg line with ${VAR}
   and copies the secret to the clipboard, same contract as the tables.

2. On a Claude Desktop profile (or any non-expanding client) the menu showed
   NOTHING, so it read as broken. Now a real stored secret always shows the
   item -- enabled on a client that expands references, or disabled with the
   reason ("unavailable for Claude Desktop -- it doesn't expand ${VAR}") so
   the gate is visible rather than silent. Applies to env, headers and args.

Not a change: after converting, env_ref_warnings still notes a variable that
isn't set in the environment. That's #82's advisory doing its job -- the user
runs the export line the dialog handed them; auto-adding a ':-default' would
bake a fallback back into the config and defeat moving the secret out.

Tests: +5 core (secret_arg_indices for token/flag-value/embedded-URL/
reference-excluded, suggested_env_var_for_arg with and without a flag).
483 passed, ruff clean. GUI wiring (context menus) remains untestable in CI.

Refs #83

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKwBecy6N83jnqQmw8ezwE
This commit is contained in:
2026-08-04 04:03:47 +00:00
co-authored by Claude Opus 4.8
parent 8fdbe90b37
commit 4743c4a995
4 changed files with 191 additions and 5 deletions
+32
View File
@@ -3148,3 +3148,35 @@ def test_is_env_var_set():
assert c.is_env_var_set("FOO", {"FOO": "x"}) is True
assert c.is_env_var_set("FOO", {"FOO": ""}) is False
assert c.is_env_var_set("FOO", {}) is False
# --------------------------------------------------------------------------- #
# Args secret indices + suggested var name (issue #83, args surface)
# --------------------------------------------------------------------------- #
def test_secret_arg_indices_flags_token_and_flag_value():
args = ["--port", "8080", "ghp_deadbeef", "--token", "sk-abc", "--flag=val"]
idxs = c.secret_arg_indices(args)
assert 2 in idxs # ghp_ token prefix
assert 4 in idxs # value following --token
assert 1 not in idxs # 8080
assert 5 not in idxs # --flag=val inline pair
def test_secret_arg_indices_flags_embedded_url_credentials():
args = ["postgres://user:pass@host/db"]
assert c.secret_arg_indices(args) == [0]
def test_secret_arg_indices_excludes_existing_references():
args = ["--token", "${GH_TOKEN}"]
assert c.secret_arg_indices(args) == []
def test_suggested_env_var_for_arg_uses_preceding_flag():
args = ["--api-key", "sk-secret"]
assert c.suggested_env_var_for_arg(args, 1) == "API_KEY"
def test_suggested_env_var_for_arg_falls_back_when_no_flag():
args = ["ghp_secret"]
assert c.suggested_env_var_for_arg(args, 0) == "SECRET"