quoid/userscripts
An open-source userscript manager for Safari
Single-maintainer risk — review before adopting
worst of 4 axescopyleft license (GPL-3.0) — review compatibility; top contributor handles 97% of recent commits
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit today
- ✓2 active contributors
- ✓GPL-3.0 licensed
Show 5 more →Show less
- ✓CI configured
- ✓Tests present
- ⚠Small team — 2 contributors active in recent commits
- ⚠Single-maintainer risk — top contributor 97% of recent commits
- ⚠GPL-3.0 is copyleft — check downstream compatibility
What would change the summary?
- →Use as dependency Concerns → Mixed if: relicense under MIT/Apache-2.0 (rare for established libs)
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Forkable" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/quoid/userscripts)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/quoid/userscripts on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: quoid/userscripts
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/quoid/userscripts shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
WAIT — Single-maintainer risk — review before adopting
- Last commit today
- 2 active contributors
- GPL-3.0 licensed
- CI configured
- Tests present
- ⚠ Small team — 2 contributors active in recent commits
- ⚠ Single-maintainer risk — top contributor 97% of recent commits
- ⚠ GPL-3.0 is copyleft — check downstream compatibility
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live quoid/userscripts
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/quoid/userscripts.
What it runs against: a local clone of quoid/userscripts — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in quoid/userscripts | Confirms the artifact applies here, not a fork |
| 2 | License is still GPL-3.0 | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 30 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of quoid/userscripts. If you don't
# have one yet, run these first:
#
# git clone https://github.com/quoid/userscripts.git
# cd userscripts
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of quoid/userscripts and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "quoid/userscripts(\\.git)?\\b" \\
&& ok "origin remote is quoid/userscripts" \\
|| miss "origin remote is not quoid/userscripts (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(GPL-3\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"GPL-3\\.0\"" package.json 2>/dev/null) \\
&& ok "license is GPL-3.0" \\
|| miss "license drift — was GPL-3.0 at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "src/app/App.svelte" \\
&& ok "src/app/App.svelte" \\
|| miss "missing critical file: src/app/App.svelte"
test -f "public/ext/shared/_locales/en/messages.json" \\
&& ok "public/ext/shared/_locales/en/messages.json" \\
|| miss "missing critical file: public/ext/shared/_locales/en/messages.json"
test -f "package.json" \\
&& ok "package.json" \\
|| miss "missing critical file: package.json"
test -f "entry-ext-background.html" \\
&& ok "entry-ext-background.html" \\
|| miss "missing critical file: entry-ext-background.html"
test -f "entry-ext-extension-page.html" \\
&& ok "entry-ext-extension-page.html" \\
|| miss "missing critical file: entry-ext-extension-page.html"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 30 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~0d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/quoid/userscripts"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Userscripts is an open-source Safari userscript manager and editor for macOS and iOS that allows users to create, edit, and execute custom JavaScript on web pages. It bypasses Safari's native extension limitations by providing a full-featured editor (built with CodeMirror) and extension system that injects scripts via WebExtension APIs, delivering capabilities similar to Tampermonkey but natively integrated into Safari. Hybrid architecture: Swift backend (153K lines) implements the native macOS/iOS app shell and file system integration, while JavaScript/Svelte (144K + 135K lines) powers the userscript editor UI and extension logic. Entry points are split by context: entry-ext-action-popup.html (extension popup), entry-ext-background.html (background script), entry-app-webview.html (app webview), and entry-ext-extension-page.html (extension settings page). Platform-specific builds are scripted in scripts/ (dev-safari.js, build-safari-15.js, etc.) with environment variable SAFARI_PLATFORM routing for macOS vs iOS.
👥Who it's for
Web developers and power users on macOS/iOS who want to customize website behavior with userscripts without jailbreaking or using desktop-only tools; also Safari extension developers looking for a reference implementation of WebExtension APIs on Apple's platform.
🌱Maturity & risk
Production-ready and actively maintained. The project is distributed on the Apple App Store (ID 1463298887), supports multiple Safari versions (15 and 16.4+), has a defined CI/CD pipeline (GitHub Actions in .github/workflows/), and shows consistent updates with version-specific build scripts. Recent development includes platform-specific builds for macOS and iOS.
Low-to-moderate risk: the project is single-maintainer (quoid) with a small team, has ~20 dependencies in package.json but they are standard tools (Vite, Svelte, ESLint), and requires careful handling of Safari WebExtension API quirks which differ across versions. The codebase spans three major languages (Swift, JavaScript, Svelte) which increases cognitive load; dependency on Apple's approval process for App Store updates adds deployment risk.
Active areas of work
Active development with Safari version-specific support: separate build pipelines for Safari 15 and Safari 16.4+ (seen in npm scripts build:mac-safari-15, build:ios-safari-15, etc.). A development progress tracker is maintained at GitHub Projects. Recent work appears focused on iOS/macOS platform parity and iCloud sync reliability (referenced in README for iOS 18+ file eviction handling).
🚀Get running
Clone and install dependencies: git clone https://github.com/quoid/userscripts.git && cd userscripts && npm install. For local Safari dev: npm run dev:mac-safari (macOS) or npm run dev:ios-safari (iOS). For production builds: npm run build:mac-safari-15 or npm run build:ios-safari-16.4 (environment variable SAFARI_PLATFORM is auto-set by these scripts).
Daily commands:
Development: npm run dev (Vite dev server for web UI), npm run dev:mac-safari (Safari extension dev mode), or npm run dev:ios-safari (iOS Safari). Production builds: npm run build:mac-safari-15, npm run build:ios-safari-16.4 (outputs to dist/ for Safari packaging). Linting: npm run lint:js && npm run lint:css or auto-fix with npm run prettier.
🗺️Map of the codebase
src/app/App.svelte— Root Svelte component for the entire userscript manager UI; every UI feature flows through this entry pointpublic/ext/shared/_locales/en/messages.json— Core i18n strings used throughout the extension; changes here affect all UI text across the apppackage.json— Defines build scripts for Safari 15/16.4 and iOS/macOS targets; critical for understanding the build matrixentry-ext-background.html— Service worker entry point for the Safari extension background logic; manages all extension lifecycle eventsentry-ext-extension-page.html— Main extension settings/management UI entry point; where users configure and edit userscriptssrc/app/i18n.js— Internationalization setup and message loading logic; required to understand how localized strings are resolvedscripts/build-safari-16.4.js— Build orchestration for Safari 16.4+ targets; defines how the extension is packaged and deployed
🛠️How to make changes
Add a new UI page/section
- Create a new Svelte component file in src/app/ or appropriate subdirectory for the new page (
src/app/App.svelte) - Import and conditionally render the component in the root App.svelte based on app state/navigation (
src/app/App.svelte) - Add English messages to the messages.js file for all user-facing strings in your component (
src/app/_locales/en/messages.js) - Add the same message keys to all other locale files (zh, zh_Hant) to maintain consistency (
src/app/_locales/zh/messages.js) - Add any global styles to app.css; component-scoped styles go in the Svelte <style> block (
src/app/app.css)
Add support for a new Safari version
- Create a new manifest file under public/ext/ for the Safari version (e.g., public/ext/safari-17/manifest.json) (
public/ext/safari-16.4/manifest.json) - Create a new build script scripts/build-safari-XX.js by copying and adapting an existing version build script (
scripts/build-safari-16.4.js) - Add npm scripts to package.json for dev and build targets (e.g., dev:mac-safari-17, build:mac-safari-17) (
package.json) - Update the build script to copy the correct manifest and assets to the output directory (
scripts/build-safari-16.4.js)
Add a new localization language
- Create a new locale directory under src/app/_locales/ (e.g., src/app/_locales/fr/) (
src/app/_locales/en/messages.js) - Create messages.js in the new locale directory with all keys from the English version translated (
src/app/_locales/en/messages.js) - Create the same directory structure and messages.json under public/ext/shared/_locales/ for WebExtension API strings (
public/ext/shared/_locales/en/messages.json) - Update src/app/i18n.js to register the new locale and ensure the locale resolver detects the user's language preference (
src/app/i18n.js)
Modify build output or extension metadata
- Edit the target Safari version's manifest file (e.g., public/ext/safari-16.4/manifest.json) to update permissions, icons, or metadata (
public/ext/safari-16.4/manifest.json) - If adding new assets, place them in public/ext/safari-16.4/images/ or public/ext/shared/images/ (
public/ext/safari-16.4/images) - Update the corresponding build script (scripts/build-safari-16.4.js) if special processing of the new assets is needed (
scripts/build-safari-16.4.js) - Run the build with npm run build:mac-safari-16.4 or build:ios-safari-16.4 to verify the changes (
package.json)
🔧Why these technologies
- Svelte — Lightweight reactive UI framework with small bundle size, critical for browser extension constraints and fast app responsiveness
- Safari WebExtension API (MV2/MV3) — Native extension format required by Safari; MV2 for Safari 15, MV3 for Safari 16.4+; enables script injection and browser integration
- Vite — Fast module bundler with hot module reloading; enables rapid development cycle and minimal build overhead
- Node.js build scripts — Custom orchestration for platform-specific builds (iOS vs macOS, Safari 15 vs 16.4); allows flexible manifest and asset management per target
⚖️Trade-offs already made
- Separate build pipelines for Safari 15 and Safari 16.4+
- Why: Safari 15 uses an older extension manifest format; Safari 16.4+ adopted Manifest V3. Supporting both requires distinct build outputs and manifest schemas
- Consequence: undefined
🪤Traps & gotchas
SAFARI_PLATFORM env var is critical: Build scripts (build-safari-15.js, etc.) read SAFARI_PLATFORM=mac or SAFARI_PLATFORM=ios to generate platform-specific outputs; omitting it or setting it wrong will cause mismatched builds. Separate Swift project: The Swift app shell (macOS/iOS native code) lives in a companion Xcode project not visible in this JavaScript repo; JavaScript changes must be bundled and passed to that project for packaging. Multiple entry points require separate Vite configs: The entry-*.html files suggest custom entry point logic in vite.config.js (not shown in file list) that may not be obvious. App Store deployment via fastlane: The fastlane/ directory indicates automated release; credentials and certificate management are required (see fastlane/Matchfile).
🏗️Architecture
💡Concepts to learn
- Content Script Injection & Messaging — Userscripts injects JavaScript into web pages via Safari's content script APIs and communicates with the extension background script; understanding this cross-context messaging model is essential to debugging script execution flow.
- WebExtension API Polyfill — Safari's WebExtension implementation differs from Chrome/Firefox; the
webextension-polyfillnpm package bridges these gaps, and the version-specific build scripts (safari-15.js vs safari-16.4.js) handle API divergence. - Vite Multi-Entry-Point Bundling — Four separate entry points (popup, background, extension page, app webview) must be bundled independently for Safari; Vite's config (not shown but inferred from
entry-*.htmlfiles) uses custom rollup options to generate separate outputs. - File System Sandboxing (macOS/iOS) — The Swift native layer manages file I/O for userscript persistence; userscripts cannot directly access the file system on iOS/macOS, so the app mediates via native APIs (FileManager, iCloud sync).
- Platform-Conditional Builds — Environment variable
SAFARI_PLATFORM(mac or ios) controls build output; different platforms require different WebExtension permissions, icon sizes, and native integration points, making the build pipeline non-trivial. - Svelte Store & Reactive Declarations — Editor state (current script, unsaved changes, metadata) is likely managed via Svelte stores; the UI reactively reflects userscript metadata and validation without explicit DOM manipulation.
- App Store Code Signing & Fastlane Automation — Distribution to the Apple App Store requires code signing, provisioning profiles, and compliance checks;
fastlane/automates these steps, making the release pipeline reproducible and reducing manual error.
🔗Related repos
violentmonkey/violentmonkey— Popular open-source userscript manager with multi-browser support (Chrome, Firefox, Edge); direct competitor providing feature parity reference and cross-platform design patterns.Tampermonkey/tampermonkey— Industry-standard userscript manager; closed-source but well-documented, serves as the UX gold standard that Userscripts aims to replicate for Safari.webkit/webkit— WebKit (Safari's engine) source; essential for understanding Safari WebExtension API limitations and version-specific behaviors that the build scripts handle.sveltejs/svelte— Core framework powering the Userscripts editor UI; understanding Svelte 5 reactivity and SFC patterns is critical for UI development in this project.codemirror/codemirror5— The text editor dependency (v5.65.19 in package.json); customizations and plugin integration for syntax highlighting happen here.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add CI workflow to validate Safari manifest.json files across versions
The repo maintains multiple Safari manifest versions (safari-15, safari-16.4, safari-dev) in public/ext/. Currently there's no automated validation that these manifests are valid JSON, have required fields, or maintain consistency across versions. A GitHub Action could catch manifest syntax errors and version mismatches before they reach production.
- [ ] Create .github/workflows/validate-manifests.yml to run on PR/push
- [ ] Add Node.js script to validate JSON syntax for all public/ext//manifest.json files
- [ ] Add schema validation checks for required WebExtension API fields (name, version, permissions, etc.)
- [ ] Add cross-version consistency checks (e.g., ensure version numbers match across manifest files)
- [ ] Document the validation rules in docs/dev.md
Add build output verification tests for Safari extension packaging
The repo has complex build scripts for different Safari versions (build-safari-15.js, build-safari-16.4.js) and platforms (mac/ios), but no automated tests verify that the build outputs are correct. This could catch regressions where builds fail silently or produce incomplete packages.
- [ ] Create tests/build-output.test.js or similar test file
- [ ] Add tests that verify: build scripts produce expected directory structure, all required manifest files exist, image assets are present, no bundling errors occur
- [ ] Test each platform/version combo (mac-safari-15, ios-safari-15, mac-safari-16.4, ios-safari-16.4)
- [ ] Integrate tests into lint.yml or create new GitHub Action workflow
- [ ] Document expected build output structure in docs/dev.md
Add CSS linting configuration validation and missing Stylelint rules
The repo uses Stylelint with config in .stylelintrc.json but the current setup only has standard + html configs. Given the project uses Svelte components and CSS-in-JS patterns, there are no rules for common issues like unused CSS variables, specificity problems, or Svelte-specific class naming. Adding targeted rules would improve style consistency.
- [ ] Review current .stylelintrc.json and identify gaps for Svelte projects
- [ ] Add stylelint-config-clean-order or similar for property ordering consistency
- [ ] Add custom rules/overrides for Svelte :global() blocks and component scoping
- [ ] Add rules for CSS variable usage and unused declarations
- [ ] Update lint:css script if needed and test against existing codebase
- [ ] Document style guidelines in docs/contributing.md with examples
🌿Good first issues
- Add missing TypeScript type definitions for Safari WebExtension APIs in
src/files; currently only@types/webextension-polyfillis installed, but Safari-specific APIs (e.g.,safari.app.getActiveTab()) lack types.: Medium: Improves IDE autocomplete and catches type errors early. - Write integration tests for the background script (
entry-ext-background.html) to verify userscript injection on different Safari versions; the test infrastructure exists (Jest config hint in tooling) but no test files are visible in the file list.: Medium-High: Prevents regressions in core userscript execution logic across Safari 15 and 16.4. - Document the CodeMirror configuration in
src/(syntax highlighting, theme, keybindings); no docs/editor-config.md exists, making it hard for contributors to customize the editor UI.: Low: Reduces contributor onboarding time and clarifies editor feature limitations.
📝Recent commits
Click to expand
Recent commits
8b743e3— Merge pull request #928 from quoid/theme-setting (ACTCD)5705b12— feat: aplly theme mode for editor and popup (ACTCD)dafc2cf— feat: add theme mode setting item (ACTCD)012fc83— style: use light-dark instead of prefers-color-scheme (ACTCD)bdaf750— Merge pull request #901 from quoid/investigate-memory-usage2 (ACTCD)b0990a5— fix: further reduces memory usage (ACTCD)dcb047e— Merge pull request #900 from quoid/memory-usage-analysis (ACTCD)f9944f7— fix: significantly reduces memory usage (ACTCD)7d77cab— build(gem): update fastlane (ACTCD)e287a03— ci: update to macos-26 runner image (ACTCD)
🔒Security observations
The Userscripts Safari codebase is a userscript manager extension with moderate security posture. Primary concerns include outdated CodeMirror dependency (v5 instead of v6), missing or unclear Content Security Policy implementation for a high-risk application handling user script execution, and lack of documented security boundaries around script execution contexts. The absence of automated dependency vulnerability scanning in CI/CD is a process-level gap. The codebase follows good development practices (ESLint, Prettier, Stylelint) which help prevent certain classes of bugs, but explicit security hardening is needed for the script execution engine and extension manifest configuration. Recommendations focus on upgrading dependencies, implementing strict CSP, documenting execution context security, and adding automated security scanning.
- Medium · Outdated CodeMirror Dependency —
package.json - devDependencies: codemirror. CodeMirror 5.65.19 is an older major version (v5). CodeMirror 6.x is the current stable version with significant security improvements and bug fixes. Using outdated versions may expose the application to known vulnerabilities in the editor component. Fix: Upgrade CodeMirror to the latest v6.x version. Review breaking changes and update code accordingly. This is especially important since CodeMirror handles user script editing which could be security-sensitive. - Medium · Missing Content Security Policy Headers —
Extension manifests and HTML entry points (public/ext/*/manifest.json, entry-ext-*.html). No explicit Content Security Policy (CSP) configuration found in the codebase. The application handles user scripts and has extension pages (entry-ext-*.html files), which are high-risk areas for XSS attacks. Without proper CSP headers, the application is vulnerable to script injection attacks. Fix: Implement strict Content Security Policy headers in manifest.json and HTTP response headers. Use directives like 'script-src' restricted to 'self', avoid 'unsafe-inline', and implement proper nonce or hash-based scripts. - Medium · User Script Execution Context Not Clearly Documented —
src/app, public/ext/shared. The application is a userscript manager that executes user-provided scripts. While this is by design, the codebase structure suggests script injection/execution occurs. Without clear sandboxing documentation and security boundaries, there's risk of malicious scripts affecting the extension itself. Fix: Document and implement clear security boundaries: (1) Isolate user script execution in separate contexts/iframes, (2) Implement proper content security policies for script execution, (3) Add validation/sanitization for script metadata parsing, (4) Consider using Web Workers for isolated execution. - Low · Missing Security Headers in Development Scripts —
scripts/ directory, vite configuration. Development scripts (scripts/dev-safari.js, scripts/build-*.js) and vite configuration lack explicit security headers configuration. While this is development-focused, it could lead to insecure defaults being deployed. Fix: Add security header configuration to build and development scripts. Ensure production builds include appropriate headers like X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and proper CSP. - Low · No Dependency Lock File Security Scanning —
.github/workflows/, package.json scripts. While package-lock.json is present, there's no evidence of automated dependency vulnerability scanning (npm audit, Snyk, etc.) in the GitHub workflows or configuration. Fix: Add automated security scanning to CI/CD pipeline: (1) Run 'npm audit' in GitHub Actions, (2) Integrate Snyk or similar tool for continuous dependency scanning, (3) Implement pre-commit hooks for dependency checks. - Low · Marked Library Usage Without Clear Sanitization —
package.json - marked dependency, src/app/. The marked library (v15.0.12) is included for markdown processing. Without clear evidence of output sanitization, there's potential for XSS if markdown is used to render user-controlled content. Fix: If marked is used to render user-provided markdown, implement output sanitization using DOMPurify or similar library. Never use marked output directly in innerHTML.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.