{"id":50434649,"url":"https://github.com/danielgerlag/air-traffic","last_synced_at":"2026-05-31T16:30:47.834Z","repository":{"id":343373489,"uuid":"1176380178","full_name":"danielgerlag/air-traffic","owner":"danielgerlag","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-10T03:03:28.000Z","size":429,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-10T10:53:11.185Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielgerlag.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-09T00:51:58.000Z","updated_at":"2026-03-10T03:03:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/danielgerlag/air-traffic","commit_stats":null,"previous_names":["danielgerlag/air-traffic"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/danielgerlag/air-traffic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fair-traffic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fair-traffic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fair-traffic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fair-traffic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielgerlag","download_url":"https://codeload.github.com/danielgerlag/air-traffic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielgerlag%2Fair-traffic/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33739860,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T16:30:47.109Z","updated_at":"2026-05-31T16:30:47.828Z","avatar_url":"https://github.com/danielgerlag.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Air Traffic\n\n\u003cimg src=\"assets/icon.svg\" width=\"128\" alt=\"Air Traffic icon\" /\u003e\n\n\u003e Remote GitHub Copilot orchestration via Slack or Discord — from your phone.\n\n## Overview\n\nAir 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.\n\n## Features\n\n### 🖥️ Multi-Machine Support\n\nRun 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.\n\n### 🔌 Multi-Platform Support\n\nAir 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:\n\n- [Slack Setup Guide](docs/slack-setup.md)\n- [Discord Setup Guide](docs/discord-setup.md)\n\n### 🔗 Session Sharing with Local Copilot CLI\n\nAir 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 \u003csession-id\u003e` — 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.\n\n### 📸 Screenshot Capture \u0026 Auto-Upload\n\nWhen 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.\n\n### 📎 File Uploads via Slack\n\nDrop 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.\n\n### 📄 Auto-Upload of Created Artifacts\n\nWhen 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.\n\n### 🤖 Slack AI Assistant Status\n\nAir 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.\n\n### 💬 DM-Based Control\n\nJust 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.\n\n### 🧠 Natural Language Commands\n\nAir Traffic includes a local NL intent classifier that maps natural phrases to commands — no LLM needed, zero latency. Examples:\n- \"make a project called api-server\" → `create api-server`\n- \"what's running?\" → `status`\n- \"show me my projects\" → `list`\n- \"switch to gpt-5\" → `model gpt-5`\n\n### 🎛️ Interactive Menu \u0026 Pickers\n\nType `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.\n\n### 🔐 Granular Permission Controls\n\nEach 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.\n\n### 🚦 Agent Modes\n\nThree operating modes control how Copilot handles prompts:\n\n- **Normal** — Default behavior, full interactive agent\n- **Plan** — Prepends `[[PLAN]]` to prompts, making Copilot generate a detailed plan for review before implementing\n- **Autopilot** — Runs without confirmation prompts, suitable for well-defined tasks\n\nSwitch modes with `mode` in a DM or `!mode` in a project channel.\n\n### 🧠 Session Management\n\nList 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.\n\n### 🌐 Web Console\n\nA built-in web dashboard (React + Tailwind) runs alongside the daemon on a configurable port. It provides:\n\n- **Dashboard** — View all projects, create/delete projects, machine status\n- **Project View** — Live terminal output with streamed deltas, send prompts, abort sessions\n- **Session History** — Load conversation history for active sessions\n- **File Browser** — Navigate project files with path-traversal protection\n- **Git Info** — Branch, remote, status, last commit\n- **Config** — Change model, agent, mode, and permission settings per project\n- **Settings** — Machine info, available models, default permissions\n\n### 📝 Markdown → Slack mrkdwn Conversion\n\nCopilot outputs standard Markdown, but Slack uses its own mrkdwn format. Air Traffic converts on the fly: `**bold**` → `*bold*`, `# Heading` → `*Heading*`, `[link](url)` → `\u003curl|link\u003e`, `- list` → `• list`, and code blocks are preserved. The system prompt also instructs Copilot to prefer Slack-native formatting.\n\n### 🏗️ Project Isolation\n\nEach project gets its own Slack channel (`#atc-\u003cmachine\u003e-\u003cproject\u003e`), 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.\n\n### 🔌 Messaging Abstraction\n\nThe 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`.\n\n## Prerequisites\n\n- **Node.js 18+**\n- **GitHub Copilot CLI** installed and authenticated — verify with `copilot --version`\n- **Active GitHub Copilot subscription** (Individual, Business, or Enterprise)\n- **Slack workspace** or **Discord server** with admin access\n\n## Platform Setup\n\nChoose your messaging platform:\n\n- **Slack** → [docs/slack-setup.md](docs/slack-setup.md)\n- **Discord** → [docs/discord-setup.md](docs/discord-setup.md)\n\n### Quick Start\n\n```bash\nnpx air-traffic init\n```\n\nThe 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.\n\n## Installation\n\n### Quick Start with npx\n\nRun Air Traffic directly without installing — just make sure your environment variables are set:\n\n```bash\n# Set required env vars (or use a .env file in the current directory)\nexport SLACK_BOT_TOKEN=xoxb-...\nexport SLACK_APP_TOKEN=xapp-...\nexport SLACK_SIGNING_SECRET=...\nexport ATC_MACHINE_NAME=my-machine\n\nnpx air-traffic\n```\n\n### Global Install\n\nInstall globally to get the `air-traffic` command:\n\n```bash\nnpm install -g air-traffic\nair-traffic\n```\n\n### From Source\n\n```bash\ngit clone https://github.com/danielgerlag/air-traffic.git\ncd air-traffic\nnpm install\ncp .env.example .env\n```\n\nEdit `.env` with your Slack credentials and machine config:\n\n```env\nSLACK_BOT_TOKEN=xoxb-your-bot-token\nSLACK_APP_TOKEN=xapp-your-app-token\nSLACK_SIGNING_SECRET=your-signing-secret\nATC_MACHINE_NAME=my-machine\n```\n\nThen run:\n\n```bash\nnpm run dev\n```\n\n## Configuration\n\n| Variable | Description | Default |\n|---|---|---|\n| `ATC_PLATFORM` | Messaging platform: `slack` or `discord` | `slack` |\n| `ATC_MACHINE_NAME` | Unique name for this machine (e.g. `desktop`, `laptop`) | *required* |\n| `ATC_PROJECTS_DIR` | Directory where project working copies are created | `~/projects` |\n| `ATC_DATA_DIR` | Directory for project metadata and config storage | `~/.air-traffic/data` |\n| `ATC_DEFAULT_MODEL` | Default Copilot model for new projects | `claude-sonnet-4.5` |\n| `ATC_LOG_LEVEL` | Log verbosity: `error`, `warn`, `info`, `debug` | `info` |\n| `ATC_WEB_PORT` | Port for the Air Traffic Console web UI | `8089` |\n| `ATC_PERMISSION_TIMEOUT_MS` | Timeout for permission prompts (ms) | `300000` |\n| `ATC_QUESTION_TIMEOUT_MS` | Timeout for agent questions (ms) | `300000` |\n\n**Slack-specific** (when `ATC_PLATFORM=slack`):\n\n| Variable | Description |\n|---|---|\n| `SLACK_BOT_TOKEN` | Bot User OAuth Token (`xoxb-...`) |\n| `SLACK_APP_TOKEN` | App-Level Token with `connections:write` (`xapp-...`) |\n| `SLACK_SIGNING_SECRET` | Slack app signing secret |\n\n**Discord-specific** (when `ATC_PLATFORM=discord`):\n\n| Variable | Description |\n|---|---|\n| `DISCORD_BOT_TOKEN` | Discord bot token |\n| `DISCORD_GUILD_ID` | Discord server (guild) ID |\n| `DISCORD_SPINNER_EMOJI` | Optional animated spinner emoji (e.g. `\u003ca:loading:123\u003e`) |\n\n## Running\n\n```bash\n# Quick run (no install needed)\nnpx air-traffic\n\n# Development (with hot-reload via tsx)\nnpm run dev\n\n# Production\nnpm run build\nnpm start\n\n# With PM2 (recommended for always-on daemon)\nnpm install -g pm2\npm2 start dist/index.js --name air-traffic\npm2 save\npm2 startup\n```\n\n## Air Traffic Console (Web UI)\n\nThe Console is a web dashboard that runs alongside the daemon on port 8089. It provides a browser-based interface for:\n\n- **Dashboard** — View all projects, create/delete projects, see machine status\n- **Project View** — Live terminal output, send prompts, abort sessions\n- **Config** — Change model, agent, and permission settings per project\n- **Settings** — View machine info, available models, and default permissions\n\n### Development\n\nRun the daemon and Vite dev server side by side:\n\n```bash\n# Terminal 1: Backend daemon\nnpm run dev\n\n# Terminal 2: Frontend dev server (with hot-reload + proxy)\nnpm run console:dev\n```\n\nThe Vite dev server runs on `http://localhost:5173` and proxies API/Socket.IO calls to `localhost:8089`.\n\n### Production\n\nBuild the frontend, then run the daemon — it serves the compiled assets automatically:\n\n```bash\nnpm run console:build   # Compiles frontend to web/dist/\nnpm run build           # Compile backend TypeScript\nnpm start               # Serves everything on port 8089\n```\n\nOpen `http://localhost:8089` to access the Console.\n\n## Multi-Machine Setup\n\n1. Run `npx air-traffic init` on each machine — give each a unique name (e.g. `desktop`, `laptop`, `cloud-dev`).\n2. Each machine gets its own Slack app with a name like \"ATC Desktop\", \"ATC Laptop\", etc.\n3. DM each bot to control that machine's projects.\n4. No shared credentials, no routing conflicts — each app is independent.\n\n## Command Reference\n\nCommands 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.\n\n### DM Commands (control)\n\n| Command | Description | Example |\n|---|---|---|\n| `create \u003cname\u003e [--from \u003curl\u003e]` | Create a new project (optionally clone a repo) | `create my-app --from https://github.com/user/repo` |\n| `delete [name]` | Delete a project (picker if omitted) | `delete my-app` |\n| `list` | List all projects | `list` |\n| `config [project] [field] [value]` | Update project config (guided wizard if omitted) | `config my-app model gpt-5` |\n| `status` | Show machine status and active sessions | `status` |\n| `models` | List available Copilot models | `models` |\n| `sessions` | List all Copilot CLI sessions | `sessions` |\n| `join [session-id]` | Join a session (picker if omitted) | `join a1b2` |\n| `menu` | Show interactive menu with action buttons | `menu` |\n| `help` | Show command reference | `help` |\n\n### Project Channel Commands (`#atc-\u003cmachine\u003e-\u003cproject\u003e`)\n\nIn project channels, regular messages are sent as prompts to Copilot. Use `!` prefix for commands:\n\n| Command | Description | Example |\n|---|---|---|\n| *(any text)* | Send as a prompt to the Copilot agent | `Add user authentication with JWT` |\n| `!model [name]` | Change the model (picker if omitted) | `!model gpt-5` |\n| `!agent [name]` | Set the agent type | `!agent code-review` |\n| `!mode [mode]` | Set operating mode (picker if omitted) | `!mode autopilot` |\n| `!status` | Show project status and session state | `!status` |\n| `!abort` | Abort the current agent session | `!abort` |\n| `!diff` | Show `git diff` of the project directory | `!diff` |\n| `!history` | Show session message history | `!history` |\n| `!sessions` | List all Copilot CLI sessions | `!sessions` |\n| `!join [id]` | Join a session (picker if omitted) | `!join a1b2` |\n| `!leave` | Detach from session without killing it | `!leave` |\n| `!help` | Show project command reference | `!help` |\n\n## Architecture\n\n```\n┌──────────────────────────────────────────────┐\n│              Slack / Discord                 │\n│         (phone / desktop client)             │\n└──────────────┬───────────────────┬───────────┘\n               │  Socket Mode /    │\n               │  Gateway          │\n┌──────────────▼───────────────────▼───────────┐\n│     SlackAdapter / DiscordAdapter            │\n│    (implements MessagingAdapter)              │\n├──────────────────────────────────────────────┤\n│           AirTrafficDaemon                       │\n│  ┌─────────────┐  ┌────────────────────────┐  │\n│  │ ProjectMgr   │  │ SessionOrchestrator    │  │\n│  │ (CRUD, store)│  │ (CopilotClient, pool)  │  │\n│  └─────────────┘  └────────────────────────┘  │\n│  ┌─────────────┐  ┌────────────────────────┐  │\n│  │PermissionMgr│  │ ModelRegistry           │  │\n│  └─────────────┘  └────────────────────────┘  │\n├───────────────────────────────────────────────┤\n│           AgentSession (per project)          │\n│  - Copilot SDK session with streaming         │\n│  - Delta batching for Slack messages          │\n│  - Permission \u0026 question flow via adapter     │\n└──────────────┬────────────────────────────────┘\n               │\n┌──────────────▼────────────────────────────────┐\n│        GitHub Copilot SDK                     │\n│   (CopilotClient / CopilotSession)            │\n└───────────────────────────────────────────────┘\n```\n\nThe `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`.\n\n## Development\n\n```bash\nnpm test           # Run tests\nnpm run test:watch # Watch mode\nnpm run build      # Compile TypeScript\n```\n\n### Project Structure\n\n```\nsrc/\n├── config.ts                  # Env var loading + Zod validation\n├── daemon.ts                  # AirTrafficDaemon — main command router\n├── index.ts                   # Entry point — wires config, adapter, daemon\n├── cli/\n│   ├── init.ts                # Setup wizard (npx air-traffic init)\n│   └── manifest-template.ts   # Generates per-machine Slack app manifest\n├── copilot/\n│   ├── agent-session.ts       # Per-project Copilot session with streaming\n│   ├── session-orchestrator.ts# CopilotClient lifecycle + session pool\n│   ├── permission-manager.ts  # Tool → category mapping + policy check\n│   └── model-registry.ts     # Known model list\n├── messaging/\n│   ├── types.ts               # Platform-agnostic interfaces\n│   ├── adapter.ts             # BaseMessagingAdapter (shared event dispatch)\n│   ├── in-memory-adapter.ts   # Test double\n│   ├── intent.ts              # NL intent classifier (synonym/keyword map)\n│   └── slack/\n│       ├── slack-adapter.ts   # Slack Bolt integration\n│       ├── commands.ts        # Message parsing (control + project channels)\n│       ├── formatters.ts      # Block Kit formatting (help, menu, welcome)\n│       └── presence.ts        # Heartbeat manager\n│   └── discord/\n│       ├── discord-adapter.ts # Discord.js integration\n│       ├── formatters.ts      # Embed/component formatting\n│       └── markdown.ts        # Slack mrkdwn → Discord markdown\n├── projects/\n│   ├── types.ts               # ProjectConfig, PermissionPolicy\n│   ├── project-manager.ts     # CRUD + validation\n│   └── project-store.ts       # JSON file persistence\n├── utils/\n│   ├── errors.ts              # Typed error hierarchy\n│   └── logger.ts              # Winston logger\n└── web/\n    ├── server.ts              # Express + Socket.IO server\n    ├── api-routes.ts          # REST API endpoints\n    ├── socket-handlers.ts     # Socket.IO event handlers\n    └── session-bridge.ts      # AgentSession → Socket.IO bridge\n\nweb/                           # React frontend (Vite + Tailwind)\n├── src/\n│   ├── App.tsx                # Router + layout\n│   ├── pages/                 # Dashboard, ProjectView, Settings\n│   ├── components/            # SessionTerminal, PromptInput, ConfigPanel\n│   ├── hooks/                 # useProjects, useSession, useStatus\n│   └── lib/                   # API client, Socket.IO client, types\n└── dist/                      # Built frontend (served by Express)\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielgerlag%2Fair-traffic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielgerlag%2Fair-traffic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielgerlag%2Fair-traffic/lists"}