🌐 Language / Dil: English | Türkçe
A terminal-based ATC radar that shows real aircraft flying near you — in real time.
squawk pulls live flight data from the OpenSky Network API every 30 seconds and renders it in your terminal as a green phosphor radar — just like the ones air traffic controllers use.
- Quick Install ← start here
- Features
- Requirements
- First Run — Setup Wizard
- OpenSky Network API Setup
- Usage
- Understanding the Display
- Keyboard Shortcuts
- Reconfiguring
- Building a Single-File Binary
- Publishing to NuGet
- Security
- How It Works
- License
- 🟢 Green phosphor radar — rotating sweep line, concentric range rings, compass labels
- ✈ Live aircraft from OpenSky Network — updates every 30 seconds
- 📡 Configurable radius — default 20 km, overridable per-run with
--radius - 🔢 Rich aircraft data — flight level, speed, heading, transponder (squawk) code
- 🚨 Emergency squawk highlighting — 7700/7500/7600 codes turn yellow automatically
- 💾 Credentials stored safely — your API keys and location live in your OS user data folder, never in the repo
- 🖥️ Cross-platform — Windows Terminal, PowerShell 7+, macOS Terminal
| Requirement | Details |
|---|---|
| .NET 10 SDK | Download here |
| Terminal | Windows Terminal or PowerShell 7+ (Windows) · Terminal.app or iTerm2 (macOS) |
| OpenSky Account | Free at opensky-network.org — anonymous access also works |
Why not CMD?
Classiccmd.exehas limited support for the Unicode characters (✈, ─, ╔, etc.) and ANSI colour sequences this app uses. Windows Terminal or PowerShell 7+ handle them perfectly.
Requires .NET 10 SDK or Runtime — the only prerequisite.
Install squawk as a global .NET tool in one command:
dotnet tool install -g squawkThen run it from anywhere in your terminal:
squawkThe first time it launches, the Setup Wizard will ask for your location and (optionally) your OpenSky API credentials.
dotnet tool update -g squawkdotnet tool uninstall -g squawkFor contributors or if you want to hack on the code:
1. Clone
git clone https://github.com/cankatabaci/squawk.git
cd squawk/src/Squawk2. Run
dotnet runNo Docker, no npm, no Python — just .NET.
The first time you run squawk, an interactive wizard starts automatically:
dotnet run
╔══════════════════════════════════════════╗
║ ◉ SQUAWK — İlk Kurulum ║
╚══════════════════════════════════════════╝
📍 Konum
▸ Konum adı (örn. Istanbul): Ankara
▸ Enlem (Latitude, örn. 39.9250): 39.9250
▸ Boylam (Longitude, örn. 32.8369): 32.8369
📡 Radar
▸ Yarıçap (km) [20]: 20
🔑 OpenSky Network API
▸ Client ID: YOUR_CLIENT_ID
▸ Client Secret: YOUR_CLIENT_SECRET
✓ Ayarlar kaydedildi.
Your answers are saved to a config file outside the project:
| OS | Config file location |
|---|---|
| Windows | %APPDATA%\squawk\config.json |
| macOS | ~/.config/squawk/config.json |
| Linux | ~/.config/squawk/config.json |
This means your credentials and location are never committed to Git.
OpenSky provides two access levels:
Just leave Client ID and Client Secret blank during setup. Anonymous access works, but has a lower daily request quota. With a 30-second refresh interval, this is usually fine for personal use.
Authenticated access gives you a higher rate limit and is free.
- Create a free account at opensky-network.org
- Log in → Account → API Clients → Create new client
- You'll get a
client_idandclient_secret - Enter them when the setup wizard asks, or run
dotnet run -- --setupto re-configure
Note: As of March 2026, OpenSky uses OAuth2 client credentials (not username/password). squawk handles the token fetch and automatic renewal for you.
# Default — use whatever you configured in the wizard
dotnet run
# Override the radar radius for this session only (doesn't change saved config)
dotnet run -- --radius 50
# Override your location for this session
dotnet run -- --lat 39.9250 --lon 32.8369
# Combine options
dotnet run -- --lat 39.9250 --lon 32.8369 --radius 30
# Re-run the setup wizard (to change location, radius, or API keys)
dotnet run -- --setup
# Help
dotnet run -- --help| Option | Type | Description |
|---|---|---|
--radius <km> |
float |
Radar radius in km (default: value from config, fallback 20) |
--lat <degrees> |
float |
Your latitude (overrides config for this session) |
--lon <degrees> |
float |
Your longitude (overrides config for this session) |
--setup |
flag | Re-run the interactive setup wizard |
--help |
flag | Show help |
N
. . · . . .
· ·
· ✈ ·
· THY448 ·
W·---------⊕---------·E
· ✈ ·
· PGT891 20km·
· ·
. . · . . .
S
| Element | Meaning |
|---|---|
⊕ |
Your position — the center of the radar |
N / S / E / W |
Compass labels — North, South, East, West |
. . · . . |
Range rings — three concentric circles at 1/3, 2/3, and full radius |
20km |
Range label — the outermost ring's distance (your configured radius) |
✈ |
Aircraft — positioned on the radar according to their real bearing and distance from you |
Callsign below ✈ |
Flight identifier — the aircraft's callsign (e.g., THY448) or ICAO address if no callsign is available |
| Rotating bright line | Sweep line — completes one full rotation every 30 seconds, in sync with the API refresh |
| Fading trail behind sweep | Persistence effect — mimics the phosphor glow fade on real radar screens |
How to read where an aircraft is:
The radar is a top-down map. If a ✈ appears in the upper-right area of the circle, that aircraft is to your northeast. If it's near the edge, it's close to your maximum range. If it's near the center, it's close to you.
Each aircraft gets two lines:
✈ THY448 8.2km E ↑
FL280 480kt SQ:2341
| Field | Example | Meaning |
|---|---|---|
✈ |
✈ |
Aircraft indicator |
| Callsign | THY448 |
The flight's radio callsign. Airlines use codes like THY (Turkish Airlines), PGT (Pegasus), SXS (SunExpress). If no callsign is broadcast, the ICAO hex address is shown instead. |
| Distance | 8.2km |
Straight-line distance from your location to the aircraft, calculated using the Haversine formula |
| Heading | E |
The direction the aircraft is flying toward (not the direction from you to the aircraft — that's shown by where the ✈ icon sits on the radar). See Compass Directions. |
| Vertical | ↑ ↓ → |
Whether the aircraft is climbing (↑), descending (↓), or in level flight (→) |
| Field | Example | Meaning |
|---|---|---|
| Flight Level | FL280 |
Altitude expressed as a Flight Level — FL280 means approximately 28,000 feet (about 8,534 metres). Divide FL by 10 to get a rough kilometre figure: FL280 ≈ 8.5 km up. |
| Speed | 480kt |
Groundspeed in knots (kt). 1 knot = 1.852 km/h. Typical cruising speed is 450–500 kt for commercial jets. |
| Squawk | SQ:2341 |
The transponder code the pilot has dialled in. A 4-digit code assigned by air traffic control to identify the flight on radar. See Special Squawk Codes. |
The HDG (Heading) column shows which direction the aircraft is flying, split into 8 compass points:
| Code | Full name | Meaning |
|---|---|---|
N |
North | Flying toward north (roughly 0° / 360°) |
NE |
Northeast | Flying toward northeast (~45°) |
E |
East | Flying toward east (~90°) |
SE |
Southeast | Flying toward southeast (~135°) |
S |
South | Flying toward south (~180°) |
SW |
Southwest | Flying toward southwest (~225°) |
W |
West | Flying toward west (~270°) |
NW |
Northwest | Flying toward northwest (~315°) |
-- |
Unknown | The aircraft is not broadcasting its heading |
Example: A flight showing SW in the HDG column is heading toward southwestern Europe or the Mediterranean. A flight showing NE might be heading toward Russia or Central Asia.
Heading vs. position on radar:
HDGtells you where the aircraft is going.
Where the✈icon sits on the radar circle tells you where the aircraft is relative to you.
These are two different things — a plane can be to your north while flying south (it already passed you and is heading away).
These codes are internationally standardised emergency transponder codes. When squawk detects one, it highlights the aircraft row in yellow:
| Code | Display | Meaning |
|---|---|---|
7700 |
SQ:7700⚠ |
General emergency — the aircraft has declared an emergency (engine failure, medical, fuel, etc.) |
7600 |
SQ:7600📻 |
Radio failure — the aircraft has lost communication with ATC |
7500 |
SQ:7500🚨 |
Hijacking — the aircraft is being hijacked |
All other codes (e.g., SQ:2341) are normal ATC-assigned codes with no special meaning visible to the public.
● Son güncelleme: 03:12:35 · 2 uçak Sonraki: 18s
| Element | Meaning |
|---|---|
● |
Green = data loaded successfully |
◌ |
Blinking = currently fetching from API |
⚠ |
Yellow = last API request failed |
Son güncelleme: HH:MM:SS |
Timestamp of the last successful data refresh |
N uçak |
Number of aircraft currently detected within your radius |
Sonraki: Ns |
Countdown in seconds until the next API request |
| Key | Action |
|---|---|
R |
Force an immediate API refresh (don't wait for the 30s timer) |
Q or Esc |
Quit squawk |
Ctrl+C |
Graceful shutdown |
Changed location? New OpenSky credentials? Run the setup wizard again:
dotnet run -- --setupOr edit the config file directly:
Windows: %APPDATA%\squawk\config.json
macOS/Linux: ~/.config/squawk/config.json
{
"OpenSky": {
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"TokenUrl": "https://auth.opensky-network.org/auth/realms/opensky-network/protocol/openid-connect/token"
},
"Location": {
"Latitude": 39.9250,
"Longitude": 32.8369,
"Name": "Ankara"
},
"Radar": {
"RadiusKm": 20,
"RefreshSeconds": 30
}
}Tip: You can also use environment variables to override any setting without touching the file, using the prefix
SQUAWK_. For example:SQUAWK_Radar__RadiusKm=50 dotnet run
To get a standalone squawk executable you can run from anywhere without dotnet run:
Windows (x64):
dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -o out/win
# Result: out/win/squawk.exemacOS (Apple Silicon):
dotnet publish -c Release -r osx-arm64 --self-contained -p:PublishSingleFile=true -o out/mac
# Result: out/mac/squawkmacOS (Intel):
dotnet publish -c Release -r osx-x64 --self-contained -p:PublishSingleFile=true -o out/mac-intelLinux (x64):
dotnet publish -c Release -r linux-x64 --self-contained -p:PublishSingleFile=true -o out/linuxAfter publishing, add the binary to your PATH to run it from anywhere as just squawk.
This is how you publish a new version so anyone can dotnet tool install -g squawk.
1. Create a NuGet account
Go to nuget.org → Sign in with your GitHub or Microsoft account → API Keys → Create → name it squawk-publish, set Glob pattern to squawk, copy the key.
2. Add the key to your GitHub repo
GitHub repo → Settings → Secrets and variables → Actions → New repository secret
Name: NUGET_API_KEY, Value: the key you just copied.
3. Fill in your details in Squawk.csproj
Replace YOUR_NAME and YOUR_GITHUB_USERNAME with your actual name and GitHub username — or just leave them as Cankat ABACI and cankatabaci if you've already filled them in.
Check the package name! Before publishing, search nuget.org/packages/squawk to see if
squawkis taken. If it is, change<PackageId>to something unique likesquawk-radarorsquawk-atc. The<ToolCommandName>squawk</ToolCommandName>can stay assquawk— that's the command users type in the terminal.
# 1. Commit all your changes
git add .
git commit -m "feat: first release"
# 2. Tag the release — this triggers the workflow
git tag v1.0.0
git push origin main --tagsThat's it. The GitHub Actions workflow automatically:
- Builds the NuGet package with version
1.0.0 - Pushes it to nuget.org
- Creates a GitHub Release
dotnet pack src/Squawk/Squawk.csproj -c Release -p:Version=1.0.0 -o nupkg
dotnet nuget push nupkg/squawk.1.0.0.nupkg \
--api-key YOUR_NUGET_API_KEY \
--source https://api.nuget.org/v3/index.json# bump <Version> in Squawk.csproj to 1.0.1, then:
git add .
git commit -m "fix: something"
git tag v1.0.1
git push origin main --tags| What | Where it lives | In the repo? |
|---|---|---|
| OpenSky Client ID & Secret | %APPDATA%\squawk\config.json |
❌ No |
| Your latitude & longitude | %APPDATA%\squawk\config.json |
❌ No |
| Default config template | src/Squawk/appsettings.json |
✅ Yes — all values are empty strings |
| Source code | src/Squawk/ |
✅ Yes — no secrets anywhere |
The appsettings.json file that ships with the source code contains only empty placeholders. Your real credentials are saved to your OS user data folder, which is listed in .gitignore.
┌─────────────────────────────────────────────────────────┐
│ Every 30 seconds │
│ 1. Compute bounding box around your location │
│ 2. GET /api/states/all?lamin=...&lomin=... │
│ with Bearer token (OAuth2 client_credentials) │
│ 3. Parse JSON state vectors │
│ 4. Calculate Haversine distance for each aircraft │
│ 5. Filter to radius, sort by distance │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Every ~80ms (12 FPS) │
│ 1. Advance sweep angle by 1.2° │
│ (360° / 30s = 12°/s = 1.2° per 80ms frame) │
│ 2. Draw to off-screen char buffer (clear → rings → │
│ crosshairs → sweep trail → sweep line → aircraft) │
│ 3. Diff render: only write changed terminal cells │
│ → no flicker, minimal I/O │
└─────────────────────────────────────────────────────────┘
Key design choices:
- No external UI library — raw ANSI escape codes for full control and minimal dependencies
- Diff rendering — the canvas compares current and previous frame; only changed characters are written to the terminal, eliminating flicker
- Aspect ratio correction — terminal character cells are roughly 2× taller than wide, so the vertical radar radius is set to half the horizontal radius to produce a visually circular display
- Bearing → screen position — aircraft are placed using
sin(bearing)for X and-cos(bearing)for Y, which correctly maps geographic bearings (0° = North, clockwise) to screen coordinates (Y increases downward)
MIT — do whatever you want with it.