diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d9f146..5f6f6a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ Conventional Commits by release-please. ## Unreleased +- Connect OpenRouter with one click, without copying a key: "Connect OpenRouter (free)" is + now the first choice on the welcome page, and Settings, Word lookups has a "Connect + OpenRouter" button. OpenRouter's sign-in opens in a new tab; when you're done, it sends + you back to kotiko.org/connect/, Kotiko gets your key, and the page you started from + says you're connected. Pasting a key still works ("Paste a key instead"). + +- Self-hosted server: other accounts on the same computer can no longer read your words. + The database (with the `-wal` and `-shm` files next to it), the model list cache and a + new data folder are now readable only by you, like the API token and the backups + already were. At start, the server makes these files private again if an older version + left them readable, and logs what it changed. It leaves a data folder that also holds + other files as it is and warns with the command to fix it. `run.sh` and the service + from `install-service.sh` now start the server with umask `077`; run + `install-service.sh` again to update the service. + - Self-hosted server: your keys can now live in their own files instead of `.env`. Set `LLM_API_KEY_FILE=/path/to/file` in place of `LLM_API_KEY=...`, and the same for `API_TOKEN`, `TELEGRAM_BOT_TOKEN` and `TRANSCRIBE_API_KEY`; this is how Docker secrets @@ -31,7 +46,7 @@ Conventional Commits by release-please. policy, published from the same file the extension shows so the two can't differ. The site sets no cookies, has no analytics and loads nothing from any other site. It also has the page OpenRouter returns to after "Connect OpenRouter"; Kotiko reads the sign-in - code there and nowhere else (the button itself stays hidden until the site is live). + code there and nowhere else. The README is now a short introduction that points to the site. - The Telegram bot no longer assumes you read English. It gives meanings in the languages diff --git a/brand/store/screenshots/en/01-page.png b/brand/store/screenshots/en/01-page.png new file mode 100644 index 0000000..602f8b4 Binary files /dev/null and b/brand/store/screenshots/en/01-page.png differ diff --git a/brand/store/screenshots/en/02-popup.png b/brand/store/screenshots/en/02-popup.png new file mode 100644 index 0000000..483b507 Binary files /dev/null and b/brand/store/screenshots/en/02-popup.png differ diff --git a/brand/store/screenshots/en/03-dashboard.png b/brand/store/screenshots/en/03-dashboard.png new file mode 100644 index 0000000..59a359e Binary files /dev/null and b/brand/store/screenshots/en/03-dashboard.png differ diff --git a/brand/store/screenshots/en/04-welcome.png b/brand/store/screenshots/en/04-welcome.png new file mode 100644 index 0000000..781534d Binary files /dev/null and b/brand/store/screenshots/en/04-welcome.png differ diff --git a/brand/store/screenshots/en/05-dark.png b/brand/store/screenshots/en/05-dark.png new file mode 100644 index 0000000..5c2ff8d Binary files /dev/null and b/brand/store/screenshots/en/05-dark.png differ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 358527b..c0d7f85 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -97,9 +97,9 @@ code at runtime; every script is a classic script that attaches one namespace to ([`lib/llm/client.js`](../extension/lib/llm/client.js), with `policy.js` and `catalog.js`) calls any OpenAI-compatible API through the presets in [`spec/providers.json`](../spec/providers.json). [`lib/pkce.js`](../extension/lib/pkce.js) - holds the "Connect OpenRouter" sign-in; its button stays hidden until the docs site - (slice [44](../slices/44-docs-site/SPEC.md)) serves the callback page, so pasting a key - is the way in today. + holds the "Connect OpenRouter" sign-in (the welcome tab's first choice and a button in + the dashboard's Word lookups), which returns to the docs site's `/connect/` page (slice + [44](../slices/44-docs-site/SPEC.md)); pasting a key works too. - **Server mode**: [`lib/sync-controller.js`](../extension/lib/sync-controller.js) keeps one sync running at a time, and [`lib/validate-words.js`](../extension/lib/validate-words.js) checks the server's answer before it is stored. [`lib/words-v1.js`](../extension/lib/words-v1.js) diff --git a/docs/privacy/en.md b/docs/privacy/en.md index c37a6bb..4cc8528 100644 --- a/docs/privacy/en.md +++ b/docs/privacy/en.md @@ -1,6 +1,6 @@ # Kotiko privacy policy -Version 1 · 5 October 2026 +Version 2 · 6 October 2026 Kotiko is a browser extension that swaps words on the web pages you read for words you're learning in other languages. This page says exactly what it keeps, what it sends, and to @@ -69,6 +69,16 @@ Kotiko also asks the service for its list of models (with OpenRouter, its free m and OpenRouter for how many free lookups you have left today. "Test" in the settings sends the word "hello". +**Connecting OpenRouter.** If you choose "Connect OpenRouter", Kotiko opens OpenRouter's +sign-in page, where you sign in to OpenRouter directly; Kotiko never sees your OpenRouter +password. OpenRouter then sends your browser to with a +one-time code in the page address. That page is part of Kotiko's website: it sets no +cookies, loads nothing from other sites, and only shows the sign-in's progress. Kotiko +reads the code there and trades it with OpenRouter for your key, which it keeps as +described below. The code works only once, expires after ten minutes, and is useless +without a second secret that never leaves your browser. Like any website, kotiko.org's +hosts (GitHub Pages and Cloudflare) see each visit, including that address. + **Pronunciations from Wiktionary.** For a word in a language with word stress (such as Russian, Ukrainian or Spanish), Kotiko asks English Wiktionary, run by the Wikimedia Foundation, for that word's page, to get its pronunciation right. Only the word is sent, @@ -158,4 +168,6 @@ everything in Kotiko's Settings. ## Changelog +- **Version 2, 6 October 2026.** Added "Connecting OpenRouter": the one-time sign-in code + passes through kotiko.org. - **Version 1, 5 October 2026.** First version. diff --git a/docs/privacy/inventory.md b/docs/privacy/inventory.md index da2afb5..8e29bd5 100644 --- a/docs/privacy/inventory.md +++ b/docs/privacy/inventory.md @@ -90,7 +90,7 @@ at run time. | Host | Contacted by Kotiko? | Why it's in the code | |---|---|---| -| `openrouter.ai` | Yes, when OpenRouter is your provider | Lookups, the free-model list and remaining free lookups; the key page link; the "Connect OpenRouter" sign-in (built, but its button stays hidden until the docs site serves the callback page) | +| `openrouter.ai` | Yes, when OpenRouter is your provider | Lookups, the free-model list and remaining free lookups; the key page link; the "Connect OpenRouter" sign-in (the sign-in page `openrouter.ai/auth` in a new tab, then the code traded for a key at `/api/v1/auth/keys`) | | `api.openai.com` | Yes, when OpenAI is your provider | Lookups | | `api.anthropic.com` | Yes, when Anthropic is your provider | Lookups | | `generativelanguage.googleapis.com` | Yes, when Google Gemini is your provider | Lookups | diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 966624f..4df295f 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -180,7 +180,7 @@ The folder that holds your words and the server's files (see - Default: `$XDG_DATA_HOME/kotiko`, or `~/.local/share/kotiko` when `XDG_DATA_HOME` isn't set (or isn't an absolute path, which the XDG Base Directory spec says to ignore). The - server creates it if it's missing. + server creates it if it's missing, readable only by you (`0700`). - Words already in `~/.local/share/kotiko` stay there: while `$XDG_DATA_HOME/kotiko` has no `kotiko.db` and `~/.local/share/kotiko` has one, the server keeps using `~/.local/share/kotiko`. To move them, stop the server and move the folder. @@ -291,11 +291,29 @@ check. | File | What it is | Permissions | |---|---|---| -| `kotiko.db` (with `kotiko.db-wal` and `kotiko.db-shm` while running) | Your words, kept responses for repeated adds (24 hours), cached lookups (30 days), and the background jobs' state. SQLite | Created with your umask: usually readable by other users of the computer unless your umask is `077`. The default folder is inside your home folder. To keep it private: `chmod 700` the data folder (usually `~/.local/share/kotiko`) | +| `kotiko.db` (with `kotiko.db-wal` and `kotiko.db-shm` while running) | Your words, kept responses for repeated adds (24 hours), cached lookups (30 days), and the background jobs' state. SQLite | `0600`. The server creates it empty and private before SQLite opens it, and SQLite gives `-wal` and `-shm` the database's permissions | | `api-token` | The generated API token, when `API_TOKEN` isn't set | `0600`, written so no other user can read it at any moment | -| `models-cache.json` | OpenRouter's model list as last read, used when the server starts offline | Your umask | +| `models-cache.json` | OpenRouter's model list as last read, used when the server starts offline | `0600` | | `backups/kotiko-pre--