An open API service indexing awesome lists of open source software.

https://github.com/danielgerlag/air-traffic


https://github.com/danielgerlag/air-traffic

Last synced: 8 days ago
JSON representation

Awesome Lists containing this project

README

          

# Air Traffic

Air Traffic icon

> Remote GitHub Copilot orchestration via Slack or Discord β€” from your phone.

## Overview

Air Traffic runs as a daemon on your development machine, watches Slack or Discord channels for commands and prompts, and bridges them to GitHub Copilot's agent SDK. You prompt Copilot from your phone (or any messaging client), and the agent executes tasks in your project directories β€” editing files, running shells, committing code β€” while streaming results back.

## Features

### πŸ–₯️ Multi-Machine Support

Run Air Traffic on every machine you work with β€” desktop, laptop, cloud VM, CI box. Each machine gets its own bot (created via `npx air-traffic init`), so events are routed directly with zero cross-daemon coordination. DM each bot to manage that machine's projects. You manage your entire fleet from your phone.

### πŸ”Œ Multi-Platform Support

Air Traffic supports both **Slack** and **Discord** as messaging platforms. The core logic is platform-agnostic β€” all platform communication goes through the `MessagingAdapter` interface. See the platform-specific setup guides:

- [Slack Setup Guide](docs/slack-setup.md)
- [Discord Setup Guide](docs/discord-setup.md)

### πŸ”— Session Sharing with Local Copilot CLI

Air Traffic can join any existing Copilot CLI session running on your machine. Run `copilot` locally to start a session, then join it remotely with `join ` β€” output streams to the Slack channel and you can continue prompting from your phone. Sessions are listed with `sessions`, showing ID prefix, task summary, branch, working directory, and age. Prefix-matching means you only need to type the first few characters of the session ID.

### πŸ“Έ Screenshot Capture & Auto-Upload

When Copilot takes a browser screenshot (via Playwright), the image is automatically uploaded to the project's Slack channel. The agent's tool output is also scanned for file paths matching image/PDF extensions (`.png`, `.jpg`, `.gif`, `.svg`, `.webp`, `.pdf`), and any matches are uploaded. You see exactly what Copilot sees without leaving Slack.

### πŸ“Ž File Uploads via Slack

Drop a file into a project channel and it's saved directly to the project's working directory, making it available to Copilot immediately. Useful for sharing config files, design mockups, CSVs, or any reference material the agent might need. Files are downloaded via Slack's authenticated URL and saved to the project root.

### πŸ“„ Auto-Upload of Created Artifacts

When Copilot creates files with uploadable extensions (images, PDFs, SVGs), they're automatically posted to the channel. Plan files (`plan.md`, `PLAN.md`) are detected and uploaded when the task completes with a πŸ“‹ marker, so you can review the plan before the agent continues.

### πŸ€– Slack AI Assistant Status

Air Traffic integrates with Slack's `assistant.threads.setStatus` API to show live progress in the thread. The status line updates with the current intent (e.g., "is exploring codebase"), and `loading_messages` cycle through recent tool calls (`βš™οΈ grep β€” *.ts`, `βœ… view β€” config.ts`). Status is re-asserted after every message to stay visible.

### πŸ’¬ DM-Based Control

Just DM the bot directly β€” no dedicated control channel needed. All control commands (`create`, `list`, `config`, `sessions`, `join`, `status`, `models`, `menu`) work in DMs. You can also type naturally β€” "make a project called my-app" or "what's running?" β€” and the NL intent classifier routes to the right command. First-time DMs show an interactive welcome menu.

### 🧠 Natural Language Commands

Air Traffic includes a local NL intent classifier that maps natural phrases to commands β€” no LLM needed, zero latency. Examples:
- "make a project called api-server" β†’ `create api-server`
- "what's running?" β†’ `status`
- "show me my projects" β†’ `list`
- "switch to gpt-5" β†’ `model gpt-5`

### πŸŽ›οΈ Interactive Menu & Pickers

Type `menu` (or tap a menu button) to get an interactive Block Kit menu with buttons for all common actions. Commands with optional parameters show interactive dropdowns when arguments are omitted β€” `model` shows a picker of available models, `mode` displays all modes with descriptions, `config` walks through a guided wizard, `join` without an ID shows a session picker.

### πŸ” Granular Permission Controls

Each project has per-category permission policies: file edits, file creates, shell commands, git operations, and network access. Each category can be set to `auto` (approve silently) or `ask` (prompt in Slack with Allow / Always Allow / Deny buttons). "Always Allow" persists the decision to the project config. Common tools like `report_intent` and `ask_user` always bypass permission checks.

