detached 6f11e7d

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +47 -6
M README.md +25 -1
@@ -28,12 +28,35 @@ ermite serve
2828 | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges |
2929 | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges |
3030
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).
3232
3333 ## Remote CLI
3434
3535 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.
3636
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+
3760 ```sh
3861 export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_...
3962 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
4568 ermite web login # prints a one-time sign-in link (60 s)
4669 ermite web logout --all # signs every browser out
4770 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
4872 ermite version # client and server, warns about missing server features
4973 ermite watch [repo] [--json] # follows events, reconnects and resumes
5074 ```
@@ -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 CLI33 ## 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 ```sh60 ```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 out69 ermite web logout --all # signs every browser out
47 ermite signing-key show # public key and allowed_signers line of squash commits70 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 features72 ermite version # client and server, warns about missing server features
49 ermite watch [repo] [--json] # follows events, reconnects and resumes73 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) {
127127 assert.Contains(t, res.stdout, "pass show ermite/token")
128128 assert.Contains(t, res.stdout, "https://git.example.com")
129129 }
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 {
8686 repo := &cobra.Command{
8787 Use: "repo",
8888 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.",
9090 }
9191 repo.AddCommand(
9292 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 {
1818 Short: "A single-user Git forge",
1919 Long: "ermite is a single-user Git forge: `ermite serve` runs the server, the other commands\n" +
2020 "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`.",
2328 SilenceUsage: true,
2429 SilenceErrors: true, // cmd/ermite prints the error once
2530 }
@@ -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 once29 SilenceErrors: true, // cmd/ermite prints the error once
25 }30 }
M skills/ermite/CLI.md +3 -2
@@ -1,6 +1,6 @@
11 # ermite CLI reference
22
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.
44
55 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.
66
@@ -77,7 +77,8 @@ Merge request JSON: `repo`, `number`, `title`, `description`, `source_branch`, `
7777 | Command | Notes |
7878 |---|---|
7979 | `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 |
8182 | `signing-key show` | public key and `allowed_signers` line to check squash commits with `git verify-commit` |
8283 | `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 |
8384 | `web logout --all` | signs every browser out |
@@ -1,6 +1,6 @@
1 # ermite CLI reference1 # 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
1616 ermite repo list
1717 ```
1818
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+
1921 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.
2022
2123 ## Git
@@ -16,6 +16,8 @@ ermite version # client and server versions, warns about
16 ermite repo list16 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 ## Git23 ## Git

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 45

↑↓ moveenter openesc close