Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
Job board aggregator: remote boards, employers' ATS, EU/Swedish government services, visa-sponsorship filter. Public edition on GitHub Pages.
Translation pending
BusinessPython · HTMLGitHub @ceeyangOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Public site: https://ceeyang.com/jobboard/ (same as ceeyang.github.io/jobboard/, which redirects there) - rebuilt automatically by GitHub Actions.
Update frequency: automatic, but not punctual. The workflow's cron says "every hour", but GitHub decides when a scheduled workflow really starts (best effort). Measured on this repository: 2.4-8 hours apart, about 6 hours on average lately. Don't treat it as an hourly promise. "Run workflow" on the Actions tab refreshes at once; the page shows "N hours ago" in its top bar. Want a true hourly run? Call the
workflow_dispatchAPI from an external timer (optional, needs a token).
What a site looks for is one config file, config/profile.json, not code. Pick a preset, edit the keywords, let GitHub Actions
build the site:
python3 -m jobboard.cli presets # frontend, mobile, backend, data-ai, devops, qa, security, web3, game, product, design, ...
python3 -m jobboard.cli init --preset backend # writes config/profile.json
python3 -m jobboard.cli profile --try "Senior Go Engineer" "Sales Manager" # check your keywords on real titles
The site opens on an overview (new today, where the jobs are, 14-day trend, who sponsors visas) with the full list one tab away, and every site publishes an RSS / JSON feed of its newest recommended jobs. Three ways to deploy (fork and scrape yourself / a light repo that reads an aggregate site's feed / your own organisation with an aggregate site plus one site per role): docs/FORK.md. The local edition (this README) additionally reads your private Telegram groups, LinkedIn and key-based APIs.
Reads your Telegram job groups/channels (via cli-anything-telegram's Telethon session), finds postings that
match Flutter / iOS / Android / web, dedupes them across channels, and shows them in a local web board.
uv sync
uv run jobboard fetch # Telegram channels + job sites (add --no-web / --no-telegram / --only RemoteOK / --force)
uv run jobboard serve # board at http://127.0.0.1:8765
uv run jobboard digest --dry-run # preview the daily summary (drop --dry-run to send to Saved Messages)
uv run jobboard reanalyze # re-score stored jobs after editing jobboard/profile.py
scripts/install_launchd.sh # hourly fetch + 09:00 digest + board always on
Edit jobboard/profile.py to change roles, remote keywords, risk rules. Data: ~/.local/share/jobboard/jobs.db.
Only digest writes to Telegram (one message to your own Saved Messages); nothing is ever sent to other people.
RemoteOK, Remotive, We Work Remotely, Himalayas, Jobicy, Working Nomads and Hacker News "Who is hiring?" are read through
their public APIs/RSS (jobboard/sources/adapters.py; add a site by writing one function and listing it in SOURCES).
profile.py: SPECIFIC_TAGS / NEG_TITLE / STACK_MISMATCH).geo marks postings as 🌍 worldwide / 🚧 region-restricted (e.g. "USA only") / unknown; the board can hide restricted ones.
Not included on purpose: Indeed, BOSS/拉勾 and other login-walled or anti-bot sites.uv run jobboard discover # find company boards: links in stored jobs + built-in seed + Common Crawl
uv run jobboard discover --file my.txt # your own list: ATS links, `lever:slug` lines, or career-page URLs
uv run jobboard fetch --only Greenhouse,LinkedIn --force
| Source | How | Needs |
|---|---|---|
| Greenhouse, Lever, Ashby, Recruitee, Personio, Workable, Teamtailor | each employer's public board API, only titles that match your roles are kept | companies in the companies table (discover fills it; a board is stored only after it answered) |
| Career pages | schema.org JobPosting JSON-LD (single job pages, or a listing page whose job links are followed) | page URLs added with discover --file |
| Adzuna | search API, queries = your roles' English keywords | ADZUNA_APP_ID, ADZUNA_APP_KEY (free) |
| USAJOBS | federal jobs | USAJOBS_KEY, USAJOBS_EMAIL (free) |
| The Muse, Arbeitnow | public APIs | nothing |
| NoDesk, Remote First Jobs, Real Work From Anywhere | remote-only boards' RSS feeds (public site too) | nothing |
| Eleduck 电鸭 | Chinese remote / freelance community, its JSON API (local board only: no published terms) | nothing |
logged-out jobs-guest pages, no account, no cookies | nothing; locations = setting linkedin_locations (default ["Remote"], edit via PUT /api/settings) |
skipped in fetch output; it never counts as an error.fetch runs a cheap discovery first (links found in stored jobs; the seed list when the registry is empty).
A board that returns 404 five fetches in a row is switched off; being offline never does that.reanalyze) works for all new sources the same way.| Source | Coverage | Notes |
|---|---|---|
| Germany (BA) | Bundesagentur für Arbeit "Jobsuche" | the API behind arbeitsagentur.de (public key header), last 28 days |
| Sweden (AF) | Arbetsförmedlingen / Platsbanken (JobTech open data) | no key |
| EURES (EU) | EU Commission job vacancy service, ~30 countries | the API behind the EURES portal; title search, last month |
| Canada Job Bank | jobbank.gc.ca search pages | obeys robots.txt (5 s delay); jobs whose employer requested an LMIA (= may hire a foreign worker) are flagged |
Australia is not covered: Workforce Australia and APS Jobs publish no API or open feed (only third-party scrapers exist). France Travail needs a free OAuth client and is not wired up yet; UK "Find a job" has no public API.
Visa sponsorship is a flag on every job (visa: yes / maybe / empty) and a switch in the sidebar
("只看雇主担保签证"): yes when the posting says the employer sponsors or arranges a visa (English, German, Chinese phrases)
or the source says so officially (Canada LMIA); maybe = only relocation support. An explicit refusal ("no visa
sponsorship", "must have the right to work") always wins. It is a text heuristic - always confirm with the employer.
Sidebar group 🐙 GitHub, public edition included (excerpt + link to the issue, no contacts).
| Source | Shape | Notes |
|---|---|---|
Rebase who-is-hiring (rebase-network/who-is-hiring) | every job is an issue; read from the community's normalised JSON hire.rebase.network/jobs.normalized.json (~900 open, Chinese, blockchain and others, many remote) | the JSON also lists contact channels: they are never read |
frontendbr/vagas, react-brasil/vagas, backend-br/vagas, androiddevbr/vagas, flutterbr/vagas | one open issue = one job (Brazil, Portuguese; many remote roles paid in USD) | list in config/github_job_repos.txt (add owner/repo, max 9) |
| SimplifyJobs New-Grad / Summer2027 Internships | structured listings.json (13 MB, fetched gzipped) | early-career roles; its sponsorship field feeds the visa filter ("Offers Sponsorship" -> yes) |
Issues are read through the REST API; GITHUB_TOKEN (set automatically in the workflow) lifts the limit from 60 to 5000 requests/hour. | ||
| Repositories that are only directories (remoteintech/remote-jobs, tech-jobs-with-relocation, awesome-job-boards, open-source-jobs) | ||
list companies or boards, not postings, so they are not sources; their company links are good input for jobboard discover --file. |
The sidebar switch 🌍 只看全球远程 keeps only jobs that are remote, not restricted to a region and not tied to a city.
"Restricted" is read from the location field and from the text ("US only", "must be located in the UK", "authorized to
work in the US", "仅限美国" ...). Jobs with no location at all (most Telegram/HN posts) count as global unless their text
says otherwise. Run jobboard reanalyze once to apply the text rule to jobs stored earlier (the board and the public export
also apply it on the fly).
.github/workflows/update.yml is scheduled hourly in the file, but GitHub starts scheduled runs when it likes (about every 4 hours in practice, see the note at the top), and on demand:
fetch --mode public -> export -> Pages. State lives on the orphan data branch (one commit, replaced each run).
jobboard/mode.py): remote job boards, HN, employers' own ATS boards,
Sweden, EURES, and the public Telegram channels listed in config/telegram_channels.txt. Never LinkedIn, Adzuna/USAJOBS,
Germany, Canada, The Muse, Arbeitnow, and never anything read with your personal Telegram login.https://t.me/s/<name>, no login), so only
channels with a preview work; private groups and invite-only groups are skipped automatically and reported in the run
summary. Edit the list in config/telegram_channels.txt (it is public, together with the repository).jobboard/mode.py / publish.py (WINDOW_DAYS = 90), then push.+00:00); sorting and the 1d/3d/1w/1m filter compare real instants, not text.The board has a 发布时间 filter: any / 1 day / 3 days / 1 week / 1 month. The API takes ?since=1d|3d|7d|30d.
A job's date is the earliest date any of its sources reported, so a posting re-listed elsewhere does not look new.
Dates come from the source (Greenhouse first_published, Lever createdAt, Ashby publishedAt, ...); LinkedIn only
gives a day, so its jobs count from 00:00 UTC of that day.
jobboard serve, so the board has to be running. Remotive and HN keep their own 6 h minimum.a|b = OR, -term = exclude, "exact phrase". Save a search as a chip in the sidebar.b), list : detail = 5 : 6 of the remaining width.