Skip to content

feat(fred): /fred:auth stores the API key, and document updating - #44

Merged
walkerhughes merged 1 commit into
mainfrom
fred/auth
Aug 6, 2026
Merged

feat(fred): /fred:auth stores the API key, and document updating#44
walkerhughes merged 1 commit into
mainfrom
fred/auth

Conversation

@walkerhughes

Copy link
Copy Markdown
Owner

Two asks from #30 follow-up: a credential command for fred, and the update commands in the repo README.

/fred:auth

Opens your OS's own password prompt, verifies the key against FRED, writes it to ~/.fred-mcp/credentials.json at 0600. Re-running always overwrites, which is the rotation path.

The command does not collect the key itself, and that is the design. A key pasted into a conversation is in the transcript and in the context window, and neither is something you can rotate away. So commands/auth.md explicitly instructs the agent not to ask for it and to delegate to scripts/save-credentials.sh, which reads the prompt directly:

Platform Prompt
macOS osascript dialog, with hidden answer so the field is masked
Linux zenity --password, else kdialog --password
Neither masked read -s from /dev/tty
None available refuses, and prints the manual one-liner

Verification happens before the write, so a mistyped key during a rotation cannot overwrite a working one. The shape check (32 lowercase alphanumeric) is the same rule as client.py, asserted in both places so they cannot drift.

curl's stderr is discarded during verification, because its error text can echo the URL and the key is a query parameter in it.

Verified, not assumed

malformed key        -> rejected locally, no network, file untouched
empty input          -> cancelled, file untouched
well-formed but bad  -> FRED 400, refused, file untouched
real key             -> "verified against FRED", written 0600
re-run               -> overwrites cleanly

Round-tripped: resolve_api_key() reads what the script writes. The key does not appear in the script's stdout or stderr on any path, and a test asserts that by running the rejection path with a sentinel value.

The AppleScript is multi-line, which is a real hazard, so I checked it two ways: osacompile for syntax, then an actual render with giving up after 1 to confirm it displays from a non-interactive context. Both pass.

make check now runs the script's own --selftest, following the same pattern as tastytrade's scripts/calendars.py: shipped payload that lives outside src/ and that pytest therefore never imports.

299 tests, 98% coverage.

README

Merging to main publishes; what each machine needs is a cache refresh. Until claude plugin marketplace update walkerhughes runs, a newly added plugin is invisible locally however many times you try to install it, which is exactly what your fred install hit.

Also documents that install and update are not interchangeable, since install no-ops on an existing plugin and reads as "nothing happened". Folded two sentences that section already duplicated.

Note

FRED_API_KEY_STDIN=1 is an escape hatch for scripted setup, used by the tests. The slash command never sets it.

Two things.

/fred:auth opens the operating system's own password prompt, verifies the key
against FRED, and writes it to ~/.fred-mcp/credentials.json at 0600. Re-running
always overwrites, which is the rotation path.

The command deliberately does not collect the key itself. A key pasted into a
conversation is in the transcript and in the context window, and neither is
something a user can rotate away, so commands/auth.md instructs the agent not to
ask for it and delegates to scripts/save-credentials.sh, which reads the prompt
directly. osascript on macOS, zenity or kdialog on Linux, a masked terminal read
otherwise. Nothing the script writes to stdout contains the key, and a test runs
the rejection path to prove it.

Verification happens before the write, so a mistyped key during a rotation cannot
overwrite a working one. The shape check is the same rule as client.py, asserted
in both places so they cannot drift.

The root README gains an Updating section: refreshing the marketplace is what
makes a newly merged plugin visible at all, and install and update are not
interchangeable. Folded two sentences that were already duplicated in that
section.
@walkerhughes
walkerhughes merged commit 66c33e9 into main Aug 6, 2026
3 checks passed
@walkerhughes
walkerhughes deleted the fred/auth branch August 6, 2026 05:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant