{"id":47701663,"url":"https://github.com/jskoiz/portpourri","last_synced_at":"2026-04-02T17:21:34.417Z","repository":{"id":347149554,"uuid":"1193033147","full_name":"jskoiz/portpourri","owner":"jskoiz","description":"macOS menu bar app for monitoring local dev port usage — see which ports are in use, who owns them, and resolve conflicts instantly","archived":false,"fork":false,"pushed_at":"2026-03-29T03:13:21.000Z","size":1977,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-29T03:19:41.690Z","etag":null,"topics":["developer-tools","local-development","macos","menu-bar","node","port-monitor","swift","swiftui"],"latest_commit_sha":null,"homepage":"https://portpoourri.com","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jskoiz.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-26T20:04:12.000Z","updated_at":"2026-03-29T03:06:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jskoiz/portpourri","commit_stats":null,"previous_names":["jskoiz/node-watcher","jskoiz/portpourri"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/jskoiz/portpourri","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jskoiz%2Fportpourri","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jskoiz%2Fportpourri/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jskoiz%2Fportpourri/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jskoiz%2Fportpourri/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jskoiz","download_url":"https://codeload.github.com/jskoiz/portpourri/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jskoiz%2Fportpourri/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31311313,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["developer-tools","local-development","macos","menu-bar","node","port-monitor","swift","swiftui"],"created_at":"2026-04-02T17:21:33.753Z","updated_at":"2026-04-02T17:21:34.409Z","avatar_url":"https://github.com/jskoiz.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Portpourri\n\n[![CI](https://github.com/jskoiz/portpourri/actions/workflows/ci.yml/badge.svg)](https://github.com/jskoiz/portpourri/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![macOS 14+](https://img.shields.io/badge/macOS-14%2B-black.svg)](https://www.apple.com/macos/)\n[![Swift 6.0](https://img.shields.io/badge/Swift-6.0-orange.svg)](https://swift.org)\n\nA macOS menu bar app that answers one question instantly: **which local ports are in use, who owns them, and is it my dev server or something blocking it?**\n\nIf you've ever had multiple projects fighting over ports 3000, 5173, or 8081 and resorted to a pile of `lsof` and `ps` commands, Portpourri replaces that with a single glance.\n\n## Features\n\n- **Live port monitoring** in the menu bar with a compact summary\n- **Project-aware** — maps Node processes back to their project root (`package.json`, `.git`, lockfiles)\n- **Smart classification** — identifies Vite, Next.js, Expo, Storybook, Nest, and other Node-family tools by name\n- **Conflict detection** — distinguishes \"your app owns this port\" from \"Docker is blocking it\" or \"an SSH tunnel is occupying it\"\n- **Safe actions** — context-aware resolution (free port, stop tunnel, configurable port command template) with no destructive force-kill\n- **Configurable** — settings for watched ports, refresh cadence, display modes, hotkeys, port command template, and grouping\n- **CLI included** — `snapshot`, `why`, `list`, and `doctor` for scripting, CI, and terminal-first diagnosis\n\n## Requirements\n\n- macOS 14 (Sonoma) or later\n- Swift 6.0+ toolchain (Xcode 16+)\n\n## Install\n\n### Download\n\nGrab the latest signed release from [GitHub Releases](https://github.com/jskoiz/portpourri/releases/latest). Unzip, drag to Applications, and launch — the app appears in the menu bar.\n\n### Build from source\n\n```bash\ngit clone https://github.com/jskoiz/portpourri.git\ncd portpourri\n./Scripts/package_app.sh\n./Scripts/verify_release_bundle.sh .build/Portpourri.app \"$(tr -d '[:space:]' \u003c VERSION)\"\nopen .build/Portpourri.app\n```\n\n### Run the app\n\n```bash\n# As a menu bar app\n./Scripts/package_app.sh\nopen .build/Portpourri.app\n\n# Or directly (development mode)\nswift run PortpourriApp\n\n# With sample data (no real processes needed)\nswift run PortpourriApp --sample-data\n```\n\n### CLI\n\n```bash\n# Live snapshot of all listening processes (schemaVersion + snapshot envelope)\nswift run portpourri snapshot --json\n\n# Explain one port\nswift run portpourri why 3000\n\n# Show watched-port status\nswift run portpourri list --watched\n\n# Show all current listeners\nswift run portpourri list --all\n\n# Run a human-readable diagnostics pass\nswift run portpourri doctor\n\n# Dump test fixtures with the same JSON envelope\nswift run portpourri fixtures --name mixed --json\n```\n\n## How It Works\n\nPortpourri builds a snapshot of local listening processes in a pipeline:\n\n1. **Probe** — scans TCP sockets via `lsof`\n2. **Enrich** — adds process metadata from `ps`\n3. **Resolve** — walks up from the process working directory to find the project root\n4. **Classify** — identifies Node-family dev tools (vite, next, expo, etc.) from the command line\n5. **Collapse** — deduplicates IPv4/IPv6 listeners into one logical port owner\n6. **Assess** — marks watched ports as owned by your app or blocked by something else\n\nThis is why Portpourri says \"3000 is blocked by Docker\" or \"8081 is owned by the Expo app in `~/projects/mobile`\" instead of just \"PID 12345 is using port 3000.\"\n\n## Architecture\n\n```\nSources/\n  PortpourriCore/    # Models, parsers, classifier, snapshot service (no UI)\n  PortpourriApp/     # SwiftUI menu bar app\n  PortpourriCLI/     # CLI commands (snapshot, why, list, doctor, fixtures)\nTests/\n  PortpourriCoreTests/   # Fixture-based parser, resolver, and integration tests\n  PortpourriCLITests/    # Fixture-backed CLI output and JSON contract tests\nScripts/\n  package_app.sh      # Build and wrap into .app bundle\n  dev_harness.sh      # Spin up local test listeners for manual testing\n  extract_release_notes.py  # Build GitHub Release notes from CHANGELOG.md\nsite/\n  index.html          # Marketing homepage\n  css/                # Styles\n  js/                 # Interactive demo and animations\n  data/               # Release manifest (version, download URL)\n  assets/             # Logos and marketing assets\ndocs/\n  product.md          # Product spec and design philosophy\n  architecture.md     # Module boundaries and data flow\n  ui.md               # Menu bar and popover layout\n  dev-harness.md      # Testing strategy and validation commands\n```\n\nThe core library (`PortpourriCore`) is deliberately free of AppKit/SwiftUI so it can be tested independently and reused by both the GUI and CLI.\n\n## Development\n\n```bash\n# Build\nswift build\n\n# Test\nswift test\n\n# Run with sample data\nswift run PortpourriApp --sample-data\n\n# Run the dev harness (creates real test listeners)\n./Scripts/dev_harness.sh\n```\n\nSee [`docs/dev-harness.md`](docs/dev-harness.md) for the full testing strategy.\nSee [`docs/troubleshooting.md`](docs/troubleshooting.md) for common failure cases and the probe commands Portpourri uses under the hood.\n\n## Contributing\n\nContributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjskoiz%2Fportpourri","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjskoiz%2Fportpourri","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjskoiz%2Fportpourri/lists"}