{"id":30143127,"url":"https://github.com/juspay/shooter","last_synced_at":"2026-05-15T05:17:32.041Z","repository":{"id":308034967,"uuid":"1031419065","full_name":"juspay/shooter","owner":"juspay","description":"Turn your phone into a remote control for AI coding sessions running on your dev machine.","archived":false,"fork":false,"pushed_at":"2026-05-09T12:05:29.000Z","size":3438,"stargazers_count":3,"open_issues_count":3,"forks_count":3,"subscribers_count":0,"default_branch":"release","last_synced_at":"2026-05-09T14:15:45.228Z","etag":null,"topics":["ai","claude-code","development","open-code","productivity","remote-control"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juspay.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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":"2025-08-03T17:41:46.000Z","updated_at":"2026-05-09T12:05:32.000Z","dependencies_parsed_at":"2025-08-03T19:33:00.096Z","dependency_job_id":"29497320-aeb9-4b31-9f51-027542d4ddcc","html_url":"https://github.com/juspay/shooter","commit_stats":null,"previous_names":["juspay/shooter"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/juspay/shooter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juspay%2Fshooter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juspay%2Fshooter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juspay%2Fshooter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juspay%2Fshooter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juspay","download_url":"https://codeload.github.com/juspay/shooter/tar.gz/refs/heads/release","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juspay%2Fshooter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33054670,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-15T02:00:06.351Z","response_time":103,"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":["ai","claude-code","development","open-code","productivity","remote-control"],"created_at":"2025-08-11T06:49:51.910Z","updated_at":"2026-05-15T05:17:32.020Z","avatar_url":"https://github.com/juspay.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Shooter\n\n**Mobile push notifications and remote terminal access for AI coding sessions.**\n\n[![SvelteKit](https://img.shields.io/badge/SvelteKit-FF3E00?logo=svelte\u0026logoColor=white)](https://kit.svelte.dev/)\n[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript\u0026logoColor=white)](https://www.typescriptlang.org/)\n[![Node.js](https://img.shields.io/badge/Node.js_20+-339933?logo=node.js\u0026logoColor=white)](https://nodejs.org/)\n[![WebSocket](https://img.shields.io/badge/WebSocket-010101?logo=socket.io\u0026logoColor=white)](#websocket-channels)\n[![Docker](https://img.shields.io/badge/Docker-2496ED?logo=docker\u0026logoColor=white)](#docker)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n---\n\n## What is Shooter?\n\nShooter turns your phone into a remote control for AI coding sessions running on your dev machine. It delivers push notifications to iOS and Android when Claude Code or OpenCode events occur -- tool usage, permission requests, session completions -- and lets you approve or deny permission prompts directly from a notification. You can also launch remote terminal sessions, stream output in real time, and browse structured AI conversation history, all from a mobile-optimized web interface accessible anywhere through a Cloudflare Tunnel.\n\n## Features\n\n- **Push notifications** -- Real-time alerts for tool usage, permission requests, session starts/stops, errors, and task completions (iOS via APNs, Android via FCM)\n- **Bidirectional permissions** -- Approve or deny Claude Code permission prompts from your phone; the hook blocks until you respond\n- **Remote terminal** -- Launch shell, Claude Code, or OpenCode sessions from your phone with full xterm.js rendering\n- **Terminal persistence** -- PTY processes run in holder processes that survive server restarts; metadata persisted in SQLite\n- **Structured Chat view** -- AI conversations rendered as message bubbles with tool-use cards and thinking indicators, parsed live from JSONL session files\n- **Session browser** -- Browse coding session history across all projects\n- **QR code pairing** -- Scan a QR code from the `/config` page to connect mobile apps to the server\n- **WebSocket streaming** -- Three multiplexed channels: terminal I/O, session updates, and global events\n- **Quick keys** -- Mobile-optimized touch bar for Ctrl+C, Tab, arrow keys, Esc, and other special characters\n- **Claude Code hooks** -- Lifecycle hooks for 13 event types with context-aware notification categorization\n- **Docker support** -- Multi-stage Dockerfile with arm64 and amd64 support\n\n---\n\n## Quick Start\n\n**One-command install** (recommended):\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/juspay/shooter/release/scripts/install.sh | sh\n```\n\nThis clones to `~/.shooter/repo`, auto-generates an API key, installs dependencies, builds, offers to install cloudflared for remote access, enables autostart on login, and starts the server.\n\n**Or clone and set up manually:**\n\n```bash\ngit clone https://github.com/juspay/shooter.git\ncd shooter\npnpm install\npnpm setup        # interactive wizard: generates .env, builds, runs health check\npnpm start        # start the server on http://localhost:54007\n```\n\nOpen [http://localhost:54007](http://localhost:54007) in your browser. Visit `/config` to enter your API key for the web UI.\n\n---\n\n## All Setup Methods\n\n| Method              | Command                                                                                        | Notes                                                                                                         |\n| ------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |\n| One-command install | `curl -fsSL https://raw.githubusercontent.com/juspay/shooter/release/scripts/install.sh \\| sh` | Recommended. Clones to `~/.shooter/repo`, auto-generates API key, builds, installs cloudflared, starts server |\n| Interactive wizard  | `pnpm setup`                                                                                   | Walks through env config, builds, and verifies. Pass `--auto` for non-interactive mode.                       |\n| CLI (npx)           | `npx @juspay/shooter setup`                                                                    | No clone needed -- runs the setup wizard directly from npm                                                    |\n| Docker              | `docker compose up -d`                                                                         | See [Docker](#docker)                                                                                         |\n| Manual              | See [Manual Setup](#manual-setup)                                                              | For advanced users                                                                                            |\n\n### Manual Setup\n\n```bash\ngit clone https://github.com/juspay/shooter.git\ncd shooter\npnpm install\npnpm setup        # generates ~/.shooter/.env with API key, builds the project\npnpm start\n```\n\nOr without the wizard:\n\n```bash\ngit clone https://github.com/juspay/shooter.git\ncd shooter\npnpm install\nmkdir -p ~/.shooter \u0026\u0026 echo \"API_KEY=$(openssl rand -hex 32)\" \u003e ~/.shooter/.env\npnpm build\npnpm start\n```\n\n\u003e **Note:** Configuration lives in `~/.shooter/.env` (not the repo root). The hook notifier reads `API_KEY` from this file automatically.\n\n---\n\n## Architecture\n\n```\n+----------------------------------------------------------+\n|  Dev Machine                                             |\n|                                                          |\n|  SvelteKit Server (adapter-node, port 54007)             |\n|    +-- REST API (/api/terminals, /api/notify, ...)       |\n|    +-- WebSocket Server (ws, noServer mode)              |\n|    +-- PTY Manager (node-pty + holder processes)         |\n|    +-- Terminal Store (SQLite persistence)                |\n|    +-- Session Watcher (chokidar file watching)          |\n|    +-- APNs Client (iOS push via @parse/node-apn)        |\n|    +-- FCM Client (Android push via firebase-admin)      |\n+------------------------------+---------------------------+\n                               |\n                     Cloudflare Tunnel\n                   shooter.yourdomain.com\n                               |\n        +----------------------+----------------------+\n        |                      |                      |\n+-------+--------+   +--------+-------+   +----------+------+\n| Mobile Browser  |   | iOS App        |   | Android App     |\n| (web UI)        |   | (APNs push +   |   | (FCM push +     |\n| Terminal, Chat, |   |  permission    |   |  WebView)       |\n| Session viewer  |   |  responses)    |   |                 |\n+-----------------+   +----------------+   +-----------------+\n```\n\n**Server entry point:** `server.ts` creates an HTTP server wrapping the SvelteKit handler, attaches a WebSocket server in `noServer` mode, and handles upgrade requests with ticket-based authentication.\n\n**Terminal persistence:** PTY processes run inside separate holder processes (`pty-holder.cjs`) that survive server restarts. Terminal metadata (ID, PID, command, cwd) is persisted in SQLite so the server can reattach on restart.\n\n**Three WebSocket channels:**\n\n| Channel        | Path               | Purpose                                              |\n| -------------- | ------------------ | ---------------------------------------------------- |\n| Terminal I/O   | `/ws/terminal/:id` | Raw PTY byte stream (xterm.js)                       |\n| Session stream | `/ws/session/:id`  | Structured AI conversation updates                   |\n| Global events  | `/ws/events`       | Server broadcasts (new sessions, exits, permissions) |\n\n---\n\n## Configuration\n\nConfiguration is stored in `~/.shooter/.env`. The `pnpm setup` wizard generates this file interactively. Only `API_KEY` is required to start -- push notification config can be added later with `shooter setup --push`.\n\n| Variable               | Required | Default | Description                                                      |\n| ---------------------- | -------- | ------- | ---------------------------------------------------------------- |\n| `API_KEY`              | **Yes**  | --      | Bearer token for authenticating all API and hook requests        |\n| `PORT`                 | No       | `54007` | HTTP server port                                                 |\n| `DEVICE_PLATFORM`      | No       | `ios`   | Push notification target: `ios` or `android`                     |\n| `APNS_KEY`             | No       | --      | APNs private key (`.p8` file contents, newlines escaped as `\\n`) |\n| `APNS_KEY_ID`          | No       | --      | 10-character APNs key identifier from Apple Developer portal     |\n| `APNS_TEAM_ID`         | No       | --      | 10-character Apple Team ID                                       |\n| `APNS_BUNDLE_ID`       | No       | --      | iOS app bundle identifier (must match Xcode project)             |\n| `APNS_PRODUCTION`      | No       | `false` | Set `true` for TestFlight / App Store builds                     |\n| `DEVICE_TOKEN`         | No       | --      | Target iOS device token (64-character hex)                       |\n| `FCM_PROJECT_ID`       | No       | --      | Firebase project ID                                              |\n| `FCM_CLIENT_EMAIL`     | No       | --      | Firebase service account email                                   |\n| `FCM_PRIVATE_KEY`      | No       | --      | Firebase service account private key (PEM format)                |\n| `ANDROID_DEVICE_TOKEN` | No       | --      | Target Android FCM device token                                  |\n\n---\n\n## iOS Setup\n\n### Prerequisites\n\n- macOS with Xcode installed\n- Apple Developer account with Push Notifications capability\n- Physical iOS device (push notifications do not work in the simulator)\n\n### APNs Key Setup\n\n1. Go to [Apple Developer \u003e Keys](https://developer.apple.com/account/resources/authkeys/list) and create a new key with **Apple Push Notifications service (APNs)** enabled\n2. Download the `.p8` file\n3. Note the **Key ID** (10 characters) shown after creation\n4. Find your **Team ID** in [Membership Details](https://developer.apple.com/account/#/membership)\n\nAdd these to your `.env`:\n\n```\nAPNS_KEY=\"-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\"\nAPNS_KEY_ID=ABC123DEFG\nAPNS_TEAM_ID=XYZ789KLMN\nAPNS_BUNDLE_ID=com.yourcompany.shooter\nDEVICE_TOKEN=\u003c64-char-hex-from-device\u003e\n```\n\n### Building the iOS App\n\n```bash\ncd ios/Shooter\nopen Shooter.xcodeproj\n```\n\n1. Select your signing team in **Signing \u0026 Capabilities**\n2. Ensure the **Push Notifications** capability is enabled\n3. Build and run on a physical device\n4. The device token is printed to the Xcode console on first launch\n\nFor TestFlight or App Store builds, set `APNS_PRODUCTION=true` in your server `.env` to route through the production APNs gateway.\n\n---\n\n## Android Setup\n\n### Prerequisites\n\n- Android Studio\n- Gradle 8.12+ (for generating the wrapper)\n- Firebase project with Cloud Messaging enabled\n\n### Firebase Setup\n\n1. Create a project in the [Firebase Console](https://console.firebase.google.com/)\n2. Add an Android app with application ID `com.shooter.android`\n3. Download `google-services.json` and place it in `android/app/`\n4. Go to **Project Settings \u003e Service Accounts** and generate a new private key\n5. Copy `project_id`, `client_email`, and `private_key` from the downloaded JSON into your `.env`:\n\n```\nFCM_PROJECT_ID=your-firebase-project-id\nFCM_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com\nFCM_PRIVATE_KEY=\"-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----\"\nANDROID_DEVICE_TOKEN=\u003cfcm-device-token\u003e\nDEVICE_PLATFORM=android\n```\n\n### Building the Android App\n\n```bash\ncd android\nchmod +x setup.sh\n./setup.sh              # generates Gradle wrapper\n./gradlew assembleDebug\n```\n\nThe app targets SDK 35 (min SDK 26) and uses a WebView that connects to your Shooter server URL.\n\n---\n\n## Claude Code Hooks\n\nShooter integrates with Claude Code through lifecycle hooks defined in `.claude/settings.json`. A unified notifier script (`.claude/hooks/notifier.cjs`) handles all hook events.\n\n### Captured Events\n\n| Hook                 | Description                                                     |\n| -------------------- | --------------------------------------------------------------- |\n| `PreToolUse`         | Before a tool executes (file edit, bash command, etc.)          |\n| `PostToolUse`        | After a tool completes successfully                             |\n| `PostToolUseFailure` | After a tool fails                                              |\n| `PermissionRequest`  | Claude Code asks for permission -- **blocks until you respond** |\n| `SessionStart`       | A new coding session begins                                     |\n| `SessionEnd`         | A coding session ends                                           |\n| `Stop`               | Claude Code stops execution                                     |\n| `Notification`       | General notification from Claude Code                           |\n| `SubagentStart`      | A subagent is spawned                                           |\n| `SubagentStop`       | A subagent completes                                            |\n| `UserPromptSubmit`   | User submits a prompt                                           |\n| `TeammateIdle`       | A teammate agent becomes idle                                   |\n| `TaskCompleted`      | A task finishes                                                 |\n| `PreCompact`         | Before context compaction                                       |\n\n### Permission Flow\n\n1. Claude Code triggers `PermissionRequest` hook\n2. Notifier sends a push notification with the tool name and details to your phone\n3. You tap **Allow** or **Deny** on the interactive notification (iOS) or in the app\n4. Notifier polls `GET /api/response?requestId=...` until your decision arrives\n5. The hook returns the decision to Claude Code, which proceeds or aborts\n\nThe `PermissionRequest` hook has a 180-second timeout in `.claude/settings.json`. The notifier's internal poll timeout is 120 seconds, providing a 60-second safety buffer.\n\n### Hook Environment Variables\n\n| Variable                     | Default | Description                                                 |\n| ---------------------------- | ------- | ----------------------------------------------------------- |\n| `SHOOTER_USE_LOCAL`          | --      | Set `true` to connect to local server instead of remote URL |\n| `SHOOTER_LOCAL_PORT`         | `54007` | Local server port when using `SHOOTER_USE_LOCAL`            |\n| `SHOOTER_API_URL`            | --      | Remote server URL (when not using local)                    |\n| `SHOOTER_PERMISSION_TIMEOUT` | `120`   | Seconds to wait for a permission response                   |\n| `API_KEY`                    | --      | Bearer token (must match the server's `API_KEY`)            |\n\n---\n\n## Docker\n\n### Quick Start\n\n```bash\n# Minimal — just set API_KEY:\necho \"API_KEY=$(openssl rand -hex 32)\" \u003e .env\ndocker compose up -d\n```\n\nOr with a Cloudflare Tunnel for remote access:\n\n```bash\ndocker compose --profile tunnel up -d\n```\n\n### Manual Build and Run\n\n```bash\ndocker build -t shooter .\n\ndocker run -d \\\n  --name shooter \\\n  -e API_KEY=your-secret-key-here \\\n  -p 54007:54007 \\\n  -v shooter-data:/root/.shooter \\\n  --restart unless-stopped \\\n  shooter\n```\n\n\u003e **Required:** Set `API_KEY` either via `-e API_KEY=...`, `--env-file .env`, or in `docker-compose.yml`. Without it, all authenticated endpoints return 401.\n\nThe multi-stage Dockerfile uses `node:20-slim` with native addon binaries copied from the build stage (no build tools in the production image). SQLite data is persisted in the `shooter-data` volume. The `.env` file is injected at runtime and never baked into the image.\n\nA separate `Dockerfile.test` is provided for verifying the fresh-user install experience in an isolated container.\n\n### docker-compose.yml\n\n```yaml\nservices:\n  shooter:\n    build: .\n    ports:\n      - '54007:54007'\n    env_file:\n      - path: .env\n        required: false\n    # Set API_KEY in .env or uncomment below:\n    # environment:\n    #   - API_KEY=your-secret-key-here\n    volumes:\n      - shooter-data:/root/.shooter\n    restart: unless-stopped\n\n  # Optional: Cloudflare Tunnel for remote access\n  # Start with: docker compose --profile tunnel up -d\n  tunnel:\n    image: cloudflare/cloudflared:latest\n    command: tunnel --no-autoupdate --url http://shooter:54007\n    depends_on:\n      - shooter\n    restart: unless-stopped\n    profiles:\n      - tunnel\n\nvolumes:\n  shooter-data:\n```\n\n---\n\n## API Reference\n\nAll endpoints require the `Authorization: Bearer \u003cAPI_KEY\u003e` header.\n\n| Method   | Path                        | Description                                          |\n| -------- | --------------------------- | ---------------------------------------------------- |\n| `GET`    | `/api/health`               | Health check with server status                      |\n| `GET`    | `/api/terminals`            | List all active and recently exited terminals        |\n| `POST`   | `/api/terminals`            | Create a new terminal session                        |\n| `GET`    | `/api/terminals/:id`        | Get details for a specific terminal                  |\n| `DELETE` | `/api/terminals/:id`        | Kill and remove a terminal session                   |\n| `POST`   | `/api/terminals/:id/resize` | Resize a terminal (cols, rows)                       |\n| `POST`   | `/api/ws-ticket`            | Generate a short-lived WebSocket auth ticket         |\n| `GET`    | `/api/ws-status`            | Get connected WebSocket client count                 |\n| `POST`   | `/api/notify`               | Send a push notification via APNs or FCM             |\n| `GET`    | `/api/notify`               | Check notification status and history                |\n| `POST`   | `/api/response`             | Submit a permission allow/deny decision              |\n| `GET`    | `/api/response`             | Poll for a pending permission decision               |\n| `GET`    | `/api/sessions`             | List sessions across all projects                    |\n| `POST`   | `/api/webhook`              | Stub — returns 501 (not yet implemented)             |\n| `GET`    | `/api/qr-config`            | Generate QR code for mobile app pairing              |\n| `POST`   | `/api/device-token`         | Register a device token (iOS or Android)             |\n| `GET`    | `/api/debug`                | Debug information (APNs config, device token status) |\n\n### WebSocket Authentication\n\nWebSocket connections use ticket-based auth. First call `POST /api/ws-ticket` with your Bearer token to receive a single-use ticket (valid 30 seconds), then connect with `?ticket=TICKET` in the query string.\n\n### Example: Create Terminal\n\n```bash\ncurl -X POST http://localhost:54007/api/terminals \\\n  -H \"Authorization: Bearer $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"command\": \"claude\", \"cwd\": \"/Users/me/project\", \"cols\": 80, \"rows\": 24}'\n```\n\nResponse:\n\n```json\n{\n  \"id\": \"term_a1b2c3\",\n  \"pid\": 45231,\n  \"command\": \"claude\",\n  \"cwd\": \"/Users/me/project\",\n  \"ws\": \"/ws/terminal/term_a1b2c3\",\n  \"sessionWs\": \"/ws/session/term_a1b2c3\",\n  \"createdAt\": \"2026-03-17T10:00:00Z\"\n}\n```\n\n---\n\n## Development\n\n```bash\npnpm dev           # Vite dev server with hot reload (no WebSocket server)\npnpm build         # Production build (outputs to build/)\npnpm start         # Production server with WebSocket support (tsx server.ts)\npnpm preview       # Preview production build via Vite\npnpm check         # TypeScript type checking\npnpm run gen:types # Generate types from YAML specs (specs/types/)\npnpm lint          # ESLint\npnpm lint:fix      # ESLint with auto-fix\npnpm format        # Prettier formatting\npnpm format:check  # Check formatting without writing\n```\n\n**Note:** `pnpm dev` runs the Vite dev server, which does not include the WebSocket server or PTY manager. For full functionality (terminal sessions, live streaming), use `pnpm build \u0026\u0026 pnpm start`.\n\n### CLI Commands\n\nThe `shooter` command (via `bin/shooter.cjs` or the global `shooter` symlink) supports:\n\n| Command                 | Description                                                                                 |\n| ----------------------- | ------------------------------------------------------------------------------------------- |\n| `shooter start`         | Start the server (default if no command given)                                              |\n| `shooter stop`          | Stop the running server gracefully (SIGTERM, then SIGKILL after 5s)                         |\n| `shooter status`        | Show PID, URL, autostart state, log path                                                    |\n| `shooter autostart on`  | Enable autostart on login (LaunchAgent on macOS, systemd on Linux)                          |\n| `shooter autostart off` | Disable autostart and remove the service definition                                         |\n| `shooter logs`          | Tail server logs (log file on macOS, journalctl on Linux)                                   |\n| `shooter setup`         | Quick setup (~60s): API key + build. `--auto` for non-interactive, `--push` for push config |\n| `shooter version`       | Print version number                                                                        |\n| `shooter help`          | Show all available commands                                                                 |\n\nProcess state is tracked via a PID file at `~/.shooter/shooter.pid`. Logs are written to `~/.shooter/logs/shooter.log` when running via autostart.\n\n### Type System\n\nTypes are auto-generated from YAML specifications in `specs/types/` using [type-crafter](https://github.com/nicktaf/type-crafter). Never edit files in `src/lib/types/generated/` directly -- edit the YAML specs and run `pnpm run gen:types`.\n\n---\n\n## Project Structure\n\n```\nshooter/\n  server.ts                        # HTTP + WebSocket server entry point (build guard check)\n  package.json                     # Dependencies and scripts (pnpm only)\n  Dockerfile                       # Multi-stage Docker build\n  Dockerfile.test                  # Test image for fresh-user install verification\n  docker-compose.yml               # Docker Compose config\n  .env.example                     # Environment variable template\n  svelte.config.js                 # SvelteKit config (adapter-node)\n  vite.config.ts                   # Vite config (node-pty external)\n  bin/\n    shooter.cjs                    # CLI entry point (start|stop|status|autostart|logs|setup|help)\n  scripts/\n    setup.cjs                      # Interactive setup wizard (--auto for non-interactive)\n    install.sh                     # One-command installer (full auto setup + cloudflared)\n  .claude/\n    hooks/notifier.cjs             # Unified hook notifier (Node.js)\n    settings.json                  # Hook configuration (13 event types)\n  src/\n    lib/\n      types/\n        generated/                 # Auto-generated TypeScript types (DO NOT EDIT)\n      modules/\n        server/\n          apn/                     # APNs push notification service\n          auth.ts                  # Shared authentication helper\n          cli/                     # CLI command utilities\n          terminal/\n            pty-manager.ts         # PTY lifecycle, scrollback, cleanup\n            pty-holder.cjs         # Standalone holder process for persistence\n            terminal-store.ts      # SQLite persistence for terminal metadata\n            session-watcher.ts     # JSONL file watcher (chokidar)\n            opencode-watcher.ts    # OpenCode session watcher\n          ws/\n            server.ts              # WebSocket upgrade routing\n            terminal-handler.ts    # Terminal I/O channel\n            session-handler.ts     # Session stream channel\n            events-handler.ts      # Global event bus channel\n            ticket-store.ts        # One-time auth ticket store\n            keepalive.ts           # Ping/pong heartbeat\n          sessions/\n            jsonl-reader.ts        # Parse JSONL session files\n            opencode-reader.ts     # Parse OpenCode sessions\n        client/\n          common/                  # Reusable UI components\n          activity/                # Activity feed components\n          dashboard/               # Dashboard components\n          neurolink/               # Neurolink integration components\n          terminal/\n            ChatView.svelte        # Structured AI conversation view\n            LaunchSheet.svelte     # Terminal launch dialog\n            QuickKeys.svelte       # Mobile quick key bar\n            ConnectionStatus.svelte # Connection state indicator\n            xterm-wrapper.ts       # Async xterm.js initialization\n    routes/\n      api/                         # REST API endpoints (17 endpoints)\n      terminals/                   # Terminal list and detail pages\n      project/                     # Project dashboard\n      session/[id]/                # Session viewer\n      config/                      # Settings page with QR pairing\n  specs/types/                     # Type-crafter YAML specifications\n  ios/Shooter/                     # Swift iOS app (Xcode project)\n  android/                         # Kotlin Android app (Gradle project)\n  docs/                            # Documentation\n  plans/                           # Architecture plans and roadmap\n```\n\n---\n\n## Updating\n\nIf you installed via the one-command installer:\n\n```bash\ncd ~/.shooter/repo\ngit pull origin release\npnpm install\npnpm build\nshooter stop \u0026\u0026 shooter start -d\n```\n\nOr re-run the installer -- it detects the existing installation and offers to update:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/juspay/shooter/release/scripts/install.sh | sh\n```\n\nIf you installed via npm:\n\n```bash\nnpm update -g @juspay/shooter\n```\n\n---\n\n## Uninstall\n\n```bash\n# 1. Stop the server and disable autostart\nshooter stop\nshooter autostart off\n\n# 2. Remove the data directory (config, logs, SQLite database)\nrm -rf ~/.shooter\n\n# 3. Remove the global command symlink\nrm -f ~/.local/bin/shooter\n\n# 4. Remove the repo (if installed via one-command installer)\nrm -rf ~/.shooter/repo\n```\n\nIf you installed via npm: `npm uninstall -g @juspay/shooter`\n\nTo also remove Claude Code hooks, delete the `hooks` section from `.claude/settings.json` in each project that uses Shooter.\n\n---\n\n## Reset\n\nTo reset Shooter to a clean state without reinstalling:\n\n```bash\nshooter stop\nrm ~/.shooter/.env            # Remove config (re-run shooter setup to regenerate)\nrm ~/.shooter/shooter.db      # Remove terminal history database\nrm -rf ~/.shooter/logs        # Remove log files\nshooter setup                 # Regenerate config\nshooter start -d              # Restart\n```\n\n---\n\n## Troubleshooting\n\n### Server does not start\n\n- Verify Node.js 20+ is installed: `node --version`\n- Ensure pnpm is used (npm and yarn are blocked): `pnpm --version`\n- Check that `pnpm build` completed without errors before running `pnpm start` -- `server.ts` has a build guard that exits with a clear error if `build/handler.js` is missing\n- Confirm `.env` exists and `API_KEY` is set (the server also checks `~/.shooter/.env` as a fallback)\n- On Linux, ensure build tools are installed: `python3`, `make`, `g++` (needed for native modules)\n\n### WebSocket connections fail\n\n- `pnpm dev` does **not** run the WebSocket server. Use `pnpm build \u0026\u0026 pnpm start` for full functionality.\n- Ensure you are obtaining a ticket via `POST /api/ws-ticket` before connecting\n- Tickets expire after 30 seconds and are single-use\n\n### Push notifications not arriving\n\n- **iOS:** Verify `APNS_KEY`, `APNS_KEY_ID`, `APNS_TEAM_ID`, `APNS_BUNDLE_ID`, and `DEVICE_TOKEN` are all set in `.env`\n- **iOS (TestFlight/App Store):** Set `APNS_PRODUCTION=true` -- sandbox tokens do not work with the production gateway and vice versa\n- **Android:** Ensure `google-services.json` is in `android/app/` and FCM credentials are in `.env`\n- Check `GET /api/debug` for APNs configuration status and device token validity\n- Check server logs for APNs or FCM error responses\n\n### Hooks not sending notifications\n\n- The notifier reads `API_KEY` from `~/.shooter/.env` automatically. If that file is missing or empty, run `shooter setup`.\n- Verify the hooks are configured in `.claude/settings.json`\n- Test connectivity: `curl -H \"Authorization: Bearer $(grep API_KEY ~/.shooter/.env | cut -d= -f2 | tr -d '\\\"')\" http://localhost:54007/api/health`\n\n### Terminal sessions lost after restart\n\n- Terminal metadata is persisted in SQLite and PTY holder processes survive restarts, so running terminals are reattached automatically\n- In-memory state (WebSocket connections, auth tickets, pending permission requests) is lost on restart\n\n### node-pty build errors\n\n- Ensure Python 3, make, and a C++ compiler are installed\n- On macOS, install Xcode Command Line Tools: `xcode-select --install`\n- Try rebuilding: `pnpm rebuild node-pty`\n\n### Port already in use\n\n- `shooter start` detects port conflicts automatically and prints a clear error\n- Default port is 54007. Set `PORT=\u003cnumber\u003e` in `~/.shooter/.env` to use a different port\n- To find what's using the port: `lsof -i :54007` (macOS) or `ss -tlnp | grep 54007` (Linux)\n\n---\n\n## Security\n\n- **Command allowlist** -- Only `zsh`, `bash`, `sh`, `fish`, `claude`, and `opencode` can be launched as terminal commands\n- **Ticket-based WebSocket auth** -- Short-lived, single-use tickets (30-second expiry) keep API keys out of WebSocket URLs\n- **Bearer token on all REST endpoints** -- Every request requires `Authorization: Bearer \u003cAPI_KEY\u003e`\n- **Working directory validation** -- The `cwd` parameter is validated against the user's home directory; symlink traversal is blocked\n- **No credentials in code** -- All secrets loaded from `.env` at runtime; `.env` is gitignored\n- **APNs JWT rotation** -- Push notification tokens are generated with short expiry and rotated automatically\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuspay%2Fshooter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuspay%2Fshooter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuspay%2Fshooter/lists"}