simonw / ospeak

CLI tool for running text through OpenAI Text to speech
Apache License 2.0
161 stars 9 forks source link

Safe and pragmatic way to use API key #6

Open doomuch opened 10 months ago

doomuch commented 10 months ago

I am concerned about setting my openai API key as global env. WIl other packages be able to use it without my confirmation?

I like how llm manages it with "llm keys set openai". It would be great to have here the same.

kquinsland commented 10 months ago

WIl other packages be able to use it without my confirmation?

They could do this with llm as well since the key is just stored in plain text json file on disk. You could wrap llm and ospeak with an alias that also pulls in the API key from a password manager if you're that concerned. Here's an example of how this can work with 1password

Having said all that...

It would be great to have here the same.

Agreed. Here's where llm implements the keys command: https://github.com/simonw/llm/blob/main/llm/cli.py#L470. I've not got any spare time to fork and add but it shouldn't be that difficult for somebody with a spare hour or two.

simonw commented 10 months ago

Yeah I'm not a huge fan of the environment variable thing either.

I'd like to make this double as an LLM plugin - if you install both this and LLM in the same environment you could run llm ospeak - at which point it would make sense for it to optionally use the LLM API key as well.

This tool uses a different incompatible version of the OpenAI Python library though so I'd need to fix that first:

mislav commented 10 months ago

With something like 1password-cli, or basically anything that allows fetching a credential from the command line, there is a simple workaround for interactive shells: making a function wrapper.

ospeak() {
  local token="${OPENAI_API_KEY:-}"
  if [[ -z $token && $1 != "--help" && $1 != "--version" ]]; then 
    token="$(op read 'op://Personal/OpenAI API Key/api key')" || return 1
  fi
  OPENAI_API_KEY="$token" command ospeak "$@"
}

Explanation: the op command (1password-cli) reads the 'api key' field from the item named "OpenAI API Key" in my Personal vault.

With this approach, OPENAI_API_KEY is only ever 1) fetched on-demand, and 2) exported to the ospeak command and nothing else. This is essentially the same mechanism that enables 1Password shell plugins, with a small difference that their plugins work by defining shell aliases instead of functions. 1Password shell plugins, however, are only available for a predefined set of tools that specific plugins exist for, so they are useless for a small, newer utility like ospeak.

The downside, of course, is that every ospeak invocation will now have a small overhead. On my machine, the op command takes about 0.75s to fetch an API key from a vault.

With macOS Keychain instead of 1Password, for example, the command to fetch the credential could be as following:

security find-generic-password -s api.openai.com -w