Skip to main content
  ____  _
 / ___|| |_   __ _   __ _   ___
 \___ \| __| / _` | / _` | / _ \
  ___) | |_ | (_| || (_| ||  __/
 |____/ \__| \__,_| \__, | \___|
                    |___/

One command. Thousands of internships.

Every CS internship you can actually apply to, in one place.

Free and open source. No account, no server, no telemetry.


python

tests coverage ci


Made with

Python     SQLite     Textual

Works on

Windows     Linux     macOS

Why

Internships disappear fast. By the time you find the right posting, hundreds of students may already be ahead of you. Most job boards make it worse by burying relevant roles under senior positions, research jobs, and listings nowhere near you.

Stage scans 1,450+ employer career boards and finds internships CS undergrads in Canada and the U.S. can actually apply to. Every rejected posting is logged with the exact reason it was filtered out.

It has kept 3,577 postings out of 166,298 screened so far.

A look at it

The Stage TUI listing internship postings, with columns for age, company, title and location, a search bar, and the full description of the highlighted posting below
stage tui — browse everything, filter with f, open with o

The stage command line help, showing commands grouped into Everyday, Keeping current, Registry and maintenance, and Health
stage --help — commands grouped by what you are doing

Install

uv tool install stage-cli
Don't have uv? Install it first, then reopen your terminal.

If you already have Python, this works everywhere:

pip install uv

Or use your usual package manager — brew install uv on macOS, winget install astral-sh.uv on Windows. More options at docs.astral.sh/uv.

Other ways to install
pipx install stage-cli                  # isolates the tool, like uv
pip install stage-cli                   # installs into the active environment

uv tool install and pipx both keep Stage away from your system Python, which is what you want for a command line tool.

Needs Python 3.12, 3.13, or 3.14. Nothing else to set up — no database to configure, no API key, no config file.

First run

stage sync                    # fetch every board once (grab a coffee)
stage tui                     # browse what came back

The first sync visits every board and takes a few minutes. Later ones are much quicker, since Stage remembers what it already has and only asks each board for what changed.

stage --install-completion    # tab-complete commands and filters

Completion needs a fresh terminal. After that, stage <TAB> lists commands and stage list --<TAB> lists filters.

The basics

stage sync                            # fetch the latest postings
stage list                            # what is new, newest first
stage search "machine learning"       # search titles, employers, descriptions
stage tui                             # browse it all in a full screen

Rows are numbered, so you can act on what you just saw:

stage list --role swe --location montreal
stage show 3                          # read the third row in full
stage open 3 5 9                      # open those three in your browser

Filters work the same on list, search, and export:

Filter What it takes
--role swe, security, data, ml-ai, quant, infra, embedded, general-cs
--location montreal, canada, usa, unknown
--term summer-2027, fall-2026, winter-2027, and so on
--lang en, fr, bilingual
--source greenhouse, lever, ashby, workday, and more
--company one exact employer
--all every match, not just the first page
--last N look back N days instead of the default 14; --last 0 removes the limit. On search, which already covers every row, it narrows instead

Mistype one and Stage tells you what it accepts, rather than showing you an empty list.

The full-screen browser

stage tui

Press / to search and f for filters — arrows move, enter picks, escape closes. Role, location, language, how far back to look, new-since-last-sync and the employer you are sitting on all live in that one panel. space marks rows and o opens every one you marked. w expands the full description, e exports what you are looking at, and ctrl+t changes the theme. Press ? for the full key list.

Same database as the CLI. The panel covers role, location, language, the date window and new-since-last-sync; --term and --source stay CLI-only. Row numbers are a CLI idea — in the TUI you move a cursor instead.

Keeping it fresh

stage schedule enable     # sync quietly in the background
stage schedule status     # check on it

Opt-in, and it uses whatever scheduler your system already has: launchd, systemd, or Task Scheduler. Nothing runs until you turn it on.

Want to hear about new postings without checking? Point Stage at a Discord channel you manage:

stage schedule notify https://discord.com/api/webhooks/...
stage schedule test-notify        # make sure it works

Create a Discord webhook in the channel you want Stage to post to. It is just a URL Stage uses to send new internships.

Notifications only follow scheduled syncs, not stage sync. Your machine also needs to be awake. If it misses runs, Stage catches up once when it can run again.

Want notifications around the clock?

Put Stage on something that stays on — a VPS, a Raspberry Pi, an old laptop:

uv tool install stage-cli
stage sync                         # first sync, a few minutes
stage schedule notify <webhook>
stage schedule enable              # systemd, launchd, or Task Scheduler

That machine keeps its own database and posts to Discord on its own schedule. Yours stays free for browsing.

Architecture

Stage/
├─ src/stage/
│  ├─ cli/                  Typer commands, Rich rendering, scheduling, notify
│  ├─ tui/                  Textual screens over the same services; safe.py escapes output
│  ├─ domain/               the types every layer speaks: jobs, filters, enums, events
│  ├─ sources/              13 ATS adapters + 11 curated feeds
│  ├─ services/             sync, discover, query, export, quarantine, health
│  ├─ classify/             role, internship scope, degree eligibility, location
│  ├─ normalize/            location, term, language, apply-url canonicalisation
│  ├─ dedup/                cross-source duplicate resolution
│  ├─ http/                 rate posture, circuit breaker, validator cache
│  ├─ storage/              SQLite repository, migrations, FTS
│  └─ data/                 packaged registry (a directory) and lexicons
├─ tests/                   2,408 tests, 89% branch coverage
└─ .github/workflows/       CI, CodeQL, scheduled canary, release
┌──────────────────────────────────────────────────────────────┐
│                    1,450+ employer boards                    │
│    Greenhouse · Lever · Ashby · Workday · SmartRecruiters    │
│                + 11 curated internship feeds                 │
└───────────────────────────┬──────────────────────────────────┘
                            │  rate-limited, cached, backed off
                            ▼
┌──────────────────────────────────────────────────────────────┐
│                          normalize                           │
│       location · term · language · canonical apply URL       │
└───────────────────────────┬──────────────────────────────────┘
                            ▼
┌──────────────────────────────────────────────────────────────┐
│                            screen                            │
│    internship? · CS role? · degree scope? · Canada or US?    │
│        every rejection keeps the rule that caught it         │
└──────────────┬───────────────────────────────┬───────────────┘
               │ kept                          │ rejected
               ▼                               ▼
        ┌─────────────┐                 ┌─────────────┐
        │    jobs     │                 │ quarantine  │
        │    5,495    │                 │   160,803   │
        └──────┬──────┘                 └─────────────┘
               │  dedup collapses the same posting across sources
               ▼
     ┌───────────────────────┐
     │     SQLite + FTS5     │
     │    on your machine    │
     └──────┬────────┬───────┘
            ▼        ▼
      stage list   stage tui
      search       full-screen browser
      export       Discord on a schedule

CI

Workflow File Purpose
CI .github/workflows/ci.yml Ruff, format, mypy strict, 2,408 tests, coverage floor, badge drift
Supported Pythons .github/workflows/ci.yml The full suite again on 3.13 and 3.14
Wheel .github/workflows/ci.yml Builds and smoke-tests the wheel on Ubuntu, Windows and macOS
CodeQL .github/workflows/codeql.yml Static analysis for Python and the workflows themselves
Canary .github/workflows/canary.yml Probes one live board per platform and opens an issue on drift
Release .github/workflows/release.yml Re-runs every gate plus pip-audit, then publishes from a v* tag
Quality checks on Python 3.12, then the full suite on 3.13 and 3.14, a wheel smoke test on three operating systems, and CodeQL. All gates must pass for CI to pass

Any failing gate blocks the merge. The canary runs weekly and opens an issue when a board changes shape, so parser drift surfaces before a sync does.

Every run also builds the wheel and installs it into a clean environment outside the checkout on Ubuntu, Windows and macOS, checks the registry, lexicons and font resolve from inside the installed package, then runs help, list, search, doctor, coverage, stats, a dry-run sync and all four export formats against it. What ships is what gets tested.

Good to know

  • Everything sits in one SQLite file you own. Open it with any SQLite client, back it up, or delete it.
  • The only network traffic is Stage reading public job boards. No account, no telemetry.
  • Requests are rate limited per host, cached, and retried with backoff, so it does not hammer anyone's site.
  • Four in five postings arrive with no description at all. Stage marks those fields unknown instead of guessing.
  • Missing something you expected? stage quarantine shows which rule caught it.

Learn more

stage help walks through the common workflows with real examples. stage help COMMAND explains one command; stage --help lists them all.

CHANGELOG.md lists what changed in each release.

Found a bug?

Open an issue, especially if a job board stopped working, since those break without warning and I cannot watch all of them myself. I read everything.

Security

Stage reads public job boards and writes to a local file. It never asks for credentials. Found a security issue? Open a security advisory instead of a public issue.

License

MIT. See LICENSE.


Built for students who would rather be studying than refreshing career pages.

Metadata

Release files for stage-cli 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stage-cli 1.1.1
File Size Uploaded
stage_cli-1.1.1.tar.gz 2.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for stage-cli 1.1.1
File Interpreter ABI Platform
stage_cli-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 2.8 MB

Release files / stage_cli-1.1.1.tar.gz

Download URL stage_cli-1.1.1.tar.gz
Size 2.1 MB
Tags Source
SHA-256 checksum
How to use checksums
d2325113ccf634558e01c522a95615584e64f7f5ee4165b86d223a001b51a878
BLAKE2b-256 checksum
How to use checksums
486aa9c8a2742204b7efc664ed68ac062daeca4ae8b3cfc2bbf666f1c3d36059
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.

Transparency log

Release files / stage_cli-1.1.1-py3-none-any.whl

Download URL stage_cli-1.1.1-py3-none-any.whl
Size 757.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fb47aef869f2cd01afb8cbf05440134e6df923abf0e110289bda975ec78ccdde
BLAKE2b-256 checksum
How to use checksums
1e1e838ed3ec84ad8bee62bb6bf919298f03f35156a42e91d7967029ed220bd0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.

Transparency log

Release history Release notifications | RSS feed

1.1.3

2 release files

1.1.2

2 release files

This release

1.1.1 This release

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page