docs(cli): document the CLI config file and token.cmd
README, root and config help, and the agent skill describe the file, its precedence and how agents should use ERMITE_TOKEN or their own ERMITE_CONFIG.
- commit
- 6f11e7dd78166a0ff6e7fde885993f3ecefa7dd0
- author
- brice · · 2026-10-12 15:00 UTC
- parent
- fc19772
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +47 -6
M README.md +25 -1
| @@ -28,12 +28,35 @@ ermite serve | |||
| 28 | 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | |
| 29 | 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | |
| 30 | 30 | ||
| 31 | - | `ermite version` prints the version (and the server's when `ERMITE_URL` is set). | |
| 31 | + | `ermite version` prints the version (and the server's when a server URL is configured, see Remote CLI). | |
| 32 | 32 | ||
| 33 | 33 | ## Remote CLI | |
| 34 | 34 | ||
| 35 | 35 | The same binary drives a running server over its API. It reads `ERMITE_URL` (the server address, `http://` or `https://`) and `ERMITE_TOKEN` (a bearer token; optional for public reads). A token sent over plain `http://` to a host that is not loopback triggers a warning on every call. | |
| 36 | 36 | ||
| 37 | + | Each setting comes from the environment or from a config file, the environment first: | |
| 38 | + | ||
| 39 | + | | Setting | Environment | Config file key | | |
| 40 | + | |---|---|---| | |
| 41 | + | | server address | `ERMITE_URL` | `url` | | |
| 42 | + | | bearer token (optional for public reads) | `ERMITE_TOKEN` | `token.cmd`, a command that prints it | | |
| 43 | + | ||
| 44 | + | The file is `~/.config/ermite/config` (`$XDG_CONFIG_HOME/ermite/config` when set; `ERMITE_CONFIG` names another file). It holds no secret: `token.cmd` is a command line that fetches the token from your secret manager (pass, gopass, op, bw, secret-tool, …). | |
| 45 | + | ||
| 46 | + | ```ini | |
| 47 | + | # ~/.config/ermite/config | |
| 48 | + | url = https://git.example.com | |
| 49 | + | token.cmd = pass show ermite/token | |
| 50 | + | ``` | |
| 51 | + | ||
| 52 | + | ```sh | |
| 53 | + | ermite config set url https://git.example.com | |
| 54 | + | ermite config set token.cmd 'pass show ermite/token' # or: gopass show -o ermite/token, op read op://vault/ermite/token, secret-tool lookup service ermite | |
| 55 | + | ermite config show # path and stored values; unset removes a key | |
| 56 | + | ``` | |
| 57 | + | ||
| 58 | + | `token.cmd` runs with `sh -c` (`cmd /C` on Windows) only when a command needs the token, at most once per run, with your environment and terminal (so a pinentry or biometric prompt works). It must print the token alone on one line, in under 4 KiB, within 60 seconds. It is not run when `ERMITE_TOKEN` is set, nor by `ermite version` or `ermite config`, and not when the config file is writable by other users. A failure is reported in one line without its output. A token sent over plain `http://` to a host that is not loopback triggers a warning on every call. | |
| 59 | + | ||
| 37 | 60 | ```sh | |
| 38 | 61 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | |
| 39 | 62 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | |
| @@ -45,6 +68,7 @@ ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <rep | |||
| 45 | 68 | ermite web login # prints a one-time sign-in link (60 s) | |
| 46 | 69 | ermite web logout --all # signs every browser out | |
| 47 | 70 | ermite signing-key show # public key and allowed_signers line of squash commits | |
| 71 | + | ermite config show | set <url|token.cmd> <value> | unset <key> # the CLI config file | |
| 48 | 72 | ermite version # client and server, warns about missing server features | |
| 49 | 73 | ermite watch [repo] [--json] # follows events, reconnects and resumes | |
| 50 | 74 | ``` | |
| @@ -28,12 +28,35 @@ ermite serve | |||||
| 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | ||
| 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | ||
| 30 | 30 | ||||
| 31 | - | `ermite version` prints the version (and the server's when `ERMITE_URL` is set). | 31 | + | `ermite version` prints the version (and the server's when a server URL is configured, see Remote CLI). |
| 32 | 32 | ||||
| 33 | ## Remote CLI | 33 | ## Remote CLI | ||
| 34 | 34 | ||||
| 35 | The same binary drives a running server over its API. It reads `ERMITE_URL` (the server address, `http://` or `https://`) and `ERMITE_TOKEN` (a bearer token; optional for public reads). A token sent over plain `http://` to a host that is not loopback triggers a warning on every call. | 35 | The same binary drives a running server over its API. It reads `ERMITE_URL` (the server address, `http://` or `https://`) and `ERMITE_TOKEN` (a bearer token; optional for public reads). A token sent over plain `http://` to a host that is not loopback triggers a warning on every call. | ||
| 36 | 36 | ||||
| 37 | + | Each setting comes from the environment or from a config file, the environment first: | |||
| 38 | + | ||||
| 39 | + | | Setting | Environment | Config file key | | |||
| 40 | + | |---|---|---| | |||
| 41 | + | | server address | `ERMITE_URL` | `url` | | |||
| 42 | + | | bearer token (optional for public reads) | `ERMITE_TOKEN` | `token.cmd`, a command that prints it | | |||
| 43 | + | ||||
| 44 | + | The file is `~/.config/ermite/config` (`$XDG_CONFIG_HOME/ermite/config` when set; `ERMITE_CONFIG` names another file). It holds no secret: `token.cmd` is a command line that fetches the token from your secret manager (pass, gopass, op, bw, secret-tool, …). | |||
| 45 | + | ||||
| 46 | + | ```ini | |||
| 47 | + | # ~/.config/ermite/config | |||
| 48 | + | url = https://git.example.com | |||
| 49 | + | token.cmd = pass show ermite/token | |||
| 50 | + | ``` | |||
| 51 | + | ||||
| 52 | + | ```sh | |||
| 53 | + | ermite config set url https://git.example.com | |||
| 54 | + | ermite config set token.cmd 'pass show ermite/token' # or: gopass show -o ermite/token, op read op://vault/ermite/token, secret-tool lookup service ermite | |||
| 55 | + | ermite config show # path and stored values; unset removes a key | |||
| 56 | + | ``` | |||
| 57 | + | ||||
| 58 | + | `token.cmd` runs with `sh -c` (`cmd /C` on Windows) only when a command needs the token, at most once per run, with your environment and terminal (so a pinentry or biometric prompt works). It must print the token alone on one line, in under 4 KiB, within 60 seconds. It is not run when `ERMITE_TOKEN` is set, nor by `ermite version` or `ermite config`, and not when the config file is writable by other users. A failure is reported in one line without its output. A token sent over plain `http://` to a host that is not loopback triggers a warning on every call. | |||
| 59 | + | ||||
| 37 | ```sh | 60 | ```sh | ||
| 38 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | 61 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | ||
| 39 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | 62 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | ||
| @@ -45,6 +68,7 @@ ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <rep | |||||
| 45 | ermite web login # prints a one-time sign-in link (60 s) | 68 | ermite web login # prints a one-time sign-in link (60 s) | ||
| 46 | ermite web logout --all # signs every browser out | 69 | ermite web logout --all # signs every browser out | ||
| 47 | ermite signing-key show # public key and allowed_signers line of squash commits | 70 | ermite signing-key show # public key and allowed_signers line of squash commits | ||
| 71 | + | ermite config show | set <url|token.cmd> <value> | unset <key> # the CLI config file | |||
| 48 | ermite version # client and server, warns about missing server features | 72 | ermite version # client and server, warns about missing server features | ||
| 49 | ermite watch [repo] [--json] # follows events, reconnects and resumes | 73 | ermite watch [repo] [--json] # follows events, reconnects and resumes | ||
| 50 | ``` | 74 | ``` | ||
M internal/app/cli_config_test.go +9 -0
| @@ -127,3 +127,12 @@ func TestConfigHelpShowsAnExample(t *testing.T) { | |||
| 127 | 127 | assert.Contains(t, res.stdout, "pass show ermite/token") | |
| 128 | 128 | assert.Contains(t, res.stdout, "https://git.example.com") | |
| 129 | 129 | } | |
| 130 | + | func TestConfigHelpDocumentsTheFile(t *testing.T) { | |
| 131 | + | res := runCLI(t, nil, "", "--help").ok(t) | |
| 132 | + | for _, s := range []string{"ERMITE_URL", "ERMITE_TOKEN", "token.cmd", "ERMITE_CONFIG", "ermite config"} { | |
| 133 | + | assert.Contains(t, res.stdout, s) | |
| 134 | + | } | |
| 135 | + | res = runCLI(t, nil, "", "config", "--help").ok(t) | |
| 136 | + | assert.Contains(t, res.stdout, "pass show ermite/token") | |
| 137 | + | assert.Contains(t, res.stdout, "https://git.example.com") | |
| 138 | + | } | |
| @@ -127,3 +127,12 @@ func TestConfigHelpShowsAnExample(t *testing.T) { | |||||
| 127 | assert.Contains(t, res.stdout, "pass show ermite/token") | 127 | assert.Contains(t, res.stdout, "pass show ermite/token") | ||
| 128 | assert.Contains(t, res.stdout, "https://git.example.com") | 128 | assert.Contains(t, res.stdout, "https://git.example.com") | ||
| 129 | } | 129 | } | ||
| 130 | + | func TestConfigHelpDocumentsTheFile(t *testing.T) { | |||
| 131 | + | res := runCLI(t, nil, "", "--help").ok(t) | |||
| 132 | + | for _, s := range []string{"ERMITE_URL", "ERMITE_TOKEN", "token.cmd", "ERMITE_CONFIG", "ermite config"} { | |||
| 133 | + | assert.Contains(t, res.stdout, s) | |||
| 134 | + | } | |||
| 135 | + | res = runCLI(t, nil, "", "config", "--help").ok(t) | |||
| 136 | + | assert.Contains(t, res.stdout, "pass show ermite/token") | |||
| 137 | + | assert.Contains(t, res.stdout, "https://git.example.com") | |||
| 138 | + | } | |||
M internal/app/cli_repo.go +1 -1
| @@ -86,7 +86,7 @@ func newRepoCmd(env *cliEnv) *cobra.Command { | |||
| 86 | 86 | repo := &cobra.Command{ | |
| 87 | 87 | Use: "repo", | |
| 88 | 88 | Short: "Manage repositories on a remote server", | |
| 89 | - | Long: "Manage repositories on the server at ERMITE_URL, authenticated with ERMITE_TOKEN.", | |
| 89 | + | Long: "Manage repositories on the configured server (ERMITE_URL or the config file, see `ermite config`), authenticated with ERMITE_TOKEN or the token command of the config file.", | |
| 90 | 90 | } | |
| 91 | 91 | repo.AddCommand( | |
| 92 | 92 | newRepoListCmd(env), newRepoShowCmd(env), newRepoCreateCmd(env), | |
| @@ -86,7 +86,7 @@ func newRepoCmd(env *cliEnv) *cobra.Command { | |||||
| 86 | repo := &cobra.Command{ | 86 | repo := &cobra.Command{ | ||
| 87 | Use: "repo", | 87 | Use: "repo", | ||
| 88 | Short: "Manage repositories on a remote server", | 88 | Short: "Manage repositories on a remote server", | ||
| 89 | - | Long: "Manage repositories on the server at ERMITE_URL, authenticated with ERMITE_TOKEN.", | 89 | + | Long: "Manage repositories on the configured server (ERMITE_URL or the config file, see `ermite config`), authenticated with ERMITE_TOKEN or the token command of the config file.", |
| 90 | } | 90 | } | ||
| 91 | repo.AddCommand( | 91 | repo.AddCommand( | ||
| 92 | newRepoListCmd(env), newRepoShowCmd(env), newRepoCreateCmd(env), | 92 | newRepoListCmd(env), newRepoShowCmd(env), newRepoCreateCmd(env), | ||
M internal/app/root.go +7 -2
| @@ -18,8 +18,13 @@ func newRootCmdWith(env *cliEnv) *cobra.Command { | |||
| 18 | 18 | Short: "A single-user Git forge", | |
| 19 | 19 | Long: "ermite is a single-user Git forge: `ermite serve` runs the server, the other commands\n" + | |
| 20 | 20 | "drive a running server over its API.\n\n" + | |
| 21 | - | "Remote commands read ERMITE_URL (the server address, http:// or https://) and\n" + | |
| 22 | - | "ERMITE_TOKEN (a bearer token, see `ermite token create`) from the environment.", | |
| 21 | + | "Remote commands need the server address (http:// or https://) and, except for public reads,\n" + | |
| 22 | + | "a bearer token (see `ermite token create`). Each comes from, in order:\n\n" + | |
| 23 | + | " ERMITE_URL / ERMITE_TOKEN the environment\n" + | |
| 24 | + | " url / token.cmd in ~/.config/ermite/config the config file (ERMITE_CONFIG selects another)\n\n" + | |
| 25 | + | "token.cmd is a command line that prints the token, run only when a command needs it, so the\n" + | |
| 26 | + | "token stays in your secret manager (pass, gopass, op, bw, secret-tool…), never in the file.\n" + | |
| 27 | + | "See `ermite config --help`.", | |
| 23 | 28 | SilenceUsage: true, | |
| 24 | 29 | SilenceErrors: true, // cmd/ermite prints the error once | |
| 25 | 30 | } | |
| @@ -18,8 +18,13 @@ func newRootCmdWith(env *cliEnv) *cobra.Command { | |||||
| 18 | Short: "A single-user Git forge", | 18 | Short: "A single-user Git forge", | ||
| 19 | Long: "ermite is a single-user Git forge: `ermite serve` runs the server, the other commands\n" + | 19 | Long: "ermite is a single-user Git forge: `ermite serve` runs the server, the other commands\n" + | ||
| 20 | "drive a running server over its API.\n\n" + | 20 | "drive a running server over its API.\n\n" + | ||
| 21 | - | "Remote commands read ERMITE_URL (the server address, http:// or https://) and\n" + | 21 | + | "Remote commands need the server address (http:// or https://) and, except for public reads,\n" + |
| 22 | - | "ERMITE_TOKEN (a bearer token, see `ermite token create`) from the environment.", | 22 | + | "a bearer token (see `ermite token create`). Each comes from, in order:\n\n" + |
| 23 | + | " ERMITE_URL / ERMITE_TOKEN the environment\n" + | |||
| 24 | + | " url / token.cmd in ~/.config/ermite/config the config file (ERMITE_CONFIG selects another)\n\n" + | |||
| 25 | + | "token.cmd is a command line that prints the token, run only when a command needs it, so the\n" + | |||
| 26 | + | "token stays in your secret manager (pass, gopass, op, bw, secret-tool…), never in the file.\n" + | |||
| 27 | + | "See `ermite config --help`.", | |||
| 23 | SilenceUsage: true, | 28 | SilenceUsage: true, | ||
| 24 | SilenceErrors: true, // cmd/ermite prints the error once | 29 | SilenceErrors: true, // cmd/ermite prints the error once | ||
| 25 | } | 30 | } | ||
M skills/ermite/CLI.md +3 -2
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | # ermite CLI reference | |
| 2 | 2 | ||
| 3 | - | Remote commands read `ERMITE_URL` (`http://` or `https://`) and `ERMITE_TOKEN` (optional for public reads). Run `ermite <command> --help` for the authoritative text of a command and its JSON fields. | |
| 3 | + | Remote commands read `ERMITE_URL` (`http://` or `https://`) and `ERMITE_TOKEN` (optional for public reads); they fall back to the `url` and `token.cmd` of the config file (`~/.config/ermite/config`, or the file named by `ERMITE_CONFIG`), the environment first. Agents: use `ERMITE_TOKEN`, or your own `ERMITE_CONFIG` file; do not edit the owner's `~/.config/ermite/config` and never run `token.cmd` yourself or print what it prints. Run `ermite <command> --help` for the authoritative text of a command and its JSON fields. | |
| 4 | 4 | ||
| 5 | 5 | Errors are one line on standard error (`ermite: ...`) with exit status 1. `--json` prints stable field names. `ermite completion bash|zsh|fish` completes repository names from the server. | |
| 6 | 6 | ||
| @@ -77,7 +77,8 @@ Merge request JSON: `repo`, `number`, `title`, `description`, `source_branch`, ` | |||
| 77 | 77 | | Command | Notes | | |
| 78 | 78 | |---|---| | |
| 79 | 79 | | `serve` | configured by `ERMITE_*` environment variables only (see the project README) | | |
| 80 | - | | `version [--json]` | client version; with `ERMITE_URL`, the server version and features | | |
| 80 | + | | `version [--json]` | client version; with a server URL (`ERMITE_URL` or the config file), the server version and features | | |
| 81 | + | | `config show` / `config set <url\|token.cmd> <value>` / `config unset <key>` | the CLI config file; `token.cmd` is a command that prints the token (any secret manager), never the token itself; `show` never runs it | | |
| 81 | 82 | | `signing-key show` | public key and `allowed_signers` line to check squash commits with `git verify-commit` | | |
| 82 | 83 | | `web login [--json]` | one-time sign-in link (about 60 s); needs the admin token or a token with every scope and no repository list; the link is a secret | | |
| 83 | 84 | | `web logout --all` | signs every browser out | | |
| @@ -1,6 +1,6 @@ | |||||
| 1 | # ermite CLI reference | 1 | # ermite CLI reference | ||
| 2 | 2 | ||||
| 3 | - | Remote commands read `ERMITE_URL` (`http://` or `https://`) and `ERMITE_TOKEN` (optional for public reads). Run `ermite <command> --help` for the authoritative text of a command and its JSON fields. | 3 | + | Remote commands read `ERMITE_URL` (`http://` or `https://`) and `ERMITE_TOKEN` (optional for public reads); they fall back to the `url` and `token.cmd` of the config file (`~/.config/ermite/config`, or the file named by `ERMITE_CONFIG`), the environment first. Agents: use `ERMITE_TOKEN`, or your own `ERMITE_CONFIG` file; do not edit the owner's `~/.config/ermite/config` and never run `token.cmd` yourself or print what it prints. Run `ermite <command> --help` for the authoritative text of a command and its JSON fields. |
| 4 | 4 | ||||
| 5 | Errors are one line on standard error (`ermite: ...`) with exit status 1. `--json` prints stable field names. `ermite completion bash|zsh|fish` completes repository names from the server. | 5 | Errors are one line on standard error (`ermite: ...`) with exit status 1. `--json` prints stable field names. `ermite completion bash|zsh|fish` completes repository names from the server. | ||
| 6 | 6 | ||||
| @@ -77,7 +77,8 @@ Merge request JSON: `repo`, `number`, `title`, `description`, `source_branch`, ` | |||||
| 77 | | Command | Notes | | 77 | | Command | Notes | | ||
| 78 | |---|---| | 78 | |---|---| | ||
| 79 | | `serve` | configured by `ERMITE_*` environment variables only (see the project README) | | 79 | | `serve` | configured by `ERMITE_*` environment variables only (see the project README) | | ||
| 80 | - | | `version [--json]` | client version; with `ERMITE_URL`, the server version and features | | 80 | + | | `version [--json]` | client version; with a server URL (`ERMITE_URL` or the config file), the server version and features | |
| 81 | + | | `config show` / `config set <url\|token.cmd> <value>` / `config unset <key>` | the CLI config file; `token.cmd` is a command that prints the token (any secret manager), never the token itself; `show` never runs it | | |||
| 81 | | `signing-key show` | public key and `allowed_signers` line to check squash commits with `git verify-commit` | | 82 | | `signing-key show` | public key and `allowed_signers` line to check squash commits with `git verify-commit` | | ||
| 82 | | `web login [--json]` | one-time sign-in link (about 60 s); needs the admin token or a token with every scope and no repository list; the link is a secret | | 83 | | `web login [--json]` | one-time sign-in link (about 60 s); needs the admin token or a token with every scope and no repository list; the link is a secret | | ||
| 83 | | `web logout --all` | signs every browser out | | 84 | | `web logout --all` | signs every browser out | | ||
M skills/ermite/SKILL.md +2 -0
| @@ -16,6 +16,8 @@ ermite version # client and server versions, warns about | |||
| 16 | 16 | ermite repo list | |
| 17 | 17 | ``` | |
| 18 | 18 | ||
| 19 | + | Instead of exporting them, the owner can keep `url` and a `token.cmd` (a command that prints the token, such as `pass show ermite/token`) in `~/.config/ermite/config`; as an agent, set `ERMITE_TOKEN` or point `ERMITE_CONFIG` at your own file. | |
| 20 | + | ||
| 19 | 21 | Never print a token, commit it, log it or paste it into a chat. Prefer a Git credential helper to a token in a URL. Use `https://`: a token sent over plain `http://` to a remote host triggers a warning. | |
| 20 | 22 | ||
| 21 | 23 | ## Git | |
| @@ -16,6 +16,8 @@ ermite version # client and server versions, warns about | |||||
| 16 | ermite repo list | 16 | ermite repo list | ||
| 17 | ``` | 17 | ``` | ||
| 18 | 18 | ||||
| 19 | + | Instead of exporting them, the owner can keep `url` and a `token.cmd` (a command that prints the token, such as `pass show ermite/token`) in `~/.config/ermite/config`; as an agent, set `ERMITE_TOKEN` or point `ERMITE_CONFIG` at your own file. | |||
| 20 | + | ||||
| 19 | Never print a token, commit it, log it or paste it into a chat. Prefer a Git credential helper to a token in a URL. Use `https://`: a token sent over plain `http://` to a remote host triggers a warning. | 21 | Never print a token, commit it, log it or paste it into a chat. Prefer a Git credential helper to a token in a URL. Use `https://`: a token sent over plain `http://` to a remote host triggers a warning. | ||
| 20 | 22 | ||||
| 21 | ## Git | 23 | ## Git | ||