Sno Station
Sno Station은 AI 에이전트를 하나의 팀으로 묶는 오픈소스 워크스테이션으로, 로컬 머신에서 에이전트 협업을 가능하게 함
Chrome 확장 프로그램으로 웹 페이지 문단을 클릭해 독일어 수준에 맞게 읽고 연습 문제를 풀 수 있습니다
Расширение для Chrome: кликните абзац на любом сайте и читайте его по-немецки на своём уровне, а потом решайте упражнения по этому тексту.
플러그인TypeScript · JavaScriptGitHub @alexbaumgertner온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A Chrome extension for learning German by reading real web content. Select text on any page, right-click and choose Translate to German (Sprachweise): the selection is replaced by a German version at your level, with glosses for new words and exercises built from that text. Progress is tracked locally.
Alt+Shift+G (changeable at
chrome://extensions/shortcuts) does the same as the menu item, for sites
that replace the right-click menu.The extension asks for no website access at install. It uses activeTab:
Chrome lets it read a page only when you pick its menu item or press its
shortcut, and only that tab. Permissions: storage, activeTab, scripting,
contextMenus; no host permissions.
pnpm install && pnpm build (builds apps/extension/.output/chrome-mv3
against the production API, https://sprachweise.vercel.app).chrome://extensions, enable Developer mode, click Load
unpacked and select apps/extension/.output/chrome-mv3.After rebuilding, click the refresh icon on the extension's card. Pages that were open keep working: the next menu click injects the new script.
Alt+Shift+G).pnpm workspace: apps/extension (WXT + React), apps/cms (Fastify API on
Vercel, Neon Postgres, Gemini), packages/shared (zod schemas for every
message and API contract).
| Command | What it does |
|---|---|
pnpm --filter @sprachweise/extension dev | WXT dev server with hot reload. |
pnpm --filter @sprachweise/cms dev | Local API on http://localhost:8787 (needs apps/cms/.env). |
pnpm build | Builds every package. |
pnpm test:unit | Vitest unit tests. |
pnpm test:integration | API tests (PGlite) and the extension's Playwright suite. |
pnpm store:zip | Production Chrome Web Store package, with manifest checks. |
pnpm build:release | Release zip; requires WXT_CMS_BASE_URL, EXPECTED_VERSION. |
pnpm --filter @sprachweise/extension store:assets | Re-renders icons and store screenshots. |
The extension's API base URL is WXT_CMS_BASE_URL at build time; otherwise
production builds use https://sprachweise.vercel.app and dev builds
http://localhost:8787 (apps/extension/lib/config.ts).
pnpm --filter @sprachweise/extension test:integration runs build:test
(fixture page http://127.0.0.1:58732, mock API http://localhost:58733) and
then Playwright in headless Chromium. The fixtures start a fixture page server
and a mock API with real CORS for each test. Playwright can't open Chrome's
native context menu or press an extension shortcut, so test builds expose the
same handlers on the service worker (sprachweiseTest) and get the fixture
host as a host permission in place of the activeTab grant. Neither exists in
production builds (pnpm store:zip checks).
Releases are built by .github/workflows/release.yml. Nothing is committed: the version lives
only in the tag.
Push a vX.Y.Z tag on a commit that is on main (X, Y, Z in 0–65535, no pre-release suffix).
The version must be higher than the one in the Chrome Web Store, or the store refuses the upload.
git switch main && git pull
git tag v0.2.0
git push origin v0.2.0
The build-release job then:
origin/main and that the tag is vX.Y.Z.version in apps/extension/package.json to X.Y.Z (working copy only), which becomes
the manifest version.vars.WXT_CMS_BASE_URL (must be https://host, no path or trailing slash) and runs
pnpm build:release against it.sprachweise-vX.Y.Z-chrome.zip and re-checks that file with
apps/extension/scripts/verify-release.mjs: no local hosts or test hooks, the expected
permissions and version. Then computes its SHA-256..github/scripts/smoke-backend.sh the way the store extension
calls it: /privacy, /api/translate, /api/exercises and their CORS preflights (plus /api/tts,
expected to answer 404: dictation uses the browser's speech synthesis and the extension never calls it)
for chrome-extension://<CWS_ITEM_ID>. /api/report and /api/events get a preflight and an
empty POST that must come back 400 from validation, so nothing is written to production. The preflights must allow the origin, POST and
Content-Type; translate and exercises bodies are checked against the extension's response
schemas (packages/shared). No response, 5xx, any other 4xx, a missing CORS grant or a body the
extension would reject fail the release. Tolerated as warnings: 429 (daily allowance used up)
on translate and exercises, 404 on /api/tts (no such route, by design).vX.Y.Z (or updates it on a re-run) with the zip and
<zip>.sha256 attached. The body, rendered by .github/scripts/render-release-notes.mjs from
store/release-notes.md.tmpl, holds the changelog since the previous tag, the package facts,
every Chrome Web Store dashboard field ready to copy, and a pre-release checklist filled in by
the steps that ran. The same body is in the run's summary.The Chrome Web Store API v2 only uploads, publishes and reports status; it can't edit the listing. Copy the dashboard fields from the release body by hand. They come from these files at the tagged commit:
| File | Dashboard field |
|---|---|
store/description.txt | Store listing → Description (Russian) |
store/description.en.txt | Store listing → Description (English) |
store/single-purpose.txt | Privacy practices → Single purpose |
store/permissions.json (or store/permissions.md) | Privacy practices → Permission justification |
store/remote-code.md | Privacy practices → Remote code |
store/data-usage.md | Privacy practices → Data usage |
store/test-instructions.txt (or .md) | Test instructions → Additional instructions |
permissions.json maps each permission or host pattern to its justification:
{"storage": "…"}. permissions.md is pasted as is. store/permission-justifications.md,
test-instructions.md and summary.txt are readable copies that the release does not read. A missing
file shows up as a PLACEHOLDER in the body and a warning in the checklist; it doesn't fail the release.
The summary comes from the manifest (extDescription in _locales/*/messages.json); the privacy
policy URL is in the template. To change any dashboard text, edit its file in store/, not the
template. pnpm check:store (also run in CI) fails when the copies, the manifest, the privacy policy
and the character cap disagree. See docs/store-listing.md for the rest of the listing.
Actions → Release → Run workflow, pick the tag under Use workflow from → Tags (a run on a
branch fails), and tick publish_to_store. build-release runs again, then the
publish-store job waits for approval in the chrome-web-store environment. Once approved, it
downloads the zip from the GitHub Release, checks its SHA-256 and runs
.github/scripts/cws-publish.mjs:
DEFAULT_PUBLISH). Visibility (unlisted) and rollout stay as set in the
dashboard.The submission takes the listing as it is in the dashboard at that moment. Paste the fields from
the release body and save the draft before you approve the publish-store job, or the old texts go
to review with the new package.
Repository variables (Settings → Secrets and variables → Actions → Variables):
| Variable | Value |
|---|---|
WXT_CMS_BASE_URL | The production API, https://host without a trailing slash. Required. |
CWS_ITEM_ID | The store item ID. Optional: when unset, the workflow uses ahkgdlbpociadhocjjomkkeliiligebj for the CORS check, the notes and the upload. |
Secrets of the chrome-web-store environment (Settings → Environments → chrome-web-store; add
required reviewers so every upload needs an approval; repository secrets with the same names also
work, but then any workflow can read them):
| Secret | Value |
|---|---|
CWS_CLIENT_ID | OAuth client ID of the Google Cloud project with the Chrome Web Store API. |
CWS_CLIENT_SECRET | That client's secret. |
CWS_REFRESH_TOKEN | Refresh token for the publisher account, scope chromewebstore. |
CWS_PUBLISHER_ID | The publisher ID from the developer dashboard. |
The secrets are only passed to the upload step. cws-publish.mjs masks the access token in the
log and prints only HTTP status and the API's error message, never a credential.