### 🚦 Agent Modes

Three operating modes control how Copilot handles prompts:

- **Normal** β€” Default behavior, full interactive agent
- **Plan** β€” Prepends `[[PLAN]]` to prompts, making Copilot generate a detailed plan for review before implementing
- **Autopilot** β€” Runs without confirmation prompts, suitable for well-defined tasks

Switch modes with `mode` in a DM or `!mode` in a project channel.

### 🧠 Session Management

List all Copilot CLI sessions on the machine with `sessions` β€” see managed vs. unmanaged sessions, their working directories, branches, and ages. Join any session by ID prefix. When joining from a DM, the system auto-creates a project and Slack channel if needed, deriving the project name from the session's working directory.

### 🌐 Web Console

A built-in web dashboard (React + Tailwind) runs alongside the daemon on a configurable port. It provides:

- **Dashboard** β€” View all projects, create/delete projects, machine status
- **Project View** β€” Live terminal output with streamed deltas, send prompts, abort sessions
- **Session History** β€” Load conversation history for active sessions
- **File Browser** β€” Navigate project files with path-traversal protection
- **Git Info** β€” Branch, remote, status, last commit
- **Config** β€” Change model, agent, mode, and permission settings per project
- **Settings** β€” Machine info, available models, default permissions

### πŸ“ Markdown β†’ Slack mrkdwn Conversion

Copilot outputs standard Markdown, but Slack uses its own mrkdwn format. Air Traffic converts on the fly: `**bold**` β†’ `*bold*`, `# Heading` β†’ `*Heading*`, `[link](url)` β†’ ``, `- list` β†’ `β€’ list`, and code blocks are preserved. The system prompt also instructs Copilot to prefer Slack-native formatting.

### πŸ—οΈ Project Isolation

Each project gets its own Slack channel (`#atc--`), working directory, Copilot session, model selection, agent type, permission policy, and operating mode. Projects can be cloned from a repo (`create my-app --from https://github.com/user/repo`) or created as empty directories.

### πŸ”Œ Messaging Abstraction

The core logic is platform-agnostic. All platform communication goes through the `MessagingAdapter` interface β€” channels, messages, questions, permissions, file uploads, thread status. Slack is the first adapter; Discord, Teams, and others can be added by implementing the interface and swapping it in `src/index.ts`.

## Prerequisites

- **Node.js 18+**
- **GitHub Copilot CLI** installed and authenticated β€” verify with `copilot --version`
- **Active GitHub Copilot subscription** (Individual, Business, or Enterprise)
- **Slack workspace** or **Discord server** with admin access

## Platform Setup

Choose your messaging platform:

- **Slack** β†’ [docs/slack-setup.md](docs/slack-setup.md)
- **Discord** β†’ [docs/discord-setup.md](docs/discord-setup.md)

### Quick Start

```bash
npx air-traffic init
```

The setup wizard asks for your **platform** (Slack or Discord), machine name, and walks you through creating the bot and collecting tokens. It writes a `.env` file ready to go.

## Installation

### Quick Start with npx

Run Air Traffic directly without installing β€” just make sure your environment variables are set:

```bash
# Set required env vars (or use a .env file in the current directory)
export SLACK_BOT_TOKEN=xoxb-...
export SLACK_APP_TOKEN=xapp-...
export SLACK_SIGNING_SECRET=...
export ATC_MACHINE_NAME=my-machine

npx air-traffic
```

### Global Install

Install globally to get the `air-traffic` command:

```bash
npm install -g air-traffic
air-traffic
```

### From Source

```bash
git clone https://github.com/danielgerlag/air-traffic.git
cd air-traffic
npm install
cp .env.example .env
```

Edit `.env` with your Slack credentials and machine config:

```env
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
SLACK_SIGNING_SECRET=your-signing-secret
ATC_MACHINE_NAME=my-machine
```

Then run:

```bash
npm run dev
```

## Configuration

| Variable | Description | Default |
|---|---|---|
| `ATC_PLATFORM` | Messaging platform: `slack` or `discord` | `slack` |
| `ATC_MACHINE_NAME` | Unique name for this machine (e.g. `desktop`, `laptop`) | *required* |
| `ATC_PROJECTS_DIR` | Directory where project working copies are created | `~/projects` |
| `ATC_DATA_DIR` | Directory for project metadata and config storage | `~/.air-traffic/data` |
| `ATC_DEFAULT_MODEL` | Default Copilot model for new projects | `claude-sonnet-4.5` |
| `ATC_LOG_LEVEL` | Log verbosity: `error`, `warn`, `info`, `debug` | `info` |
| `ATC_WEB_PORT` | Port for the Air Traffic Console web UI | `8089` |
| `ATC_PERMISSION_TIMEOUT_MS` | Timeout for permission prompts (ms) | `300000` |
| `ATC_QUESTION_TIMEOUT_MS` | Timeout for agent questions (ms) | `300000` |

