shooter
Health Uyari
- No license — Repository has no license file
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Uyari
- process.env — Environment variable access in .claude/hooks/notifier.cjs
- fs module — File system access in .claude/hooks/notifier.cjs
- fs module — File system access in .github/workflows/yama-review.yml
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Turn your phone into a remote control for AI coding sessions running on your dev machine.
Shooter
Mobile push notifications and remote terminal access for AI coding sessions.
What is Shooter?
Shooter 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.
Features
- Push notifications -- Real-time alerts for tool usage, permission requests, session starts/stops, errors, and task completions (iOS via APNs, Android via FCM)
- Bidirectional permissions -- Approve or deny Claude Code permission prompts from your phone; the hook blocks until you respond
- Remote terminal -- Launch shell, Claude Code, or OpenCode sessions from your phone with full xterm.js rendering
- Terminal persistence -- PTY processes run in holder processes that survive server restarts; metadata persisted in SQLite
- Structured Chat view -- AI conversations rendered as message bubbles with tool-use cards and thinking indicators, parsed live from JSONL session files
- Session browser -- Browse coding session history across all projects
- QR code pairing -- Scan a QR code from the
/configpage to connect mobile apps to the server - WebSocket streaming -- Three multiplexed channels: terminal I/O, session updates, and global events
- Quick keys -- Mobile-optimized touch bar for Ctrl+C, Tab, arrow keys, Esc, and other special characters
- Claude Code hooks -- Lifecycle hooks for 13 event types with context-aware notification categorization
- Docker support -- Multi-stage Dockerfile with arm64 and amd64 support
Quick Start
One-command install (recommended):
curl -fsSL https://raw.githubusercontent.com/juspay/shooter/release/scripts/install.sh | sh
This 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.
Or clone and set up manually:
git clone https://github.com/juspay/shooter.git
cd shooter
pnpm install
pnpm setup # interactive wizard: generates .env, builds, runs health check
pnpm start # start the server on http://localhost:54007
Open http://localhost:54007 in your browser. Visit /config to enter your API key for the web UI.
All Setup Methods
| Method | Command | Notes |
|---|---|---|
| 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 |
| Interactive wizard | pnpm setup |
Walks through env config, builds, and verifies. Pass --auto for non-interactive mode. |
| CLI (npx) | npx @juspay/shooter setup |
No clone needed -- runs the setup wizard directly from npm |
| Docker | docker compose up -d |
See Docker |
| Manual | See Manual Setup | For advanced users |
Manual Setup
git clone https://github.com/juspay/shooter.git
cd shooter
pnpm install
pnpm setup # generates ~/.shooter/.env with API key, builds the project
pnpm start
Or without the wizard:
git clone https://github.com/juspay/shooter.git
cd shooter
pnpm install
mkdir -p ~/.shooter && echo "API_KEY=$(openssl rand -hex 32)" > ~/.shooter/.env
pnpm build
pnpm start
Note: Configuration lives in
~/.shooter/.env(not the repo root). The hook notifier readsAPI_KEYfrom this file automatically.
Architecture
+----------------------------------------------------------+
| Dev Machine |
| |
| SvelteKit Server (adapter-node, port 54007) |
| +-- REST API (/api/terminals, /api/notify, ...) |
| +-- WebSocket Server (ws, noServer mode) |
| +-- PTY Manager (node-pty + holder processes) |
| +-- Terminal Store (SQLite persistence) |
| +-- Session Watcher (chokidar file watching) |
| +-- APNs Client (iOS push via @parse/node-apn) |
| +-- FCM Client (Android push via firebase-admin) |
+------------------------------+---------------------------+
|
Cloudflare Tunnel
shooter.yourdomain.com
|
+----------------------+----------------------+
| | |
+-------+--------+ +--------+-------+ +----------+------+
| Mobile Browser | | iOS App | | Android App |
| (web UI) | | (APNs push + | | (FCM push + |
| Terminal, Chat, | | permission | | WebView) |
| Session viewer | | responses) | | |
+-----------------+ +----------------+ +-----------------+
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.
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.
Three WebSocket channels:
| Channel | Path | Purpose |
|---|---|---|
| Terminal I/O | /ws/terminal/:id |
Raw PTY byte stream (xterm.js) |
| Session stream | /ws/session/:id |
Structured AI conversation updates |
| Global events | /ws/events |
Server broadcasts (new sessions, exits, permissions) |
Configuration
Configuration 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.
| Variable | Required | Default | Description |
|---|---|---|---|
API_KEY |
Yes | -- | Bearer token for authenticating all API and hook requests |
PORT |
No | 54007 |
HTTP server port |
DEVICE_PLATFORM |
No | ios |
Push notification target: ios or android |
APNS_KEY |
No | -- | APNs private key (.p8 file contents, newlines escaped as \n) |
APNS_KEY_ID |
No | -- | 10-character APNs key identifier from Apple Developer portal |
APNS_TEAM_ID |
No | -- | 10-character Apple Team ID |
APNS_BUNDLE_ID |
No | -- | iOS app bundle identifier (must match Xcode project) |
APNS_PRODUCTION |
No | false |
Set true for TestFlight / App Store builds |
DEVICE_TOKEN |
No | -- | Target iOS device token (64-character hex) |
FCM_PROJECT_ID |
No | -- | Firebase project ID |
FCM_CLIENT_EMAIL |
No | -- | Firebase service account email |
FCM_PRIVATE_KEY |
No | -- | Firebase service account private key (PEM format) |
ANDROID_DEVICE_TOKEN |
No | -- | Target Android FCM device token |
iOS Setup
Prerequisites
- macOS with Xcode installed
- Apple Developer account with Push Notifications capability
- Physical iOS device (push notifications do not work in the simulator)
APNs Key Setup
- Go to Apple Developer > Keys and create a new key with Apple Push Notifications service (APNs) enabled
- Download the
.p8file - Note the Key ID (10 characters) shown after creation
- Find your Team ID in Membership Details
Add these to your .env:
APNS_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
APNS_KEY_ID=ABC123DEFG
APNS_TEAM_ID=XYZ789KLMN
APNS_BUNDLE_ID=com.yourcompany.shooter
DEVICE_TOKEN=<64-char-hex-from-device>
Building the iOS App
cd ios/Shooter
open Shooter.xcodeproj
- Select your signing team in Signing & Capabilities
- Ensure the Push Notifications capability is enabled
- Build and run on a physical device
- The device token is printed to the Xcode console on first launch
For TestFlight or App Store builds, set APNS_PRODUCTION=true in your server .env to route through the production APNs gateway.
Android Setup
Prerequisites
- Android Studio
- Gradle 8.12+ (for generating the wrapper)
- Firebase project with Cloud Messaging enabled
Firebase Setup
- Create a project in the Firebase Console
- Add an Android app with application ID
com.shooter.android - Download
google-services.jsonand place it inandroid/app/ - Go to Project Settings > Service Accounts and generate a new private key
- Copy
project_id,client_email, andprivate_keyfrom the downloaded JSON into your.env:
FCM_PROJECT_ID=your-firebase-project-id
FCM_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com
FCM_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
ANDROID_DEVICE_TOKEN=<fcm-device-token>
DEVICE_PLATFORM=android
Building the Android App
cd android
chmod +x setup.sh
./setup.sh # generates Gradle wrapper
./gradlew assembleDebug
The app targets SDK 35 (min SDK 26) and uses a WebView that connects to your Shooter server URL.
Claude Code Hooks
Shooter integrates with Claude Code through lifecycle hooks defined in .claude/settings.json. A unified notifier script (.claude/hooks/notifier.cjs) handles all hook events.
Captured Events
| Hook | Description |
|---|---|
PreToolUse |
Before a tool executes (file edit, bash command, etc.) |
PostToolUse |
After a tool completes successfully |
PostToolUseFailure |
After a tool fails |
PermissionRequest |
Claude Code asks for permission -- blocks until you respond |
SessionStart |
A new coding session begins |
SessionEnd |
A coding session ends |
Stop |
Claude Code stops execution |
Notification |
General notification from Claude Code |
SubagentStart |
A subagent is spawned |
SubagentStop |
A subagent completes |
UserPromptSubmit |
User submits a prompt |
TeammateIdle |
A teammate agent becomes idle |
TaskCompleted |
A task finishes |
PreCompact |
Before context compaction |
Permission Flow
- Claude Code triggers
PermissionRequesthook - Notifier sends a push notification with the tool name and details to your phone
- You tap Allow or Deny on the interactive notification (iOS) or in the app
- Notifier polls
GET /api/response?requestId=...until your decision arrives - The hook returns the decision to Claude Code, which proceeds or aborts
The 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.
Hook Environment Variables
| Variable | Default | Description |
|---|---|---|
SHOOTER_USE_LOCAL |
-- | Set true to connect to local server instead of remote URL |
SHOOTER_LOCAL_PORT |
54007 |
Local server port when using SHOOTER_USE_LOCAL |
SHOOTER_API_URL |
-- | Remote server URL (when not using local) |
SHOOTER_PERMISSION_TIMEOUT |
120 |
Seconds to wait for a permission response |
API_KEY |
-- | Bearer token (must match the server's API_KEY) |
Docker
Quick Start
# Minimal — just set API_KEY:
echo "API_KEY=$(openssl rand -hex 32)" > .env
docker compose up -d
Or with a Cloudflare Tunnel for remote access:
docker compose --profile tunnel up -d
Manual Build and Run
docker build -t shooter .
docker run -d \
--name shooter \
-e API_KEY=your-secret-key-here \
-p 54007:54007 \
-v shooter-data:/root/.shooter \
--restart unless-stopped \
shooter
Required: Set
API_KEYeither via-e API_KEY=...,--env-file .env, or indocker-compose.yml. Without it, all authenticated endpoints return 401.
The 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.
A separate Dockerfile.test is provided for verifying the fresh-user install experience in an isolated container.
docker-compose.yml
services:
shooter:
build: .
ports:
- '54007:54007'
env_file:
- path: .env
required: false
# Set API_KEY in .env or uncomment below:
# environment:
# - API_KEY=your-secret-key-here
volumes:
- shooter-data:/root/.shooter
restart: unless-stopped
# Optional: Cloudflare Tunnel for remote access
# Start with: docker compose --profile tunnel up -d
tunnel:
image: cloudflare/cloudflared:latest
command: tunnel --no-autoupdate --url http://shooter:54007
depends_on:
- shooter
restart: unless-stopped
profiles:
- tunnel
volumes:
shooter-data:
API Reference
All endpoints require the Authorization: Bearer <API_KEY> header.
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Health check with server status |
GET |
/api/terminals |
List all active and recently exited terminals |
POST |
/api/terminals |
Create a new terminal session |
GET |
/api/terminals/:id |
Get details for a specific terminal |
DELETE |
/api/terminals/:id |
Kill and remove a terminal session |
POST |
/api/terminals/:id/resize |
Resize a terminal (cols, rows) |
POST |
/api/ws-ticket |
Generate a short-lived WebSocket auth ticket |
GET |
/api/ws-status |
Get connected WebSocket client count |
POST |
/api/notify |
Send a push notification via APNs or FCM |
GET |
/api/notify |
Check notification status and history |
POST |
/api/response |
Submit a permission allow/deny decision |
GET |
/api/response |
Poll for a pending permission decision |
GET |
/api/sessions |
List sessions across all projects |
POST |
/api/webhook |
Stub — returns 501 (not yet implemented) |
GET |
/api/qr-config |
Generate QR code for mobile app pairing |
POST |
/api/device-token |
Register a device token (iOS or Android) |
GET |
/api/debug |
Debug information (APNs config, device token status) |
WebSocket Authentication
WebSocket 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.
Example: Create Terminal
curl -X POST http://localhost:54007/api/terminals \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"command": "claude", "cwd": "/Users/me/project", "cols": 80, "rows": 24}'
Response:
{
"id": "term_a1b2c3",
"pid": 45231,
"command": "claude",
"cwd": "/Users/me/project",
"ws": "/ws/terminal/term_a1b2c3",
"sessionWs": "/ws/session/term_a1b2c3",
"createdAt": "2026-03-17T10:00:00Z"
}
Development
pnpm dev # Vite dev server with hot reload (no WebSocket server)
pnpm build # Production build (outputs to build/)
pnpm start # Production server with WebSocket support (tsx server.ts)
pnpm preview # Preview production build via Vite
pnpm check # TypeScript type checking
pnpm run gen:types # Generate types from YAML specs (specs/types/)
pnpm lint # ESLint
pnpm lint:fix # ESLint with auto-fix
pnpm format # Prettier formatting
pnpm format:check # Check formatting without writing
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 && pnpm start.
CLI Commands
The shooter command (via bin/shooter.cjs or the global shooter symlink) supports:
| Command | Description |
|---|---|
shooter start |
Start the server (default if no command given) |
shooter stop |
Stop the running server gracefully (SIGTERM, then SIGKILL after 5s) |
shooter status |
Show PID, URL, autostart state, log path |
shooter autostart on |
Enable autostart on login (LaunchAgent on macOS, systemd on Linux) |
shooter autostart off |
Disable autostart and remove the service definition |
shooter logs |
Tail server logs (log file on macOS, journalctl on Linux) |
shooter setup |
Quick setup (~60s): API key + build. --auto for non-interactive, --push for push config |
shooter version |
Print version number |
shooter help |
Show all available commands |
Process state is tracked via a PID file at ~/.shooter/shooter.pid. Logs are written to ~/.shooter/logs/shooter.log when running via autostart.
Type System
Types are auto-generated from YAML specifications in specs/types/ using type-crafter. Never edit files in src/lib/types/generated/ directly -- edit the YAML specs and run pnpm run gen:types.
Project Structure
shooter/
server.ts # HTTP + WebSocket server entry point (build guard check)
package.json # Dependencies and scripts (pnpm only)
Dockerfile # Multi-stage Docker build
Dockerfile.test # Test image for fresh-user install verification
docker-compose.yml # Docker Compose config
.env.example # Environment variable template
svelte.config.js # SvelteKit config (adapter-node)
vite.config.ts # Vite config (node-pty external)
bin/
shooter.cjs # CLI entry point (start|stop|status|autostart|logs|setup|help)
scripts/
setup.cjs # Interactive setup wizard (--auto for non-interactive)
install.sh # One-command installer (full auto setup + cloudflared)
.claude/
hooks/notifier.cjs # Unified hook notifier (Node.js)
settings.json # Hook configuration (13 event types)
src/
lib/
types/
generated/ # Auto-generated TypeScript types (DO NOT EDIT)
modules/
server/
apn/ # APNs push notification service
auth.ts # Shared authentication helper
cli/ # CLI command utilities
terminal/
pty-manager.ts # PTY lifecycle, scrollback, cleanup
pty-holder.cjs # Standalone holder process for persistence
terminal-store.ts # SQLite persistence for terminal metadata
session-watcher.ts # JSONL file watcher (chokidar)
opencode-watcher.ts # OpenCode session watcher
ws/
server.ts # WebSocket upgrade routing
terminal-handler.ts # Terminal I/O channel
session-handler.ts # Session stream channel
events-handler.ts # Global event bus channel
ticket-store.ts # One-time auth ticket store
keepalive.ts # Ping/pong heartbeat
sessions/
jsonl-reader.ts # Parse JSONL session files
opencode-reader.ts # Parse OpenCode sessions
client/
common/ # Reusable UI components
activity/ # Activity feed components
dashboard/ # Dashboard components
neurolink/ # Neurolink integration components
terminal/
ChatView.svelte # Structured AI conversation view
LaunchSheet.svelte # Terminal launch dialog
QuickKeys.svelte # Mobile quick key bar
ConnectionStatus.svelte # Connection state indicator
xterm-wrapper.ts # Async xterm.js initialization
routes/
api/ # REST API endpoints (17 endpoints)
terminals/ # Terminal list and detail pages
project/ # Project dashboard
session/[id]/ # Session viewer
config/ # Settings page with QR pairing
specs/types/ # Type-crafter YAML specifications
ios/Shooter/ # Swift iOS app (Xcode project)
android/ # Kotlin Android app (Gradle project)
docs/ # Documentation
plans/ # Architecture plans and roadmap
Updating
If you installed via the one-command installer:
cd ~/.shooter/repo
git pull origin release
pnpm install
pnpm build
shooter stop && shooter start -d
Or re-run the installer -- it detects the existing installation and offers to update:
curl -fsSL https://raw.githubusercontent.com/juspay/shooter/release/scripts/install.sh | sh
If you installed via npm:
npm update -g @juspay/shooter
Uninstall
# 1. Stop the server and disable autostart
shooter stop
shooter autostart off
# 2. Remove the data directory (config, logs, SQLite database)
rm -rf ~/.shooter
# 3. Remove the global command symlink
rm -f ~/.local/bin/shooter
# 4. Remove the repo (if installed via one-command installer)
rm -rf ~/.shooter/repo
If you installed via npm: npm uninstall -g @juspay/shooter
To also remove Claude Code hooks, delete the hooks section from .claude/settings.json in each project that uses Shooter.
Reset
To reset Shooter to a clean state without reinstalling:
shooter stop
rm ~/.shooter/.env # Remove config (re-run shooter setup to regenerate)
rm ~/.shooter/shooter.db # Remove terminal history database
rm -rf ~/.shooter/logs # Remove log files
shooter setup # Regenerate config
shooter start -d # Restart
Troubleshooting
Server does not start
- Verify Node.js 20+ is installed:
node --version - Ensure pnpm is used (npm and yarn are blocked):
pnpm --version - Check that
pnpm buildcompleted without errors before runningpnpm start--server.tshas a build guard that exits with a clear error ifbuild/handler.jsis missing - Confirm
.envexists andAPI_KEYis set (the server also checks~/.shooter/.envas a fallback) - On Linux, ensure build tools are installed:
python3,make,g++(needed for native modules)
WebSocket connections fail
pnpm devdoes not run the WebSocket server. Usepnpm build && pnpm startfor full functionality.- Ensure you are obtaining a ticket via
POST /api/ws-ticketbefore connecting - Tickets expire after 30 seconds and are single-use
Push notifications not arriving
- iOS: Verify
APNS_KEY,APNS_KEY_ID,APNS_TEAM_ID,APNS_BUNDLE_ID, andDEVICE_TOKENare all set in.env - iOS (TestFlight/App Store): Set
APNS_PRODUCTION=true-- sandbox tokens do not work with the production gateway and vice versa - Android: Ensure
google-services.jsonis inandroid/app/and FCM credentials are in.env - Check
GET /api/debugfor APNs configuration status and device token validity - Check server logs for APNs or FCM error responses
Hooks not sending notifications
- The notifier reads
API_KEYfrom~/.shooter/.envautomatically. If that file is missing or empty, runshooter setup. - Verify the hooks are configured in
.claude/settings.json - Test connectivity:
curl -H "Authorization: Bearer $(grep API_KEY ~/.shooter/.env | cut -d= -f2 | tr -d '\"')" http://localhost:54007/api/health
Terminal sessions lost after restart
- Terminal metadata is persisted in SQLite and PTY holder processes survive restarts, so running terminals are reattached automatically
- In-memory state (WebSocket connections, auth tickets, pending permission requests) is lost on restart
node-pty build errors
- Ensure Python 3, make, and a C++ compiler are installed
- On macOS, install Xcode Command Line Tools:
xcode-select --install - Try rebuilding:
pnpm rebuild node-pty
Port already in use
shooter startdetects port conflicts automatically and prints a clear error- Default port is 54007. Set
PORT=<number>in~/.shooter/.envto use a different port - To find what's using the port:
lsof -i :54007(macOS) orss -tlnp | grep 54007(Linux)
Security
- Command allowlist -- Only
zsh,bash,sh,fish,claude, andopencodecan be launched as terminal commands - Ticket-based WebSocket auth -- Short-lived, single-use tickets (30-second expiry) keep API keys out of WebSocket URLs
- Bearer token on all REST endpoints -- Every request requires
Authorization: Bearer <API_KEY> - Working directory validation -- The
cwdparameter is validated against the user's home directory; symlink traversal is blocked - No credentials in code -- All secrets loaded from
.envat runtime;.envis gitignored - APNs JWT rotation -- Push notification tokens are generated with short expiry and rotated automatically
License
MIT
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi