pew_pew_plx 0.5.0
pew_pew_plx: ^0.5.0 copied to clipboard
A Dart CLI tool for project file monitoring and tooling automation
[Pew Pew Plx Hero]
Pew Pew Plx #
A Dart CLI for project file monitoring and tooling automation.
Features #
- Real-time file change monitoring with throttled JSON output
- Bidirectional communication via stdin/stdout JSON protocol
- File read (
get) and directory listing (list) operations - Clipboard-to-file paste via
pbpaste - File content appending with duplicate detection
- Image generation from text prompts via Gemini API (
plx create image) - List feedback markers (
plx list feedback) and PR comments (plx list comments) with markdown/JSON/YAML output - Copy feedback markers or PR comments to clipboard (
plx copy feedback,plx copy comments) - Authentication with PLX backend (
plx login,plx logout,plx show auth) - Configurable watch extensions, ignore folders, and throttle rate
- Path traversal protection and extension validation
Platform Support #
| Platform | Status | Notes |
|---|---|---|
| macOS (arm64, x64) | Fully supported | Primary development platform |
| Linux (x64) | Partially supported | Clipboard commands require xclip installed |
| Windows | Not supported | — |
Table of Contents #
- Platform Support
- Installation
- External Dependencies
- Commands
- Usage
- Configuration
- How It Works
- Development
- Troubleshooting
- License
Installation #
Homebrew (macOS / Linux) #
brew tap appboypov/tap
brew install plx
pub.dev #
From the repo (path-based, for development or when using local symlinks):
dart pub global activate --source path .
When the package is published to pub.dev:
dart pub global activate pew_pew_plx
External Dependencies #
These tools must be available on your PATH for full functionality:
| Tool | Required By | Install |
|---|---|---|
git |
All commands | Pre-installed on macOS, apt install git on Linux |
gh |
copy comments, list comments |
GitHub CLI |
xclip |
copy, paste (Linux only) |
apt install xclip |
claude |
watch (agent mode) |
Claude Code CLI |
Commands #
| Command | Description |
|---|---|
plx watch project |
Monitor project files for changes with JSON output |
plx paste <filename> |
Write clipboard content to a file in the current directory |
plx append <source> <target> |
Append source file content to target file and delete source |
plx create image <prompt> |
Generate images from text prompts via Gemini API |
plx list feedback |
List feedback markers in source files (markdown by default) |
plx list comments |
List unresolved PR review comments (markdown by default) |
plx copy feedback |
Find feedback markers and copy categorized report to clipboard |
plx copy comments |
Fetch unresolved PR comments and copy to clipboard |
plx login |
Authenticate with the PLX backend via browser |
plx logout |
Clear stored authentication credentials |
plx show auth |
Show current authentication status |
plx init |
Ensure config schema is up to date |
plx --version |
Print the current version |
Usage #
Watch Project Files #
Monitor project files for changes with throttled JSON output:
plx watch project
Output (stdout)
File changes are emitted as JSON:
{"event": "create", "path": "docs/readme.md", "content": "# Hello"}
{"event": "modify", "path": "docs/readme.md", "content": "# Hello World"}
{"event": "delete", "path": "docs/readme.md", "content": null}
Input (stdin)
Send JSON requests to write or delete files:
{"event": "create", "path": "docs/new.md", "content": "# New File", "id": "req-1"}
{"event": "modify", "path": "docs/existing.md", "content": "Updated content", "id": "req-2"}
{"event": "delete", "path": "docs/old.md", "id": "req-3"}
Responses mirror the request structure:
{"event": "create", "path": "docs/new.md", "content": "# New File", "id": "req-1"}
{"event": "error", "path": "docs/invalid.txt", "content": "File extension not allowed", "id": "req-4"}
Read Operations (stdin)
Request file content or directory listings:
{"event": "get", "path": "docs/readme.md", "id": "req-5"}
{"event": "list", "path": "docs", "id": "req-6"}
Get response (single file):
{"event": "get", "path": "docs/readme.md", "content": "# Hello", "lastModified": 1737475200000, "id": "req-5"}
List response (directory contents):
{"event": "list", "path": "docs", "files": [{"path": "docs/readme.md", "content": "# Hello", "lastModified": 1737475200000}], "id": "req-6"}
Event Schema
| Field | Type | Description |
|---|---|---|
event |
string | create, modify, delete, get, list, or error |
path |
string | Relative path within watched directory |
content |
string? | File content (null for delete/list/error) |
id |
string? | Optional correlation ID (echoed in response) |
lastModified |
int? | File timestamp in milliseconds (get responses) |
files |
array? | File entries with path, content, lastModified (list responses) |
Constraints:
- Paths must be within the watched directory
- File extension must match configured extensions
- Paths in ignored folders are rejected
- Parent directories are created automatically for writes
Paste Clipboard to File #
Write clipboard content to a file in the current directory using pbpaste (macOS only):
plx paste notes.md
This reads the system clipboard and writes its content to the specified file, creating parent directories if needed.
Append File Content #
Append the content of a source file to a target file, then delete the source:
plx append fragment.md document.md
The command:
- Verifies both files exist
- Checks the target does not already contain the source content (duplicate detection)
- Appends source content to the end of the target
- Deletes the source file after a successful append
Create Image #
Generate images from text prompts using the Gemini API:
plx create image "a red apple"
plx create image "a blue cat" --output ./my-images
plx create image "sunset over mountains" --filename landscape.png --count 4
Options:
--output,-o: Output directory (default:workspace/images)--filename,-f: Output filename or path--count,-n: Number of images to generate (1-8, default: 1)--model,-m: Gemini model override (e.g.gemini-2.0-flash-exp)
API key resolution (first found): GEMINI_API_KEY env, PLX_GEMINI_API_KEY env, or create_image.api_key in config.
List Feedback #
List feedback markers (#FEEDBACK) found in source files. Default output is markdown; use -o json or -o yaml for structured output:
plx list feedback
plx list feedback -o json
plx list feedback -o yaml
Requires feedback config in .plx/config.yaml (marker, extensions, ignore_folders).
List Comments #
List unresolved PR review comments from the current branch. Requires gh and a branch with an open PR. Default output is markdown:
plx list comments
plx list comments -o json
plx list comments -o yaml
Copy Feedback #
Find all feedback markers (#FEEDBACK) in source files and copy a categorized report to the clipboard. Uses pbcopy (macOS only). Requires feedback config in .plx/config.yaml:
plx copy feedback
The report is printed to stdout and copied to the clipboard.
Copy Comments #
Fetch unresolved PR review comments from the current branch and copy a formatted report to the clipboard. Uses pbcopy (macOS only). Requires gh and a branch with an open PR:
plx copy comments
The report is printed to stdout and copied to the clipboard.
Authentication #
Authenticate with the PLX backend for features that require it:
plx login
Opens a browser for sign-in. If the browser does not open, the verification URL and code are printed. Credentials are stored locally.
Check current status:
plx show auth
Clear stored credentials:
plx logout
Configuration #
Configuration is split across two files. Run plx init to merge new config keys or migrate legacy keys.
User config (~/.plx/config.yaml) #
Stores personal credentials and per-user settings:
auth: Firebase authentication credentialsagent: Agent configurationcreate_image.api_key: Gemini API key (fallback if env vars not set)
Project config (.plx/config.yaml) #
Stored in the project root (auto-created with defaults) and checked in per project:
watch:
throttle_ms: 1000
extensions:
- .md
ignore_folders:
- .git
- node_modules
- build
- .dart_tool
- .plx
create_image:
default_output_dir: workspace/images
default_model: gemini-2.0-flash-exp
feedback:
marker: "#FEEDBACK"
extensions: [.dart, .ts, .tsx, .js, .jsx, .kt, .swift, .java, .go]
ignore_folders: [.git, node_modules, build, .dart_tool, .plx]
| Setting | Description | Default |
|---|---|---|
throttle_ms |
Minimum milliseconds between events | 1000 |
extensions |
File extensions to watch | [.md] |
ignore_folders |
Folders to exclude from watching | [.git, node_modules, build, .dart_tool, .plx] |
create_image.default_output_dir |
Default output directory for generated images | workspace/images |
create_image.default_model |
Default Gemini model for image generation | gemini-2.0-flash-exp |
feedback.marker |
Feedback marker string to scan for | #FEEDBACK |
feedback.extensions |
File extensions to scan for feedback | [.dart, .ts, .tsx, .js, .jsx, .kt, .swift, .java, .go] |
feedback.ignore_folders |
Folders to exclude from feedback scan | [.git, node_modules, build, .dart_tool, .plx] |
How It Works #
This section explains the architecture of plx watch project for developers new to the project.
What is it? #
Pew Pew Plx is a Dart CLI that:
- Watches project files and reports changes as JSON
- Accepts JSON commands on stdin and responds on stdout
- Can run Claude Code agents and list their sessions
It's designed to be driven by another app (e.g. plaza) that sends JSON and reads JSON back.
The Big Picture: plx watch project #
When you run plx watch project, the process:
- Starts watching the current directory for file changes
- Reads JSON lines from stdin
- Writes JSON lines to stdout
It's a long-running process that communicates via stdin/stdout using JSON.
Two Kinds of Output #
There are two main output streams:
| Source | What it does |
|---|---|
| File watcher | When files change, it emits events like {"event":"create","path":"docs/new.md",...} |
| Agent system | When you run agents or list sessions, it emits events like {"type":"agent","run_id":"...","stream":"lifecycle",...} |
Both go to stdout, but they're distinguished by fields like event vs type.
How stdin Requests Are Handled #
Every line from stdin is JSON. The first thing the system does is look at the type field to decide what to do:
stdin line → parse JSON → check "type" → route to the right handler
The router (StdinRequestRouter) does this branching:
type value |
Handler | What happens |
|---|---|---|
agent.run |
AgentRunHandler | Runs a Claude Code agent |
agent.sessions.list |
SessionStore | Lists Claude Code sessions |
agent.session.get |
SessionStore | Gets a session transcript |
| anything else | FileReaderService / FileWriterService | File read/write (get, list, create, modify, delete) |
So the router is a dispatcher: one JSON line in, one or more JSON lines out.
Agent Flow: agent.run #
When you send:
{"type":"agent.run","prompt":"Say hi","args":["--output-format","stream-json"],"cwd":"/path/to/project"}
the flow is:
- StdinRequestRouter parses the JSON and sees
type: "agent.run". - It calls AgentRunHandler.handleRun().
- AgentRunHandler validates that
cwdexists and is a directory, generates arun_idif you didn't provide one, then calls the ClaudeCodeBackend to run the agent. - ClaudeCodeBackend spawns
claude -p "Say hi" --output-format stream-jsonin the givencwd, reads the subprocess stdout line-by-line, parses the JSON, and converts it intoAgentEventDtoobjects that it yields as a stream. - AgentRunHandler receives each event and pushes it to the AgentEventBus.
- ProjectCommand has subscribed to the bus and writes each event to stdout as
{"type":"agent","run_id":"...","stream":"lifecycle","data":{"phase":"start"},...}.
So: stdin request → handler → backend (subprocess) → event bus → stdout.
Agent Event Bus #
The AgentEventBus is a simple pub/sub:
- Publish:
emitAgentEvent(event)— anyone can emit events - Subscribe:
onAgentEvent(callback)— returns an unsubscribe function
There's typically one subscriber: the one that writes agent events to stdout. That keeps the "write to stdout" logic in one place instead of scattered across handlers.
Session List: agent.sessions.list #
When you send {"type":"agent.sessions.list"}:
- StdinRequestRouter sees
type: "agent.sessions.list". - It calls SessionStore.listSessions().
- SessionStore resolves the Claude data dir (env
PLX_CLAUDE_DATA_DIR, config, or~/.claude), scans~/.claude/projects/...for JSONL session files, and reads the first line of each file to build session summaries. - The router writes the response directly to stdout:
{"type":"agent.sessions.list","sessions":[{"session_id":"...","project_dir":"..."},...]}.
No event bus here — the router writes the response itself.
Dependency Injection (GetIt) #
The project uses GetIt for dependency injection:
- Lazy singletons: e.g.
AgentEventBus,SessionStore— created on first use - Factories: e.g.
AgentRunHandler,StdinRequestRouter— new instance each time
Registration happens in LocatorService at startup. Commands and services get their dependencies via GetIt.I.get<SomeType>() instead of constructing them manually.
Summary Diagram #
stdin (JSON lines)
│
▼
┌──────────────────────┐
│ StdinRequestRouter │
│ (parse, switch on │
│ "type") │
└──────────┬────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
agent.run agent.sessions watch (get/list/
│ .list create/modify)
│ │ │
▼ ▼ ▼
AgentRunHandler SessionStore FileReader/
│ │ FileWriter
▼ │ │
ClaudeCodeBackend │ │
(spawn claude) │ │
│ │ │
▼ │ │
AgentEventBus ◄────────┘ │
│ │
└───────────────┬────────────────┘
│
▼
stdout (JSON lines)
Key Takeaways #
- plx is a JSON-over-stdin/stdout CLI for file watching and agent control.
- StdinRequestRouter routes each JSON line by
typeto the right handler. - AgentEventBus is a pub/sub for agent events; one subscriber writes them to stdout.
- ClaudeCodeBackend spawns the
claudeCLI and converts its output into events. - SessionStore reads Claude's session files from disk.
- ProjectCommand ties everything together: file watcher, stdin loop, and bus subscriber.
Development #
Local setup: This package uses path dependencies under symlinks/ that must point to the turbo_* packages (e.g. turbo_plx_cli, turbo_promptable, turbo_response, turbo_serializable). Create the symlinks if missing:
# From pew_pew_plx repo root, with turbo_packages as sibling directory:
ln -sf ../turbo_packages/turbo_plx_cli symlinks/turbo_plx_cli
ln -sf ../turbo_packages/turbo_promptable symlinks/turbo_promptable
ln -sf ../turbo_packages/turbo_response symlinks/turbo_response
ln -sf ../turbo_packages/turbo_serializable symlinks/turbo_serializable
# Then get dependencies and activate
dart pub get && dart pub global activate --source path .
# Run in development mode
dart run bin/plxdev.dart watch project
# Full CI pipeline (get, build, analyze, test)
make all
# Individual commands
make get # Get dependencies
make build # Generate code (build_runner)
make analyze # Static analysis
make test # Run tests with coverage
make format # Format code
make dev # Compile and install plxdev
make prod # Compile and install plx (requires PLX_FIREBASE_API_KEY)
# Production build requires Firebase API key
make prod PLX_FIREBASE_API_KEY=<your-key>
# Firebase Functions (contact form, auth) require RESEND_API_KEY, RESEND_FROM_EMAIL, RESEND_TO_EMAIL — see firebase/README.md
# Or compile manually:
dart compile exe bin/plx.dart -o build/plx --define=env=prod --define=PLX_FIREBASE_API_KEY=<your-key>
Troubleshooting #
If you see "The current activation of pew_pew_plx cannot resolve to the same set of dependencies", ensure the four symlinks/ path dependencies exist, then from the repo root run:
rm -rf .dart_tool/pub/bin/pew_pew_plx && dart pub get && dart pub global activate --source path .
License #
See LICENSE for details.