Skip to content

Setting up a project ​

A project is one tenant plus the credentials to reach it. You can configure as many as you like and switch between them; commands act on the active one unless you pass --project.

Interactively ​

sh
$ fft project add prod
Project name: prod
Base URL (e.g. https://acme.api.fulfillmenttools.com): https://ocff-acme-pre.api.fulfillmenttools.com
fulfillmenttools API key: AIzaSy…
fulfillmenttools project id: acme
Environment (pre or prd): pre
Username (login name): jane.doe
Password:
Project "prod" added and is now active.

The password is masked as you type, never passed as a flag, and never written to your shell history. A value you already gave as a flag is not asked for again, so you can mix the two freely.

Non-interactively (scripts, provisioning) ​

There is deliberately no --password flag — it would land in your shell history and in ps output. Use --password-stdin:

sh
echo "$PASSWORD" | fft project add prod \
  --base-url   https://ocff-acme-pre.api.fulfillmenttools.com \
  --api-key    "$FIREBASE_WEB_API_KEY" \
  --project-id acme \
  --env        pre \
  --username   jane.doe \
  --password-stdin

The API key is sensitive too — it goes to the keychain, never to the config file. To keep it off the command line as well, pipe it in with --api-key-stdin. Together with --password-stdin, stdin holds the key on its first line and the password after it:

sh
printf '%s\n%s' "$FIREBASE_WEB_API_KEY" "$PASSWORD" | fft project add prod \
  --base-url   https://ocff-acme-pre.api.fulfillmenttools.com \
  --project-id acme \
  --env        pre \
  --username   jane.doe \
  --api-key-stdin --password-stdin

Pass --email jane.doe@ocff-acme-pre.com instead of --username/--project-id/--env if you already know the full address. --force overwrites an existing project of the same name.

Working with several projects ​

sh
fft project list                    # * marks the active one
fft project use staging             # switch
fft project current                 # which am I on?
fft facility list --project prod    # one command against another project
fft project remove staging          # forgets it, and wipes its keychain entries
$ fft project list
NAME        BASE URL                                        EMAIL                        CREDENTIAL
* prod      https://ocff-acme-pre.api.fulfillmenttools.com  jane.doe@ocff-acme-pre.com   keyring
  staging   https://ocff-acme-stg.api.fulfillmenttools.com  jane.doe@ocff-acme-stg.com   keyring

Where things are kept ​

  • Secrets (password, refresh token, ID token) → your OS keychain, one entry each.
  • Templates (saved request bodies) → ~/.local/share/fft/templates, mode 0600, or ./.fft/templates for the ones a repository commits. See Request templates.
  • Request history (what fft history shows) → ~/.local/state/fft/history.jsonl, mode 0600. See Request history below.
  • Everything else (name, base URL, email, active project) → ~/.config/fft/config.yaml, mode 0600. Plain YAML; safe to read, edit, and commit to a dotfiles repo — it contains no secrets. One caveat if you sync it: settings.noKeyring below is not a secret but it is a security posture, and it travels with the file. fft says so on any run where the config, rather than you, put it in cleartext.

No keychain available (WSL, headless Linux, a container)? --no-keyring / FFT_NO_KEYRING=1 falls back to a 0600 file, and settings.noKeyring in the config file makes that permanent so you never type the flag again:

yaml
settings:
  output: table
  noKeyring: true

fft tells you when there is no keychain to talk to, rather than failing with a D-Bus error — and on an interactive fft project add it offers the fallback there and then, and remembers your answer. See WSL and headless Linux: there is no Secret Service. On Windows that 0600 buys you less than it looks like, see On Windows, --no-keyring protects less than 0600 suggests. In CI, skip projects entirely — see CI and headless use.

Request history ​

fft records every command that sent an API operation — typed in a shell or sent from fft tui — so that fft history list can show what ran and fft history top which operations you reach for most. The requests fft tui makes on its own, such as reading your roles, are not recorded:

sh
fft history list --limit 10     # the latest requests in the current project
fft history list --all-projects # ... and in every project
fft history top                 # the most used operations in the current project
fft history top --all-projects -o json
fft history clear               # asks first; --yes in a script

An entry holds the time, the project name, the operation, the command with its flags, the HTTP status, the exit code and the duration. It never holds a request or response body, and inline --data bodies, header values, template values, credential-shaped flags and flags or query parameters named like a person's data (--email, --query email=…, --search-term, …) are stored as <redacted>; the full list is in What history records. The file is $XDG_STATE_HOME/fft/history.jsonl (by default ~/.local/state/fft/history.jsonl), mode 0600; it is trimmed to its newest half once it passes 1 MiB, and it never leaves the machine. It does name your projects, and keeps the ids, file names and other values you passed on the command line as typed, since reopening a request needs them; that is worth knowing before you share a home directory.

Recording is switched off:

  • by settings.noHistory: true in the config file,

    yaml
    settings:
      noHistory: true
  • by FFT_HISTORY=off, everywhere — the environment wins over the config file. on, true, yes, 1 and t switch recording on; any other non-empty value switches it off, so a typo does not quietly start recording. An empty value counts as unset;

  • in headless mode (FFT_BASE_URL and friends), unless FFT_HISTORY=on asks for it: a CI job's history is a file on a runner nobody reads. A run whose --project names a configured project is not headless, even with FFT_* exported, and is recorded.

Whether or not an entry can be written never changes what a command prints or how it exits.

Shell completion ​

sh
fft completion zsh  > "${fpath[1]}/_fft"          # zsh
fft completion bash > /etc/bash_completion.d/fft  # bash
fft completion fish > ~/.config/fish/completions/fft.fish

An independent open-source project — not affiliated with, endorsed by, or supported by fulfillmenttools.