Description
What is missing
The GUI settings page (Model services tab, emrg/gui/renderer/src/components/SettingsPanel.tsx) offers exactly three fields for a model endpoint: API key, base URL and default model. Nothing else. A user who wants to use aitokenpool must first open its website, register an account, copy an API key, and then hand-fill all three fields.
aitokenpool already supports registering and signing in with a GitHub identity — an account can be opened without an email address — through a standard OAuth device flow. EMRG exposes no entry point for it, so that capability is unreachable from inside the only non-terminal surface this project has.
The failure mode is the reason this matters more than convenience: any one of the three fields being wrong (trailing /v1, a stale key, a model name the endpoint does not serve) presents as "the model does not answer". Diagnosing that costs far more than configuring it.
What is expected
One action in the settings page: click a button, authorize in the system browser, come back, and the API key, service address and default model are already configured and ready to use.
Requirements
- The Model services tab gains an aitokenpool account block above the existing model fields: when not signed in, one line of explanation plus a Sign in with GitHub button.
- The button opens a dialog showing a large one-time code (copyable), an Open browser action and a waiting for authorization state; the authorization URL the server returned is opened in the system browser.
- Poll at the interval the server returns. A
429must be honoured silently by switching to the new interval — the user sees nothing. - On success, the new key is first exercised against the protected
GET /api/models; a non-200 response means nothing is written and an error is shown. - Only after that probe passes is
[llm]written:base_url = <service address>/v1,api_key, andmodel = deepseek-flash;context_windowis taken from the server's model list. - If
[[llm.models]]already carries an entry nameddeepseek-flash, only itscontext_windowis updated — no second entry is appended. - Every other key in
[llm]— including hand-added ones such asbase_url_1orapi_key3— and every other section is preserved verbatim. Not one byte of them changes. - When
api_keyis already non-empty, the write is preceded by a confirmation that this replaces the key and address currently in use; cancel means zero writes. - On success the dialog closes, the three fields are filled into the existing form and saved, and the confirmation names the GitHub user and the model that is now ready.
- The service address is switchable: default
https://aitokenpool.args.fun, alternativelyhttps://atp-dev.shenbiai.net. Changing the address does not trigger a login. - Each of the four failures says what to do next — service not enabled (
503), code expired (410), user denied (403), service unreachable. A bare "failed" is not an answer. - Not to be built: signed-in state detection and a disconnect button. There is no reliable way to derive an identity from a key, and a fabricated state indicator is worse than none.
- An already-connected TUI session needs no login entry, but a config change must show the new model name immediately — never a restart.
Server contract
Already deployed; the field names and status codes below are fixed and must not be changed by this work.
POST /api/auth/github/device/code→200 {device_code, user_code, verification_uri, expires_in, interval}; when the deployment has GitHub login switched off,503 github_login_not_enabled.POST /api/auth/github/device/tokenwith{device_code}→200 {api_key, user_id, login}; intermediate states carry202 authorization_pending,429 slow_down,410 expired_token,403 access_denied.
Acceptance
- Renderer test: click produces a code → polling succeeds → the filled-in field values are correct; each of the four failure states matches its own message.
- On cancelling the overwrite confirmation the write function is called zero times (assert the call count, not that the file's contents are unchanged).
- Write test: a
[llm]preseeded with custom keys such asbase_url_1andapi_key3keeps them verbatim after the write. - Write test: an existing
[[llm.models]]entry of the same name is not duplicated — only itscontext_windowchanges. - The whole flow runs against a fake upstream and never leaves the machine; the positive control is: a
200from the fake upstream really writes, a202really writes nothing. - Mutation check: turning "confirm before overwrite" into a direct overwrite must redden a test; rewriting the whole
[llm]section instead of preserving keys must redden a test. - Daemon side: a change to
[llm] modeltakes effect within 2 seconds and is broadcast to connected clients. - Green on all three CI platforms (ubuntu / windows / macos).
Where the work lives
emrg/gui/renderer/src/components/— the settings section and the login dialog (the existingGithubDeviceDialog.tsxis the shape to follow).emrg/gui/main.js+emrg/gui/preload.js— the renderer has no network access (sandboxed,contextIsolation), so the HTTP calls and the config write belong to the main process, reached through a whitelisted IPC channel.~/.emrg/config.toml—[llm]and[[llm.models]]; the existingemrg:saveSettingshandler already merges rather than replaces.
Activity
argszero commentedon Nov 9, 2026
Handled by #2775
argszero commentedon Nov 9, 2026
On acceptance item 8: this repository's CI runs two legs, not three.
.github/workflows/test.ymlhastest(ubuntu) andtest-windows; macOS is exercised only by the tag-triggeredbuild-release.yml, which no pull request can run. So item 8 is read as met when both PR legs are green, which they are on head59ad6b47(run 42642395006:test2m54s,test-windows5m33s) — the third platform is covered by the release build, not by CI on a PR.