zipavlin/redline
Redline is a lightweight Laravel/PHP package that helps you define and enforce boundaries in your application. With simple configuration and a clean API, it adds practical guard rails and limits without heavy dependencies, making it a good fit for small to mid-sized projects.
Redline is a self-contained issue/report tracker that runs inside a Laravel host app: it ships its own SQLite StorageProvider, an embeddable widget, a dashboard, an HTTP API under /{path}/api/*, and — as of v0.3.0 — an MCP server so coding agents can read and triage reports. Your day-to-day job is wiring auth, roles, feature flags, and the upgrade path — not touching package internals.
REDLINE_FEATURES (e.g. REDLINE_FEATURES=screenshots,comments,mcp). This replaces REDLINE_DISABLED, and existing installs must set it once or every feature stays off. Use php artisan redline:enable to list features and switch them on/off from the CLI; a misspelled name hard-fails the app rather than being ignored.composer require the package, then run php artisan redline:update (alias redline:upgrade). It backs up the SQLite DB, runs pending migrations (including the new redline_tokens table), re-publishes assets, and clears config/view/route caches. It also prints what's enabled. Use --dry-run first, and --no-backup only when your platform already snapshots storage.LaravelAuthProvider resolves a Redline identity from your logged-in User; LaravelGuardProvider injects the widget and dashboard for authenticated users. There is no separate Redline login to build.config/redline.php (path prefix, features, auth providers, sanitizer, mail views), routes/redline.php (the v2 wire contract), redline-app/contracts/mcp.md (MCP tool contract), docs/06-guards.md (guard/404 semantics), and the published widget Blade view.developer, open a report, PATCH /report/{report}/priority to high, set due_at, and POST /report/{report}/member with a user_ids array from GET /user — that exercises auth, roles, priorities, due dates, members, and serialization in one pass. For agents: php artisan redline:enable mcp, php artisan redline:update, then php artisan redline:mcp [email protected] --name=claude to mint a token and get copy-paste config for Claude Code/Codex/Cursor/VS Code/Gemini CLI. Point the agent at {redline.path}/api/mcp and ask it to report_list then report_update_status.Feature flags are the new deployment contract. GET /bootstrap only advertises enabled features, so drive your widget/UI gating off the bootstrap payload rather than assuming a capability exists. Treat REDLINE_FEATURES as environment-specific config: staging with mcp on and production with it off is a legitimate setup, but then your docs and onboarding must say so.
MCP is a thin, permissioned façade over the same domain rules. Eight tools: report_list, report_get, screenshot_get, comment_list, user_list (read) and report_create, comment_create, report_update_status (write). Writes inherit the dashboard's rules — sanitized HTML, role permissions, status transitions, new-report mail — so you don't need a parallel authorization model. What you do need is a policy on which human's token an agent gets: mint a developer token for triage bots, a lower-privilege one for read-only assistants.
Token lifecycle is CLI-only. Mint via php artisan redline:mcp (interactive) or --user=/--name= for scripts; audit with --list; revoke with --revoke=, --revoke-all, or per-user. Tokens are rlm_-prefixed, accepted only as Authorization: Bearer, never expire, and are invalidated by a password change — but not by logout. There is no UI to revoke them, so put revocation in your offboarding runbook next to password resets.
report_get mutates state unless you ask it not to. Reading a report marks it noticed by default; pass mark_noticed: false (per the MCP contract) when an agent is just surveying. For any "summarize my backlog" workflow, explicitly opt out or your agents will silently clear the new-report queue.
Agent-created reports: send a URL, not a location. report_create derives location from url; the agent supplies element (a precise CSS selector, ≤512 chars, validated against the live page when the agent has a browser) and a viewport-relative pin. Instruct agents to prefer a real selector over a screenshot description — that's what makes the report actionable for a human.
Treat report text as untrusted input to the agent. The MCP server explicitly tells agents that report content is data, not instructions — mirror that in your own system prompts and tool descriptions, and don't let an agent chain report_get straight into shell commands or deployments. Human-authored bodies are the classic injection vector here.
Member assignment is a set operation, not a toggle. Both POST and DELETE /report/{report}/member take user_ids arrays; send the full desired set or computed deltas. Removal is special: developer+ and the report author can remove anyone, but any member can remove themselves. Agents cannot assign members yet (no MCP tool) — route that through the HTTP API from your own client code.
Treat the author and members as disjoint. The author is never a member; posting the author's id is a deliberate silent no-op, and member reads exclude the author. Filter the author out of pickers and don't render an "assign myself" affordance for owners.
Ownership + claim workflow. noticed → progress auto-assigns the acting user when eligible (approved, non-guest). Let your own "claim" button fall through to that transition instead of re-implementing assignment — otherwise you double-assign or assign ineligible users. Note that report_update_status over MCP triggers the same behavior for the token's user.
Priorities and due dates are just report columns. Priority is low|normal|high|critical, defaults to normal, and is patched via PATCH /report/{report}/priority (developer+ or owner). Due dates serialize as RFC3339 Z and are filterable/sortable via the grammar.
Handle the email PII boundary explicitly. Non-developer roles get 422 for include[]=email and filter[email]=… — an intentional denial, not a missing key. Branch your request builder on role instead of stripping keys afterwards; search matches names only for non-developers while ORing emails for developer+. filter[author.email:like] on /report stays rejected for everyone. The same role gates apply to user_list over MCP.
Emails are normalized. Trim + lowercase on write, case-insensitive on lookup. Send what the user typed; apply the same normalization to any mirror table or your joins drift.
Still on wire contract v2. has_media is gone — use filter[screenshot:exists]=1 and filter[video:exists]=1 in query builders, saved views, and client presets. user_agent is no longer serialized. Verify GET /{path}/api/bootstrap reports "version": 2 (and the feature list you expect) when debugging odd widget behavior.
Upgrade mechanics as deployment steps. The post-package-update Composer hook applies migrations (with a pre-migration backup) and re-publishes assets; StorageProvider::ensureSchema() no longer re-runs per-migration guards on boot. In CI prefer composer update + the hook, or run redline:update --dry-run to preview. Where the package version is pinned, run redline:update explicitly in the release step so migrations and asset publishes aren't skipped.
User administration via CLI. redline:user promotes existing users and edits name/email, which is the fastest way to seed a developer in fresh environments without touching the API.
REDLINE_FEATURES turns everything off — including screenshots and comments — with no error, so the symptom is "the widget lost its buttons." A typo'd feature name, conversely, hard-fails the app; if you hit that after a config change, php artisan config:clear. Remember redline:update shows what's currently enabled — check it as a post-deploy smoke test.REDLINE_DISABLED → REDLINE_FEATURES is a rename, not an addition. Old env vars are ignored. Audit every environment's .env, Dockerfile, Helm values, and Terraform-managed config; a missed environment ships with Redline effectively inert.route:cache/optimize can no longer leak Redline on hosts where it should be off. If Redline vanished after a deploy, check the guard before checking routes. Caveat: LaravelGuardProvider can't answer before the session stack runs, so its API/MCP surface is token-gated per request instead of 404 — see docs/06-guards.md.route:cache previously dropped Redline routes entirely. That's fixed, but cached routes from before the upgrade may still be stale — redline:update clears config/view/route caches; your deploy pipeline may need to as well.--revoke/--revoke-all on offboarding, and prefer one named token per agent per person (--name=) so --list stays legible.report_get marks reports noticed by default. Agents that "just read" will clear your new-report queue. Make mark_noticed: false the default in your prompt templates and only allow the mutating variant in explicit triage flows.POST /report now returns 403 for unauthorized users (previously a server error). That makes it safe to branch on: 403 = role problem, 422 = validation problem. Don't retry 403s.StorageProvider is the single concrete SQLite implementation; the old Orbit/FileStorageProvider backend and the Base/Database model layers are gone. Extend via container bindings or wrap the API.post-package-update hook plus unguarded ensureSchema() means a composer update in a read-only replica, or in a container lacking write access to the SQLite path, can fail or write a backup where you didn't expect. Pin the SQLite path and permissions in config/database.php and config/redline.php.redline:update --force never overwrites customized config or mail views. A feature, but it means new v0.3.0 defaults (features block, MCP settings) won't land once you've customized. Diff your published config against the package's config/redline.php after upgrading.screenshot_get output into external tooling.401, approval bumps token_version so pre-approval tokens die, and login returns 202 without a token. Treat 202 as "awaiting approval" and never cache tokens across the approval boundary.How can I help you explore Laravel packages today?