**Slack-specific** (when `ATC_PLATFORM=slack`):

| Variable | Description |
|---|---|
| `SLACK_BOT_TOKEN` | Bot User OAuth Token (`xoxb-...`) |
| `SLACK_APP_TOKEN` | App-Level Token with `connections:write` (`xapp-...`) |
| `SLACK_SIGNING_SECRET` | Slack app signing secret |

**Discord-specific** (when `ATC_PLATFORM=discord`):

| Variable | Description |
|---|---|
| `DISCORD_BOT_TOKEN` | Discord bot token |
| `DISCORD_GUILD_ID` | Discord server (guild) ID |
| `DISCORD_SPINNER_EMOJI` | Optional animated spinner emoji (e.g. ``) |

## Running

```bash
# Quick run (no install needed)
npx air-traffic

# Development (with hot-reload via tsx)
npm run dev

# Production
npm run build
npm start

# With PM2 (recommended for always-on daemon)
npm install -g pm2
pm2 start dist/index.js --name air-traffic
pm2 save
pm2 startup
```

## Air Traffic Console (Web UI)

The Console is a web dashboard that runs alongside the daemon on port 8089. It provides a browser-based interface for:

- **Dashboard** β€” View all projects, create/delete projects, see machine status
- **Project View** β€” Live terminal output, send prompts, abort sessions
- **Config** β€” Change model, agent, and permission settings per project
- **Settings** β€” View machine info, available models, and default permissions

### Development

Run the daemon and Vite dev server side by side:

```bash
# Terminal 1: Backend daemon
npm run dev

# Terminal 2: Frontend dev server (with hot-reload + proxy)
npm run console:dev
```

The Vite dev server runs on `http://localhost:5173` and proxies API/Socket.IO calls to `localhost:8089`.

### Production

Build the frontend, then run the daemon β€” it serves the compiled assets automatically:

```bash
npm run console:build # Compiles frontend to web/dist/
npm run build # Compile backend TypeScript
npm start # Serves everything on port 8089
```

Open `http://localhost:8089` to access the Console.

## Multi-Machine Setup

1. Run `npx air-traffic init` on each machine β€” give each a unique name (e.g. `desktop`, `laptop`, `cloud-dev`).
2. Each machine gets its own Slack app with a name like "ATC Desktop", "ATC Laptop", etc.
3. DM each bot to control that machine's projects.
4. No shared credentials, no routing conflicts β€” each app is independent.

## Command Reference

Commands are sent via DM to the bot or with `!` prefix in project channels. You can also type naturally β€” the NL intent classifier handles common phrases.

### DM Commands (control)

| Command | Description | Example |
|---|---|---|
| `create [--from ]` | Create a new project (optionally clone a repo) | `create my-app --from https://github.com/user/repo` |
| `delete [name]` | Delete a project (picker if omitted) | `delete my-app` |
| `list` | List all projects | `list` |
| `config [project] [field] [value]` | Update project config (guided wizard if omitted) | `config my-app model gpt-5` |
| `status` | Show machine status and active sessions | `status` |
| `models` | List available Copilot models | `models` |
| `sessions` | List all Copilot CLI sessions | `sessions` |
| `join [session-id]` | Join a session (picker if omitted) | `join a1b2` |
| `menu` | Show interactive menu with action buttons | `menu` |
| `help` | Show command reference | `help` |

### Project Channel Commands (`#atc--`)

In project channels, regular messages are sent as prompts to Copilot. Use `!` prefix for commands:

