DeepSeek Harness Remote
DeepSeek Harness Remote: DeepSeek Harness 실행 PC와 모바일·PC를 안전하게 연결해 어디서나 작업 이어받기
StillOpen이 수백 개 기업 채용 페이지와 구인 사이트를 검색해 지원 가능한 일자리만 선별
StillOpen searches hundreds of company career pages and job boards for you, keeps the jobs you can really take, tailors your CV and cover letter for each one, and tracks every application. Free, and y
생산성GitHub @Druvo온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
저장소 정보는 아직 수집하지 않았습니다.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Open-source, self-hostable job search: finds openings across hundreds of company career pages and job boards, tailors your CV, tracks applications. Django, Apache-2.0, MCP for Claude Code.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗The public job board: no sign-in needed. Signed in, the same list is ranked against your own CV and places.
If StillOpen saves you a few hours of clicking through job sites, a star on GitHub helps other job seekers find it.
The pictures on this page come from invented demo data ("Jane Doe"), made by pnpm readme:images (see docs/images/README.md); no real person appears.
| Dashboard. Every job, best match first, with "Open" or "Closed" next to it. | Board. The same jobs by stage; drag a card to move it. |
| A CV that stays honest. A claim that is not in your own material is flagged and locks the download until you approve it. | Your numbers. Plain counts from your own search, never predictions. |
| Openings board. A public page of current openings, each crediting its source. | On a phone. The web app is responsive (there is no native app). |
git clone https://github.com/Druvo/stillopen.git && cd stillopen
docker compose -f docker-compose.selfhost.yml up -d --build
docker compose -f docker-compose.selfhost.yml exec stillopen python manage.py createsuperuser
Open http://localhost:8000. A ready-built image (ghcr.io/druvo/stillopen) is built by .github/workflows/image.yml when a version tag is pushed; it appears after the first tagged release, so use the compose path above until then. More ways to install (plain Docker, from source, an Ubuntu service, CapRover, Coolify, Dokploy): docs/INSTALL.md. Email, HTTPS, backups and updates: docs/SELF-HOSTING.md. Running it as a shared service for many people: docs/HOSTING.md.
Measured on this repository's demo data (nine jobs, manage.py e2e_seed --demo, SQLite, the Django test client, 2026-10): the dashboard answer takes 45 database queries, the numbers page 16 and the profile call 9. The count does not grow with the number of jobs (it was one query per job before it was fixed; see the changelog). The signed-in app's first load is 168.7 kB gzip in 9 scripts, and all its JavaScript 459.4 kB gzip in 49 files (pnpm size; budgets 172 and 480 kB). Anything beyond these numbers (how many people one small server carries, how long a search takes) has not been benchmarked, so none is claimed.
People can make a personal token under Profile > Connect an AI assistant and give it to Claude Code (or any MCP client) to work with their own job search from the assistant: docs/MCP.md.
Install · Self-hosting · Contributing · Security policy · Code of conduct · Changelog · License (Apache-2.0)
Job-search dashboard on Django. Each person registers, adds the roles they want and their CV, tracks the jobs they are chasing, and sees a match percentage and whether each posting is still open. Optional AI scoring with your own key, retrieval over your own documents, an MCP server for Claude Code, Paperless-ngx for documents, and a career-ops import for admins. Everything that changes behaviour is a row in the admin, not code.
It runs two ways: on your own machine for one person, or as a shared service for many people (each only ever sees their own data). Hosting guide: docs/HOSTING.md.
cd stillopen
.venv\Scripts\python.exe manage.py migrate
.venv\Scripts\python.exe manage.py createsuperuser
.venv\Scripts\python.exe manage.py runserver 127.0.0.1:8000
Optional, admins only: .venv\Scripts\python.exe manage.py import_careerops loads a career-ops folder into your account. Jobs and settings created before accounts existed are given to the first account you create (or run manage.py claim_data <username>).
Management commands act for one person: pass --user NAME, or leave it out when there is exactly one superuser. check_liveness --all-users suits a scheduled job.
The signed-in app is a front end built once ahead of time (Node 22 or later and pnpm, only for this step): cd frontend && pnpm install && pnpm build. The Docker image already includes it. Until it is built, /app/ says so on a plain page.
Open http://127.0.0.1:8000 (public landing page), http://127.0.0.1:8000/app/ (the app, login required; the old addresses such as /dashboard/ redirect to it) and http://127.0.0.1:8000/admin/ (site configuration). Copy .env.example to .env first if you want to change the career-ops path or Paperless URL before the first migrate.
| Admin section | Controls |
|---|---|
| Site configuration | Dashboard title, registration open/closed, privacy and terms text, landing page on/off, headline, sub-headline and GitHub link, score scale (match % = score / scale), urgent-deadline window, auto-close of expired postings, career-ops folder, Node command, open/closed check mode and timeout, Paperless URL/token/TLS/auto-upload |
| Buckets | The dashboard tabs: label, order, which one is the "closed" tab |
| Statuses | Names, colour, tab, whether a status counts as "CV submitted" (stamps the applied date), whether it is terminal (skipped by the open/closed check) |
| Score bands | Labels and colours per match % threshold |
| Document kinds | File-name keywords that classify PDFs (CV, cover letter...), plus the Paperless tag and document type for each |
| Sources | Job-site names matched against the posting URL |
| Expired phrases | Text that marks a posting as closed (built-in HTTP check) |
| User profiles | Each person's roles, CV, AI provider, retrieval settings and keys (keys are write-only) |
| Applications | Edit any role (admins see everyone's), add notes/insights/documents/timeline events; bulk actions: mark applied, mark discarded, check open/closed, push to Paperless |
Import is idempotent: new tracker rows are created, existing rows refresh only evaluation data (score, summary, strengths, gaps, deadline, PDFs). Your local status, notes and URL are never overwritten unless you run import_careerops --sync-status.
Visitors need no account to see what the app does and what is open.
/): leads with the idea that nobody should visit job sites one by one, then shows live numbers (open jobs, employers, countries, sources searched), the latest openings, a before/after picture, how it works step by step, how a search narrows the list and how "fit" is worked out, live charts (jobs by country, by kind of source, new per day once there is history), the benefits, a comparison table, the privacy promises, the sources, and an FAQ. The headline and sub-headline are editable in the admin; everything else describes what the app really does./openings/): every currently listed opening, newest first, with search, country and remote filters and paging, and a page per opening (/openings/<id>/<slug>/) with a short excerpt, a credit link and the apply link unchanged. It is the same for everyone: the personalised part (fit to your CV and places, tailored CV, tracking) starts after sign-in.robots.txt and sitemap.xml are generated. Private areas are disallowed.The rules it follows (checked against each source's own terms and Google's job-posting guidelines, 2026-10):
| Rule | Why |
|---|---|
| Only sources ticked Show publicly (admin > Job sources) appear, never a person's own boards | Each source decides what may be republished |
| Remotive, We Work Remotely, Working Nomads, The Muse and Jooble are off by default. Remotive's terms forbid sending its jobs to third-party sites and showing them to collect sign-ups | A public board whose purpose is attracting sign-ups is exactly what that clause bars |
| RemoteOK, Himalayas, Jobicy and Arbeitnow are shown with a visible credit link; the apply link is the source's own, unchanged | Their terms ask for both |
| Employer boards (Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee) are shown, crediting the employer | They exist to be seen; review them if you want to be stricter |
| A short excerpt only, never the full posting | The full text stays with its owner, one click away |
| No JobPosting structured data | Google requires the full description on a single-job page; Himalayas and Remotive forbid passing jobs to Google Jobs |
An opening not seen for STILLOPEN_PUBLIC_ACTIVE_DAYS (3) days answers 410 Gone and drops off the board | Google and the sources require expired jobs to be removed |
An opening's own page is noindex unless you switch on public_details_indexable | Short excerpts of other people's postings can be judged thin or copied content; the landing page, board and sitemap stay indexable |
Filtered and deeper pages are noindex,follow | They are for people, not search results |
Freshness needs no person: the board refreshes itself in the background when a visitor arrives and it is older than STILLOPEN_PUBLIC_REFRESH_HOURS (6), at most once an hour, or run python manage.py refresh_postings from cron (it is in deploy/). Public pages are cached for STILLOPEN_PUBLIC_CACHE_SECONDS (300). Under Site configuration > Public job board you can switch the whole board off, allow indexing of opening pages, and set a contact email that appears in the footer for employers who want a listing removed.
The look. A standard, familiar style: light by default for everyone, a toggle to dark that is remembered, one brand colour, system fonts only (no external fonts or scripts), a skip link, labelled forms, charts drawn as accessible inline SVG with titles and descriptions, and no sideways scrolling on small screens.
The dashboard lists every job you have, best match first, with nothing hidden behind tabs. Narrow it down with the bar above the list: stage chips (All, To apply, Applied & pending, Closed & skipped, each with a count), a text search over company, role and place, and filters for fit to your places, how well you match, remote/hybrid/on-site, country, whether the posting is open, and when it was found. Sort by best match, newest, oldest, company or closing soonest. "Showing 6 of 40" says what is filtered and "Clear filters" undoes it. Your choices are remembered for the browser tab.
A new install already knows where to look: it ships with about 100 job sources (8 remote-job feeds such as Remotive, RemoteOK, We Work Remotely, Himalayas, Jobicy, Working Nomads, Arbeitnow and The Muse, plus the public hiring boards of ~90 well-known companies on Greenhouse, Lever, Ashby, SmartRecruiters and Recruitee). All of them are open, documented, key-free endpoints. They are added by the migration, or by python manage.py seed_sources on an existing install, and the admin can pause or remove any.
Candidates do nothing. When someone opens their dashboard and their last search is older than auto_scan_hours (Site configuration > Dashboard, default 12; 0 turns it off), a search starts in the background and the jobs appear. No cron, no button, no AI key. What a search does:
STILLOPEN_SCAN_FIRST_WAIT_SECONDS (15) have passed it works on the sources that answered, adds a first batch of up to STILLOPEN_SCAN_FIRST_BATCH (25) jobs (the dashboard refreshes itself to show them), then waits for the slow ones up to STILLOPEN_SCAN_DEADLINE_SECONDS (60) and tops the list up. A host still silent by then is skipped and finishes into a shared cache for next time; each source is read at most once per STILLOPEN_SCAN_CACHE_SECONDS (6 hours) however many people search. A typical cold search takes about a minute, with the first jobs at about 25 seconds, and a progress bar shows where it is.STILLOPEN_SCAN_MAX_NEW (40) per search, the best first (clear fits, then listed roles, then CV match), and at most STILLOPEN_SCAN_MAX_PER_COMPANY (5) from one employer so one big company cannot fill the list. Search again later for the next batch.Few job sites in these markets publish an open feed, so coverage comes from three honest routes:
JOOBLE_API_KEY in the environment (which wins). The key is write-only and never shown again or put in a log or error. Until a key exists, the eight Jooble sources are simply skipped. Unlike a company board, Jooble is a search, so each person's search asks it for their own target roles (up to 3) in each country, and answers are cached per set of roles. Check Jooble's terms for how you may use the results; every job links back to its Jooble page. This connector was built against Jooble's documented request and response shape and tested with simulated responses; it has not been run against a live key.Not included on purpose: LinkedIn, Indeed, Glassdoor, bdjobs (its robots.txt says other crawlers will not spider, and it publishes no feed) and JobStreet (its robots.txt disallows crawling its job pages and job-search API). Paste one of their links into Add job instead. Jobs in the same place from the same employer with the same title are collapsed to one per search, because big employers post the same role once per team.
| Command | What it is for |
|---|---|
python manage.py check_email | Shows how email is set up (never the password) and sends one test message. Run it before your first real sign-up. Without EMAIL_HOST it says plainly that nothing is delivered. |
python manage.py check_jooble | Tries your Jooble key with one real search (--place bd/my/ae/..., --role "..."). The key is never printed. |
python manage.py update_legal_text | Puts the current privacy and terms templates in place, but only where the old unedited default is still there (--force replaces anything). A migration already does this for untouched defaults. |
python manage.py seed_sources | Adds any missing default job source. |
Schedules. None are needed to use the app (the search runs when someone opens the dashboard). The emails need them: deploy/crontab.example (Linux/macOS) and deploy/windows/schedule-stillopen.ps1 (Windows Task Scheduler, -Remove undoes it) set up interview reminders hourly, follow-up emails and job alerts daily, and a weekly source refresh.
Privacy and terms. The shipped texts describe what the app really does (the search, Jooble, AI use, cookies, export and deletion) in plain words. They are templates: fill in the [bracketed parts] under Site configuration > Legal and have them reviewed for your country. While sign-up is open and blanks remain, admins see a warning on their dashboard.
Tests in CI. .github/workflows/tests.yml runs the whole suite and checks that no model change is missing its migration on every pull request and push to main.
Under Job sources in the sidebar, anyone can add the companies they care about. Pick the hiring system the company uses (Greenhouse, Lever, Ashby, SmartRecruiters or Recruitee), then type the company's name from its careers page address or paste the address itself (jobs.lever.co/acme gives acme). The app reads that company's public job list once to make sure it works, and only then saves it. Your boards are searched only for you and are invisible to everyone else; the boards the site owner added are searched for everyone. Limits per person: STILLOPEN_MAX_OWN_SOURCES (20) boards and STILLOPEN_SOURCE_ADDS_PER_HOUR (20) additions an hour.
Why this is safe to offer: nobody enters a web address. They choose from a fixed list of hiring systems, and the app builds the real address itself on that system's own host, through the same guarded fetch every scan uses. Boards for sites that need a browser or break a site's terms (LinkedIn, and aggregators like RemoteOK) stay site-owner only or unsupported.
All of this is also available to an AI client through the MCP server: list_interviews, add_interview, set_interview_outcome, list_contacts, add_contact, add_referral, set_referral_status, list_offers, add_offer, set_offer_status and compare_offers. They are validated and owner-scoped like everything else, there is still no delete tool, and none of them contacts anyone.
Three pages in the sidebar, all private to you and none of them contacts anyone:
Insights shows plain counts from your own tracker: how many applications reached an interview or an offer, the typical days from applying to a first interview, and which channels (and referrals) led to interviews. It describes, it never predicts.
Interview reminders. With follow-up emails switched on (Site configuration > Dashboard) and SMTP configured, run python manage.py send_interview_reminders hourly from cron or Task Scheduler. Each person gets one email for interviews in the next 24 hours, once per interview, and only at a verified address.
Each person can save up to STILLOPEN_MAX_PIPELINE (500) of each. Everything is included in the Profile > Your data export and removed with the account.
Each person can tick Profile > Job alerts to have their saved search run on a schedule and get one email listing what was added. The site owner switches this on with scan_emails_enabled (Site configuration > Dashboard), configures SMTP, and runs python manage.py scheduled_scan daily from cron or Task Scheduler. Nothing is sent when nothing new was found, or to an unverified address.
node mode runs career-ops check-liveness.mjs (Playwright). Use this: it renders JavaScript pages, so it catches closed postings that a plain HTTP check misses.http mode is a plain request plus your expiry phrases. It cannot see closed postings on JavaScript sites and will report them as open.Run it from the dashboard button, the admin action, or python manage.py check_liveness (suitable for Task Scheduler).
copy .env.example .env # then fill PAPERLESS_ADMIN_USER, PAPERLESS_ADMIN_PASSWORD, PAPERLESS_SECRET_KEY
docker compose up -d
PAPERLESS_TOKEN in .env (the environment variable wins).Company - Role - Kind, tagged stillopen plus the kind's tag. Re-sending finds the existing title instead of duplicating.Optional container + nginx front end: docker compose --profile web up -d --build serves the dashboard on http://127.0.0.1:8080. Set CAREEROPS_HOST_PATH in .env to mount your career-ops folder, and use http check mode there (the image has no Node).
Username/password login always works. Google sign-in is optional and off until you set it up:
http://127.0.0.1:8000/accounts/google/login/callback/ (use your real address and https in production).GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env.)[email protected] or a whole domain @company.com.Rules: a Google account must have a verified email. A user already in the database with that email (for example you, the superuser, once your user has an email address set) is signed in as that user. New people are created only if they are on the allowed list, never as staff, so they get the dashboard but not the admin. Remember that everyone on the list sees all jobs and documents. Nobody can self-register with a password.
Each person keeps their own, in the web app (not the admin):
python manage.py index_knowledge --import-careerops --user NAME.A person's search is a list of search areas, one row per place, set on the Profile page:
| Where | Accepts | Needs from the employer |
|---|---|---|
| Bangladesh (Dhaka) | on-site, hybrid | nothing |
| Anywhere | remote | nothing |
| Malaysia | on-site, hybrid | visa sponsorship, relocation support |
| Gulf | on-site | sponsorship, relocation, at least 8,000 USD per month |
A place is a country, a region (Gulf, Europe, Southeast Asia, EU, MENA...), a city, or Anywhere. Each row says which of remote, hybrid and on-site you accept there, whether you need visa sponsorship or relocation support, and an optional minimum pay with its own currency. The profile also records the country you live in, because a remote job limited to one country only fits someone who lives there.
Each job has a location, country, work arrangement, whether it offers sponsorship and relocation, a type (full-time, contract...) and seniority. They are suggested by reading the posting (no AI needed) and you can correct them on the job's Details page; what you set by hand is never overwritten. Every job is then checked against your areas, and the result shows as Fits, Check fit or Fit unknown on the dashboard and in the apply checklist: outside your areas, sponsorship not offered or not mentioned, pay below your minimum, wrong employment type or seniority. A job is only ever flagged, never hidden.
Detection recognises about 90 countries, regions and several hundred cities from a built-in table. An unrecognised place is kept and matched by name, and unclear postings are reported as unknown rather than guessed. The Profile page also has an Account section: change email or password, connected accounts, what is stored, export, delete.
Press Tailor CV on a job to make a CV for that role from your own CV and documents. With an AI provider configured the model rewrites it; without one, the free keyword mode reorders your bullets and skills by relevance to the posting and trims the weakest, adding nothing.
list_cv_versions, get_cv_version and save_tailored_cv MCP tools let an AI client write a tailored CV; it goes through the same check and cannot approve its own work.Limits: 200 versions and 20 tailorings per hour per person (STILLOPEN_MAX_CV_VERSIONS, STILLOPEN_CVGEN_PER_HOUR). Cover letters. On the apply page, "Write a cover letter" makes a short letter from your own material (an AI if you have connected one, otherwise your own most relevant CV bullets in a plain frame). It goes through the same fact check; a flagged letter is locked until you edit it or approve it, and any edit re-checks it and withdraws the approval. DOCX and plain-text download; it is never sent for you.
StillOpen never submits an application for you: employer sites need your login and block bots, and a robot applying in your name is a bad idea. The Apply button on a job opens an assistant that prepares everything and then records what you did.
[Your name]) that is never sent.Site configuration > Dashboard). It sets the status to Applied (never moving you backwards from Interview), links the exact CV version you sent, and saves a snapshot of the job description as it was that day. A CV recorded as sent cannot be deleted. A posting that looks closed needs an explicit confirmation.Follow-ups due appear at the top of the dashboard with Done and "In 3 days" buttons. In Claude Code, record_application and list_follow_ups do the same, and record_application is only for after you confirm you really submitted.
A long CV and many documents do not need to be sent for every job. With Retrieval (RAG) on (the default), each evaluation sends the opening of your CV plus only the passages most relevant to that job, and records how in the role's timeline ("RAG lexical, 4 passages from 29").
| Mode | How it ranks | Cost and privacy |
|---|---|---|
| Keyword ranking (default) | BM25 over your passages | Free, offline. Only the final prompt goes to your AI provider. |
| Embeddings | Cosine similarity of vectors from an OpenAI-compatible /embeddings API (OpenAI, Ollama nomic-embed-text, ...) | Better at meaning. Your passages are sent to the embeddings service once and cached; each job description is sent to embed the query. |
If embeddings fail, retrieval falls back to keyword ranking and the timeline says why. Tune passage size and count under AI settings > Retrieval. The same retrieval is available to Claude Code through the search_profile MCP tool.
Each person chooses under AI settings how their jobs are scored (admins can also edit a user's profile in the admin):
| Provider | What it needs |
|---|---|
| Off | Nothing. Free keyword match against your CV and knowledge sources. It never overwrites a score you already have. |
| Anthropic API key | A key |
| OpenAI-compatible | A key and base URL. Covers OpenAI, OpenRouter, Groq, Mistral, Gemini (https://generativelanguage.googleapis.com/v1beta/openai), DeepSeek, Ollama (http://127.0.0.1:11434/v1, no key), LM Studio and vLLM |
| Custom HTTP | Any other JSON API. You give the endpoint, header lines (Authorization: Bearer {{api_key}}), a JSON body using {{system}}, {{prompt}}, {{model}}, and the dotted path to the reply text (e.g. candidates.0.content.parts.0.text) |
| Claude Code CLI | Site admins only. claude installed and logged in on the same machine (not available inside Docker) |
| Claude Agent SDK | Site admins only. pip install -r requirements-ai.txt |
On a shared server, ordinary users can only point at public https addresses (no localhost or private networks). python manage.py ai_check --user NAME sends one tiny request to confirm a provider works. python manage.py evaluate_jobs scores unscored roles. Keys are stored encrypted and never shown again; STILLOPEN_AI_API_KEY in the environment overrides the saved one.
How job text is handled: descriptions are untrusted, so they are wrapped in tags with an instruction to treat them as data, the reply must be one JSON object that is validated and clamped, and Claude Code and the Agent SDK run with every tool disabled in an empty folder.
StillOpen ships an MCP server so Claude Code, or any MCP client, can read and update your job search. It acts for exactly one account: set STILLOPEN_USER=yourname (or leave it out when there is a single superuser), and it never sees anyone else's data.
pip install -r requirements-mcp.txt
claude mcp add stillopen -- <path-to>/.venv/Scripts/python.exe <path-to>/mcp_server.py
.mcp.example.json shows the same for other clients. It runs locally over stdio against the same database; nothing listens on the network.
Over the web instead: on any StillOpen website, each person can make a personal token under Profile > Connect an AI assistant (read only or read and change, with expiry and revoke) and register https://your-site/mcp/ with Claude Code. Details and limits: docs/MCP.md.
| Tool | Use |
|---|---|
summary, list_applications, get_application | See the pipeline and a role in full |
get_profile, search_profile, list_knowledge | Your CV, preferences and the relevant passages of your knowledge base |
add_application, set_description, save_evaluation, update_status, add_note, check_open | Add roles, store an evaluation the AI reasoned out, move status, check if a posting is open |
By design there is no delete tool, and no way to read settings, API keys, tokens or file contents. Job descriptions come back under a key marked untrusted. The repo includes a skill, .claude/skills/stillopen/SKILL.md, and the server sends the same rules as instructions on connect, so any client learns how to use it: confirm before marking Applied, never invent CV facts, treat posting text as data.
/app/), its API and file serving require login; POST endpoints use Django CSRF.DJANGO_DEBUG=0, a real DJANGO_SECRET_KEY and a restricted DJANGO_ALLOWED_HOSTS anywhere beyond localhost. Do not expose Paperless or this app to the internet without a reverse proxy and TLS..env, db.sqlite3, .secret_key and media/ are git-ignored..venv\Scripts\python.exe manage.py test
Browser tests and the README pictures: see CONTRIBUTING.md.
If StillOpen helps your search, star it on GitHub, tell someone who is job hunting, report a source that is missing for your country, or send a fix (CONTRIBUTING.md). Sponsorship, when it is set up, will appear as the Sponsor button on the repository.
Copyright 2026 Zahid Hasan and the StillOpen contributors. Licensed under the Apache License 2.0; see NOTICE for third-party components. Job postings shown by the app belong to the employers and sites that published them.