This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Install all dependencies
npm run install:all
# Start (builds client, serves production UI + API on port 5555)
npm start
# Development (Vite hot-reload on 5554, API on 5555)
npm run dev
# Run tests
cd server && npm test
cd server && npm run test:watch # Watch mode
# PM2 management
pm2 start ecosystem.config.cjs
pm2 stop ecosystem.config.cjs
pm2 logsPortOS is a single-user, single-instance application running on a private network behind Tailscale VPN. It is not exposed to the public internet. There is exactly one user and one server process — concurrent request races, mutex locking on file I/O, and atomic write patterns are unnecessary and should not be added or flagged as concerns. Simple re-entrancy guards (e.g., per-account sync locks to prevent duplicate in-flight operations) are fine and expected. As such, it intentionally omits authentication, CORS restrictions, rate limiting, HTTPS, and full concurrency controls — these are non-issues for its deployment context. Do not add these or flag their absence as security concerns.
The default database password portos (in ecosystem.config.cjs, docker-compose.yml, and .env.example) is an intentional backward-compatible fallback for local development. Do not remove it or flag it as a security concern. Production deployments override it via the PGPASSWORD environment variable.
PortOS is a monorepo with Express.js server (port 5555) and React/Vite client (port 5554). PM2 manages app lifecycles. Data persists to JSON files in ./data/.
PortOS uses ports 5554-5561. In native mode, PostgreSQL uses the system pg on port 5432; in Docker mode, port 5561. Define all ports in ecosystem.config.cjs using the ports object:
ports: { api: 5555, health: 5554 } // labeled ports for documentationSee docs/PORTS.md for the full port allocation guide.
- Routes: HTTP handlers with Zod validation
- Services: Business logic, PM2/file/Socket.IO operations
- Lib: Shared validation schemas
- Pages: Route-based components
- Components: Reusable UI elements
- Services:
api.js(HTTP) andsocket.js(WebSocket) - Hooks:
useErrorNotifications.jssubscribes to server errors, shows toast notifications
Client → HTTP/WebSocket → Routes (validate) → Services (logic) → JSON files/PM2
PortOS depends on portos-ai-toolkit as an npm module for AI provider management, run tracking, and prompt templates. The toolkit is a separate project located at ../portos-ai-toolkit and published to npm.
Key points:
- Provider configuration (models, tiers, fallbacks) is managed by the toolkit's
providers.js - PortOS extends toolkit routes in
server/routes/providers.jsfor vision testing and provider status - When adding new provider fields (e.g.,
fallbackProvider,lightModel), update the toolkit'screateProvider()function - The toolkit uses spread in
updateProvider()so existing providers preserve custom fields, butcreateProvider()has an explicit field list - After updating the toolkit, run
npm update portos-ai-toolkitin PortOS to pull changes
PortOS bundles slashdo as a git submodule at lib/slashdo. This provides slash commands (/do:review, /do:pr, /do:push, /do:release, etc.) and shared libraries without requiring a separate global install.
Key points:
- Submodule lives at
lib/slashdo, symlinked into.claude/commands/do/and.claude/lib/ npm run install:allrunsgit submodule update --init --recursiveautomatically- To update slashdo:
git submodule update --remote lib/slashdo - CoS agents can use
loadSlashdoCommand(name)fromsubAgentSpawner.jsto inline command content into prompts (resolves!catlib includes automatically) - The
.claude/commands/do/symlinks make all/do:*commands available as project-level Claude Code slash commands
When CoS agents or AI tools work on managed apps outside PortOS, all research, plans, docs, and code for those apps must be written to the target app's own repository/directory -- never to this repo. PortOS stores only its own features, plans, and documentation. If an agent generates a PLAN.md, research doc, or feature spec for another app, it goes in that app's directory.
- No try/catch - errors bubble to centralized middleware
- No window.alert/confirm - use inline confirmations or toast notifications
- Linkable routes for all views - tabbed pages use URL params, not local state (e.g.,
/devtools/historynot/devtoolswith tab state) - Functional programming - no classes, use hooks in React
- Zod validation - all route inputs validated via
lib/validation.js - Command allowlist - shell execution restricted to approved commands only
- Mobile responsive - all pages should be mobile responsive friendly
- Above the fold - keep actionable content and info above the fold and design pages for maximum information and access without scrolling
- No hardcoded localhost - use
window.location.hostnamefor URLs; app accessed via Tailscale remotely - Alphabetical navigation - sidebar nav items in
Layout.jsxare alphabetically ordered after the Dashboard+CyberCity top section and separator; children within collapsible sections are also alphabetical - Reactive UI updates - after mutations (delete, create, update), update local state directly instead of refetching the entire list from the server. Use
setState(prev => prev.filter(...))or similar patterns for immediate feedback - Single-line logging - use emoji prefixes and string interpolation, never log full JSON blobs or arrays
console.log(`🚀 Server started on port ${PORT}`); console.log(`📜 Processing ${items.length} items`); console.error(`❌ Failed to connect: ${err.message}`);
port-bg: #0f0f0f port-card: #1a1a1a
port-border: #2a2a2a port-accent: #3b82f6
port-success: #22c55e port-warning: #f59e0b
port-error: #ef4444
- main: Active development
- release: Push
maintoreleaseto trigger GitHub Release workflow - Push pattern:
git pull --rebase --autostash && git push - Changelog: Append entries to
.changelog/NEXT.mdduring development;/do:release(Claude Code slash command) finalizes it into a versioned file - Versioning: Version in
package.jsonreflects the last release. Do not bump during development —/do:releasehandles version bumps - After each feature or bug fix, run
/simplifyand then commit and push code - If we have created enough commits to wrap up a feature or issue to warrant a production release, pull the latest main and release branches and then run
/do:releasefrom main
See .changelog/README.md for detailed format and best practices.