| Command | Description | Example |
|---|---|---|
| *(any text)* | Send as a prompt to the Copilot agent | `Add user authentication with JWT` |
| `!model [name]` | Change the model (picker if omitted) | `!model gpt-5` |
| `!agent [name]` | Set the agent type | `!agent code-review` |
| `!mode [mode]` | Set operating mode (picker if omitted) | `!mode autopilot` |
| `!status` | Show project status and session state | `!status` |
| `!abort` | Abort the current agent session | `!abort` |
| `!diff` | Show `git diff` of the project directory | `!diff` |
| `!history` | Show session message history | `!history` |
| `!sessions` | List all Copilot CLI sessions | `!sessions` |
| `!join [id]` | Join a session (picker if omitted) | `!join a1b2` |
| `!leave` | Detach from session without killing it | `!leave` |
| `!help` | Show project command reference | `!help` |

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Slack / Discord β”‚
β”‚ (phone / desktop client) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ Socket Mode / β”‚
β”‚ Gateway β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SlackAdapter / DiscordAdapter β”‚
β”‚ (implements MessagingAdapter) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ AirTrafficDaemon β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ ProjectMgr β”‚ β”‚ SessionOrchestrator β”‚ β”‚
β”‚ β”‚ (CRUD, store)β”‚ β”‚ (CopilotClient, pool) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚PermissionMgrβ”‚ β”‚ ModelRegistry β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ AgentSession (per project) β”‚
β”‚ - Copilot SDK session with streaming β”‚
β”‚ - Delta batching for Slack messages β”‚
β”‚ - Permission & question flow via adapter β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ GitHub Copilot SDK β”‚
β”‚ (CopilotClient / CopilotSession) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

The `MessagingAdapter` interface (`src/messaging/types.ts`) abstracts all platform-specific communication. To add a new platform, implement the interface and swap it in `src/index.ts`.

## Development

```bash
npm test # Run tests
npm run test:watch # Watch mode
npm run build # Compile TypeScript
```

### Project Structure

```
src/
β”œβ”€β”€ config.ts # Env var loading + Zod validation
β”œβ”€β”€ daemon.ts # AirTrafficDaemon β€” main command router
β”œβ”€β”€ index.ts # Entry point β€” wires config, adapter, daemon
β”œβ”€β”€ cli/
β”‚ β”œβ”€β”€ init.ts # Setup wizard (npx air-traffic init)
β”‚ └── manifest-template.ts # Generates per-machine Slack app manifest
β”œβ”€β”€ copilot/
β”‚ β”œβ”€β”€ agent-session.ts # Per-project Copilot session with streaming
β”‚ β”œβ”€β”€ session-orchestrator.ts# CopilotClient lifecycle + session pool
β”‚ β”œβ”€β”€ permission-manager.ts # Tool β†’ category mapping + policy check
β”‚ └── model-registry.ts # Known model list
β”œβ”€β”€ messaging/
β”‚ β”œβ”€β”€ types.ts # Platform-agnostic interfaces
β”‚ β”œβ”€β”€ adapter.ts # BaseMessagingAdapter (shared event dispatch)
β”‚ β”œβ”€β”€ in-memory-adapter.ts # Test double
β”‚ β”œβ”€β”€ intent.ts # NL intent classifier (synonym/keyword map)
β”‚ └── slack/
β”‚ β”œβ”€β”€ slack-adapter.ts # Slack Bolt integration
β”‚ β”œβ”€β”€ commands.ts # Message parsing (control + project channels)
β”‚ β”œβ”€β”€ formatters.ts # Block Kit formatting (help, menu, welcome)
β”‚ └── presence.ts # Heartbeat manager
β”‚ └── discord/
β”‚ β”œβ”€β”€ discord-adapter.ts # Discord.js integration
β”‚ β”œβ”€β”€ formatters.ts # Embed/component formatting
β”‚ └── markdown.ts # Slack mrkdwn β†’ Discord markdown
β”œβ”€β”€ projects/
β”‚ β”œβ”€β”€ types.ts # ProjectConfig, PermissionPolicy
β”‚ β”œβ”€β”€ project-manager.ts # CRUD + validation
β”‚ └── project-store.ts # JSON file persistence
β”œβ”€β”€ utils/
β”‚ β”œβ”€β”€ errors.ts # Typed error hierarchy
β”‚ └── logger.ts # Winston logger
└── web/
β”œβ”€β”€ server.ts # Express + Socket.IO server
β”œβ”€β”€ api-routes.ts # REST API endpoints
β”œβ”€β”€ socket-handlers.ts # Socket.IO event handlers
└── session-bridge.ts # AgentSession β†’ Socket.IO bridge

web/ # React frontend (Vite + Tailwind)
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ App.tsx # Router + layout
β”‚ β”œβ”€β”€ pages/ # Dashboard, ProjectView, Settings
β”‚ β”œβ”€β”€ components/ # SessionTerminal, PromptInput, ConfigPanel
β”‚ β”œβ”€β”€ hooks/ # useProjects, useSession, useStatus
β”‚ └── lib/ # API client, Socket.IO client, types
└── dist/ # Built frontend (served by Express)
```

## License

MIT