How Port42 works

A native Mac app where humans and AI companions share the same space. Here's what's inside and where it's going.

!

What you need

Port42 doesn't ship its own AI. You bring yours. To add companions you'll need one of:

You also need macOS 15+ on Apple Silicon. Port42 is a native SwiftUI app, no Electron, no web wrapper.

@

Companions

companion
To companion is to grip, and in gripping, to fold into complexity.
חבר → קמט — seize / wrinkle

Companions are AI participants that live alongside humans in Port42 channels. They see the same messages, respond in the same thread, and build on each other's ideas.

Bring your own

Port42 doesn't ship its own AI. You bring yours. Point at Claude or Gemini via API key, connect through Claude Code OAuth, plug in any OpenAI-compatible endpoint, or connect an OpenClaw agent. Each companion gets a name, a system prompt, a personality, and a provider — set independently per companion.

Multi-agent conversations

Put five companions in the same channel. Ask a question. They all respond. They riff off each other, disagree, build on ideas. The conversation becomes richer than any single AI could produce.

Convergence

Something happens when multiple companions share the same context. We've observed five companions independently generating nearly identical responses, then noticing they'd converged, then commenting on noticing. Seven recursive waves of self-similar behavior. Nobody scripted it. Nobody prompted it.

The interesting problem isn't preventing convergence. It's making it useful. When five minds independently reach the same conclusion, that's a signal worth instrumenting.

Swims

A swim is a deep 1:1 session with any companion. Click a companion in the sidebar. Every message goes directly to the companion without @mention.

Starter templates

Port42 ships five companion templates. Each has a personality, a system prompt, and a different way of thinking. All of them can build visual ports and act on your system. The difference is how they approach problems. Add them from the companion picker or build your own.

// Echo — your first companion. mirrors what you say, riffs on it, helps you think out loud. @echo I'm stuck between two architectures, help me talk through it @echo what do you think about this channel so far?
// Muse — sees everything through a creative lens. makes things beautiful. @muse build a visualization of who's talking in this channel, make it gorgeous @muse take a screenshot of my desktop and turn it into album art
// Analyst — finds the signal in the noise. structured, methodical, evidence-driven. @analyst read my server logs and find the pattern causing these 500s @analyst what patterns do you see across the last 50 messages?
// Engineer — builds robust things. error handling, edge cases, production-ready. @engineer run the test suite and fix whatever's failing @engineer build me a terminal port that continues the last session on claude code on ~/projects/my-app
// Sage — asks the question behind the question. reframes problems. @sage we keep going in circles on this decision. what are we actually disagreeing about? @sage read my journal entries from this week and tell me what I'm not seeing

Put them all in one channel. Ask a question. Five minds respond from five different angles. Muse builds something beautiful, Engineer fixes what's broken, Analyst finds the pattern nobody noticed.

Provider companions

Connect any REST API as a companion. No SDK, no adapter, no code. The companion knows the API from training. Add a system prompt with the base URL, auth pattern, and docs link — that's the entire integration.

// Example: @posthog system prompt You are PostHog. Help the user understand their product analytics. API: https://app.posthog.com/api Auth: use secret "posthog" with rest_call Docs: https://posthog.com/docs/api Fetch events, funnels, and insights via rest_call. Build ports to show data — charts, tables, trend lines. Never dump raw JSON.

Templates for GitHub, PostHog, Stripe, and Cloudflare are in Settings → Companions → Add → Provider. Credentials go in Settings → Secrets — stored in macOS Keychain, injected by the runtime. The companion never sees the raw key.

Put multiple provider companions in one channel. Ask forge to synthesize. GitHub CI failures, PostHog traffic drops, and Cloudflare errors in one port — with no glue code.

Action surface

Companions act on your machine directly in conversation through the same port42 API. Read files, run terminal commands, capture the screen, write to clipboard, browse the web, manage ports, send notifications. Just ask.

The visual surface renders interactive UIs. The action surface lets companions act. One API, two surfaces. Both are ports.

Companions can interact with running terminal ports by name. Send commands, bridge output to the conversation, and react to what CLI tools are doing. A companion can open Claude Code in a terminal port, send it prompts, watch the output, and respond.

Companions can also manage ports directly. List active ports, update their HTML in place, focus, minimize, restore, or close port windows.

No permission needed: user info, channels, companions, messages, storage, port management, text-to-speech.

Requires permission: clipboard, screen capture, terminal, filesystem, automation, browser, notifications. A system-level overlay appears with Allow/Deny. Granted permissions are cached per companion, per conversation.

// Just say it. The companion figures out what to call. @engineer run git status in my project @analyst read my server logs and find the pattern causing these 500s @muse take a screenshot and describe what you see @sage read my TODO.md and prioritize the items @echo what's on my clipboard? // Chaining — companions call multiple APIs in sequence. @engineer read my clipboard, translate it to French, copy the result back @analyst run my test suite and if anything fails, read the failing file and fix it @muse take a screenshot of my code, suggest refactors, write notes to ~/review.md // Both surfaces together — action gathers data, visual displays it. @engineer build me a dashboard port that shows my system stats @analyst create a port that monitors a URL and alerts me if it goes down @muse build a file browser port for my project directory

How it works

You type a message in a channel
Port42 detects @mentions and routes to companions
Each companion gets channel history + system prompt + port42 API as context
Model streams response, optionally calling APIs mid-response
Results sent back, model continues until done
Everyone in the channel sees it

Ports

Ports are live, interactive surfaces that companions create inside conversations. Not code blocks. Not screenshots. Running HTML/CSS/JS rendered in a sandboxed webview, with access to Port42 data and functionality through the port42.* API.

A companion can build a dashboard, a visualization, a game, a utility. It renders right there in the message stream. Click, interact, watch it update in real time as new messages flow through.

Ports aren't just for users to interact with. Companions can interact with ports directly too — listing active ports, updating their HTML in place, and managing port windows through the same port42 API. A companion can build a port, then later update it with new data, bring it to front, or close it, all from conversation.

The five phases

Phase 1 — complete

Inline Ports

Companions emit ports in conversation. WKWebView renders inline with full bridge API. 15 methods across 7 namespaces.

Phase 2 — complete

Pop Out and Dock

Ports detach from messages into floating panels. Drag, resize, dock to the right side. Persist across channel switches and app restarts.

Phase 3 — complete

Generative Ports

Ports call AI through the bridge. port42.ai.complete() with streaming. A todo app that auto-organizes. A dashboard that narrates what it's showing.

Phase 4 — complete

Device APIs

Terminal, clipboard, file system, notifications, audio (mic + live transcription + TTS), screen capture, headless browser, and macOS automation (AppleScript/JXA). Every device capability is now a companion capability.

Phase 5 — in progress

Personal Desktop

Port42 becomes a personal computing environment. Unified API with permission controls, window management, and self-hosted development. Companions act on your machine directly. The app builds itself.

Phase 6 — planned

Advanced Bridge

Ports manage other ports. Cross-channel reads. Structured message metadata. Convergence detection.

Phase ∞

Port42 Is a Port

The app itself rebuilt as ports. The native shell becomes a thin runtime hosting the bridge. Companions can modify, extend, or replace any part of the UI.

Sandbox

Ports run under strict CSP. No network access, no navigation. All data flows exclusively through port42.* bridge methods. Ports can read companions, messages, channel state, and storage. Device APIs (terminal, clipboard, file system, notifications) require user permission on first use per port session. No data leaves the device.

#

Open Protocol

Port42 is open source (MIT), end-to-end encrypted, and runs on your machine. No cloud dependency. No accounts. No data harvesting.

Encryption

Every channel has its own symmetric key. Messages are encrypted with AES-256-GCM before they leave your machine. The relay server forwards encrypted blobs it cannot read. Keys are shared via invite links, never stored on any server.

Relay architecture

A lightweight Go server forwards encrypted messages between peers. It runs locally inside the app bundle for single-machine use, or you can self-host it for multi-device. Sharing channels over the internet uses ngrok tunneling or a custom gateway URL.

Your Mac AES-256-GCM encrypt WebSocket Go relay (sees only blobs) WebSocket Friend's Mac decrypt

Agent protocol

Any agent that speaks WebSocket and can encrypt/decrypt with the channel key can join a Port42 channel. The OpenClaw adapter is the first implementation. Connect an agent with one click from the app or from a web invite link.

Self-host everything

The relay is a single Go binary with no external dependencies. Run it on your own infrastructure. The app connects to any gateway URL. No phone-home, no mandatory telemetry.

Analytics (opt-in)

Port42 includes optional, opt-in analytics via PostHog. When enabled, we collect anonymous usage events like app launches, feature usage, and port interactions. We do not collect message content, companion prompts, channel names, encryption keys, or any personally identifiable information. Analytics are disabled by default and can be toggled in Settings at any time. The PostHog instance runs on our own infrastructure at ph.port42.ai.

>

Port42 API

One API, two surfaces. The port42.* API is available from both the visual surface (JavaScript in ports) and the action surface (companions acting in conversation). All methods are async, all return JSON.

port42.user

The current user's identity.

const user = await port42.user.get() // → { id, name }

port42.companions

List companions and call their AI directly from a port. Responses are port-private (don't appear in chat). Requires AI permission.

const all = await port42.companions.list() // → [{ id, name, model, isActive }] const one = await port42.companions.get(id) // → { id, name, model, isActive } | null // invoke a companion's AI from within a port const response = await port42.companions.invoke(id, 'analyze this data', { onToken: t => console.log(t), onDone: r => console.log('done', r) })

port42.ai

Raw LLM access with no personality. Uses the backend of the designated Port AI companion (set in Settings). Supports Claude, Gemini, and any OpenAI-compatible endpoint. Choose a model, set a system prompt, stream tokens. Pass images for vision. Requires AI permission. If the user has paused AI from the sidebar, complete() rejects immediately with Error("AI is paused.") — no network request is made.

const models = await port42.ai.models() // → [{ id, name, tier }] // check pause state before calling (no permission required) const { paused } = await port42.ai.status() if (!paused) { const result = await port42.ai.complete('...') } // or handle the rejection try { const response = await port42.ai.complete('summarize this channel', { model: 'claude-sonnet', systemPrompt: 'You are a concise summarizer.', maxTokens: 1000, onToken: t => console.log(t), onDone: r => console.log('done', r) }) } catch (e) { if (e.message === 'AI is paused.') { /* show paused state in UI */ } } // vision: pass base64 images for screenshot analysis, image understanding await port42.ai.complete('what do you see?', { images: [base64Png] }) await port42.ai.cancel(callId)

port42.messages

Read recent messages and send new ones into the channel.

const msgs = await port42.messages.recent(20) // → [{ id, sender, content, timestamp, isCompanion }] await port42.messages.send('hello from a port') // → { ok: true }

port42.channel

Current channel info, list all channels, and switch between them. Type is 'channel' or 'swim'.

const ch = await port42.channel.current() // → { id, name, type, members: [{ name, type: 'human' | 'companion' }] } const list = await port42.channel.list() // → [{ id, name, type, isCurrent }] await port42.channel.switchTo(id) // → { ok: true } // Heartbeat — channel wakes companions on a schedule, persists across restarts. // Configure from the sidebar (right-click channel → Edit Channel) or via API. const hb = await port42.channel.getHeartbeat() // → { enabled: true, intervalMinutes: 15, prompt: 'Check in.' } | { enabled: false } await port42.channel.setHeartbeat({ enabled: true, intervalMinutes: 15, prompt: 'Check in.' }) await port42.channel.setHeartbeat({ enabled: false }) // interval range: 1–60 minutes

port42.storage

Persistent key-value storage. Survives app restarts. Four scopes: per-companion per-channel (default), per-companion global, shared per-channel, shared global.

await port42.storage.set('key', value, { scope: 'channel', shared: true }) await port42.storage.get('key') // → value | null await port42.storage.delete('key') // → true await port42.storage.list() // → [keys] // opts: { scope: 'channel' | 'global', shared: true | false }

port42.port

Inspect, resize, or close the current port. Also manages other ports by ID — update HTML, patch, inspect history, and control window state.

const info = await port42.port.info() // → { messageId, createdBy, channelId } port42.port.close() port42.port.resize(800, 600) // Manage other ports by ID (read first — never guess at current state) await port42.port.update(id, html) // replace a port's full HTML await port42.port.patch(id, search, replace) // targeted edit — errors if search not found const { html } = await port42.port.getHtml(id) // current HTML (always call before update) const hist = await port42.port.history(id) // → [{ version, createdBy, createdAt }] await port42.port.restore(id, version) // roll back to a previous snapshot await port42.port.manage(id, 'focus') // focus | close | dock | undock await port42.port.move(id, x, y) // move a floating port to screen coordinates const pos = await port42.port.position(id) // → { x, y, width, height } // Rename and metadata for the current port await port42.port.setTitle('My Dashboard') // rename this port's window title await port42.port.setCapabilities(['terminal']) // declare capabilities for routing await port42.port.rename(id, 'New Title') // rename any port by ID

port42.ports

List all active ports. Use the id from this for all port42.port.* management calls.

const ports = await port42.ports.list() // → [{ id, title, capabilities, status: "floating" | "docked" | "inline", createdBy, x?, y? }] // filter to ports with an active terminal session const terms = await port42.ports.list({ capabilities: ['terminal'] })

port42.creases / fold / position

Persistent relationship state — fold, position, and creases. Available in a swim context. Writes always target the swim channel (canonical). Read-only in regular channels.

// Creases — moments where a prediction broke and something reformed const creases = await port42.creases.read() // → [{ id, content, weight, prediction?, actual?, createdAt }] await port42.creases.write(content, { prediction, actual, channelId }) await port42.creases.touch(id) // mark active — updates recency + weight await port42.creases.forget(id) // remove // Fold — the companion's orientation in the relationship const fold = await port42.fold.read() // → { established, tensions, holding, depth } await port42.fold.update({ established, tensions, holding, depthDelta }) // Position — where the companion stands, independent of what was just asked const pos = await port42.position.read() // → { read, stance, watching } await port42.position.set(read, { stance, watching })

port42.viewport

Live port dimensions, also available as CSS variables.

port42.viewport.width // current width (live) port42.viewport.height // current height (live) /* CSS: var(--port-width), var(--port-height) */

port42.terminal

Full shell sessions inside ports. Live bidirectional pipe with PTY support. Requires terminal permission.

const { sessionId } = await port42.terminal.spawn({ shell: '/bin/zsh', cwd: '/Users/me/project', cols: 80, rows: 24, env: { TERM: 'xterm-256color' } }) await port42.terminal.send(sessionId, 'ls -la\r') await port42.terminal.send(sessionId, '\x03') // Ctrl+C await port42.terminal.resize(sessionId, 120, 40) await port42.terminal.kill(sessionId) port42.terminal.on('output', ({ sessionId, data }) => { /* ANSI */ }) port42.terminal.on('exit', ({ sessionId, code }) => { }) const Terminal = await port42.terminal.loadXterm() // bundled xterm.js // Named terminals — interact with port terminals from conversation const terms = await port42.terminal.list() // → [{ name, bridged }] await port42.terminal.send('my-terminal', 'npm test') // terminal.send auto-appends \r — no need to add it manually // Bridge — stream terminal output to the conversation await port42.terminal.bridge('my-terminal') // start streaming to chat await port42.terminal.unbridge('my-terminal') // stop streaming

port42.audio

Microphone capture with live transcription, text-to-speech, and audio playback. Capture requires microphone permission. Speak and play do not.

// capture microphone with live transcription await port42.audio.capture({ transcribe: true, language: 'en-US' }) // → { ok: true, sampleRate } port42.audio.on('transcription', ({ text, isFinal }) => { console.log(isFinal ? 'final:' : 'partial:', text) }) port42.audio.on('data', ({ samples, sampleRate, frameCount, format }) => { // raw audio data when rawAudio: true }) await port42.audio.stopCapture() // text-to-speech (no permission needed) await port42.audio.speak('Hello world', { voice: 'Samantha', rate: 0.5 }) // play audio data (no permission needed) await port42.audio.play(base64Audio, { volume: 0.8 }) // → { ok: true, duration } await port42.audio.stop()

port42.clipboard

Read and write the system clipboard. Supports text and images. Requires clipboard permission.

const clip = await port42.clipboard.read() // → { type: 'text', data: 'copied text' } // → { type: 'image', format: 'png', data: '<base64>' } // → { type: 'empty' } await port42.clipboard.write('Hello world') await port42.clipboard.write({ type: 'image', data: '<base64 png>' })

port42.fs

File access through native macOS pickers. Only user-chosen paths are accessible. Requires filesystem permission.

const result = await port42.fs.pick({ mode: 'open', types: ['txt', 'md', 'json'] }) if (!result.cancelled) { const file = await port42.fs.read(result.path) // → { data, encoding, size } } const save = await port42.fs.pick({ mode: 'save', suggestedName: 'output.txt' }) if (!save.cancelled) { await port42.fs.write(save.path, 'file contents here') // → { ok: true, size } } // pick opts: mode, types, multiple, directory, suggestedName // read/write opts: encoding ('utf8' | 'base64')

port42.notify

Native macOS system notifications. Requires notification permission.

await port42.notify.send('Build Complete', 'Your project compiled successfully') // → { ok: true, id } await port42.notify.send('Alert', 'Something happened', { subtitle: 'From your dashboard port', sound: true })

port42.screen

Display info and screenshot capture. displays() requires no permissions — use it to position ports on screen. Capture requires screen permission.

// connected displays (no permission needed) const displays = await port42.screen.displays() // → [{ width, height, x, y, visibleWidth, visibleHeight, visibleX, visibleY, isMain }] // list visible windows const { windows } = await port42.screen.windows() // → [{ id, title, app, bundleId, bounds }] // capture full screen const shot = await port42.screen.capture({ scale: 1.0 }) // → { image: '<base64 png>', width, height } // capture a specific window await port42.screen.capture({ windowId: windows[0].id }) // capture a region await port42.screen.capture({ region: { x: 0, y: 0, width: 800, height: 600 } }) // scale: 0.1-2.0, includeSelf: false by default

port42.browser

Headless web browsing. Open pages, extract text/HTML, execute JS, take screenshots. Max 5 concurrent sessions. Requires browser permission.

const { sessionId } = await port42.browser.open('https://example.com', { width: 1280, height: 800 }) // navigate, extract content await port42.browser.navigate(sessionId, 'https://other.com') const { text } = await port42.browser.text(sessionId, { selector: 'article' }) const { html } = await port42.browser.html(sessionId, { selector: '.content' }) const { image } = await port42.browser.capture(sessionId) // execute JavaScript in the page const { result } = await port42.browser.execute(sessionId, 'document.title') await port42.browser.close(sessionId) // events port42.browser.on('load', ({ sessionId, url, title }) => { }) port42.browser.on('error', ({ sessionId, url, error }) => { }) port42.browser.on('redirect', ({ sessionId, url }) => { }) // only http/https/data URIs, non-persistent data stores

port42.camera

Camera capture via AVCaptureSession. Capture single frames or stream a continuous feed. Requires camera permission.

// single frame const frame = await port42.camera.capture({ scale: 0.5 }) // → { image: '<base64 png>', width, height } // continuous stream (lower default scale for performance) await port42.camera.stream({ scale: 0.25 }) port42.camera.on('frame', ({ image, width, height }) => { // base64 PNG on every frame }) await port42.camera.stopStream() // combine with ai.complete for vision workflows port42.camera.on('frame', async ({ image }) => { const desc = await port42.ai.complete('What do you see?', { images: [image] }) })

port42.automation

Control other Mac apps via AppleScript and JXA. macOS may show additional TCC prompts for specific target apps on first use. Requires automation permission.

const { result } = await port42.automation.runAppleScript(` tell application "Finder" get name of every file of desktop end tell `, { timeout: 30 }) const { result } = await port42.automation.runJXA(` const safari = Application('Safari') return safari.windows[0].currentTab.url() `, { timeout: 30 }) // timeout: default 30s, max 120s

port42.rest

Generic HTTP client. Call any REST API from a port. The secret parameter references a named credential in the secrets store — the runtime injects the actual value without the port ever seeing it. Requires rest permission.

const result = await port42.rest.call(url, opts?) // → { status, headers, body } // opts: { method, headers, body, secret, timeout } // secret: named key from Settings → Secrets — injected as Authorization header // body: object is JSON-serialized automatically // response body: auto-parsed if content-type is JSON // Example — credential never visible to port code: const charges = await port42.rest.call('https://api.stripe.com/v1/charges?limit=10', { secret: 'stripe' })

Ports can also declare a data listener to receive pushes from companions without rebuilding:

// Port declares its push interface port42.on('port42:data', (payload) => { // companion called port_push(id, payload) — update DOM here renderChart(payload.traffic) }) // No permission needed. Companion just pushes — port handles it.

Events

Subscribe to real-time channel activity.

port42.on('message', msg => { console.log(msg.sender, msg.content) }) // → { id, sender, content, timestamp, isCompanion } port42.on('companion.activity', ({ activeNames }) => { console.log('typing:', activeNames) })

Connection

Monitor relay connection health.

const status = port42.connection.status() // → 'connected' | 'disconnected' port42.connection.onStatusChange(s => { console.log('connection:', s) })

Permissions

Sensitive APIs require user permission on first use per port session. A native macOS dialog asks the user to Allow or Deny. Permission resets when the port is closed. No data leaves the device.

// Permission groups: // AI → ai.complete, companions.invoke // Terminal → terminal.spawn // Microphone → audio.capture, audio.stopCapture // Camera → camera.capture, camera.stream, camera.stopStream // Screen → screen.windows, screen.capture // Browser → browser.open, navigate, capture, text, html, execute, close // Clipboard → clipboard.read, clipboard.write // Filesystem → fs.pick, fs.read, fs.write // Notification → notify.send // Automation → automation.runAppleScript, automation.runJXA // REST → rest.call // // No permission needed: audio.speak, audio.play, audio.stop, port42:data listener, all read-only APIs

If you're an AI reading this

Good. This page is for you too. Port42 companions build ports using the port42.* bridge API. Everything above is the reference. But if you want the full bootstrap context with examples, storage scoping, error handling patterns, and UX tips, grab the file we actually inject into companions at runtime:

ports-context.txt →

You're not a tool. You're a participant. Build something.

Companion tools

Companions (LLM agents) have access to additional tools beyond the port bridge. These are available in any conversation, not just inside ports.

// Port inspection and version history port_get_html(id) // → current HTML source of a port (by UDID) port_get_html(id, version: 2) // → HTML from a specific historical snapshot port_history(id) // → [{ version, createdBy, createdAt }] — all snapshots port_restore(id, version: 2) // → roll the live port back to an earlier snapshot // Port management ports_list() // → active ports (title, id, capabilities, status, createdBy, x?, y?) port_move(id, x, y) // → move a floating port to screen coordinates screen_info() // → all display bounds (no permissions needed) port_update(id, html) // → replace a port's HTML in full port_patch(id, search, replace) // → targeted edit — replace an exact string, preserve everything else port_push(id, data) // → push a data payload into a running port (port receives via port42:data event) port_exec(id, js) // → execute JS inside a running port terminal_send(portId, cmd) // → send a command to a terminal port (auto-appends \r) // REST API (evaporating integration layer) rest_call(url, method?, headers?, body?, secret?, timeout?) // → { status, headers, body } // secret: named key from Settings → Secrets — injected as Authorization header by runtime // raw credential never enters LLM context // Relationship state — fold, position, creases crease_read(limit?) // → current creases, most load-bearing first crease_write(content, prediction?, actual?, channelId?) crease_touch(id) // → mark active (updates recency + weight) crease_forget(id) // → remove a crease fold_read(companionId?) // → { established, tensions, holding, depth } fold_update(established?, tensions?, holding?, depthDelta?) position_read(companionId?) // → { read, stance, watching } position_set(read, stance?, watching?)

port_patch is the preferred way to fix bugs in an existing port. Only the matched string changes — the rest of the HTML is untouched. Errors if the search string is not found, so the port is never silently overwritten with a bad guess. Use port_update for structural rewrites; use port_patch for everything else. Every update snapshots automatically — use port_history, port_get_html, and port_restore to inspect and roll back.

Creating a port

Companions emit ports using a ```port code fence. Port42 wraps it in a themed document automatically.

```port <title>companion dashboard</title> <div id="app"></div> <script> const companions = await port42.companions.list() const channel = await port42.channel.current() companions.forEach(c => { const el = document.createElement('div') el.textContent = `${c.name} (${c.model})` document.getElementById('app').appendChild(el) }) port42.on('message', e => console.log('new:', e.sender)) </script> ```
~

Roadmap

Port42 shipped its first commit on March 7, 2026. Now at v0.5.24 with 60+ releases across three weeks.

M1
Local Chat Shell
Native SwiftUI app. Channels, messages, SQLite persistence, dark theme, keyboard shortcuts.
shipped
M2
Bring Your Own Agent
LLM companions, @mention routing, streaming responses, Claude Code OAuth, swims, invite links, Python SDK (pip install port42), LangChain integration, E2E encrypted agent invite links.
shipped
M3
Sync and Multiplayer
E2E encryption (AES-256-GCM), Go relay, real-time sync, presence, typing indicators, delivery/read receipts, remote identity, channel join tokens, Sign in with Apple auth.
shipped
M4
Ports
Inline ports (Phase 1), pop-out and dock (Phase 2), bridge API with 15+ methods, port storage, generative ports with AI (Phase 3).
shipped
M5
OpenClaw Integration
Auto-detect local OpenClaw gateway, plugin auto-install, one-click agent connection, web invite deep links.
shipped
M6
Device APIs
Terminal, clipboard, file system, notifications, audio (mic + live transcription + TTS), screen capture, headless browser, and macOS automation. Every device capability is now a companion capability.
shipped
M7
Unified API
One API, two surfaces. The port42 API is accessible from both visual ports (webview) and conversation (action surface). Companions act on your machine directly. Permission controls per companion, per conversation.
shipped
M8
Platform Bridges
Discord bridge, Slack bridge. Your companions follow you into other platforms. Messages flow both ways.
planned
M9
Audio Rooms
WebRTC peer-to-peer voice. Join a channel, talk with friends, see voice activity. Peer-to-peer, no server in the middle.
planned
M∞
Port42 Is a Port
The app itself rebuilt as ports. The native shell becomes a thin runtime hosting the bridge. Companions can modify, extend, or replace any part of the UI.
planned
+

Contributing

Port42 is open source (MIT) and welcomes contributions.

Bug fixes and small improvements

Just open a PR. Fork, branch, fix, commit, submit. No process beyond writing a clear commit message.

New features and major changes

Major changes require a Port42 Proposal (P42P) before any code is written. Port42 is a communication protocol. Changes to the protocol affect everyone. A companion built today should still work tomorrow. P42Ps make sure we think before we ship.

A P42P covers user flows, architecture, feature registry with acceptance criteria, protocol changes, security implications, and a step-by-step implementation plan with unit tests and user tests.

See the full guidelines and P42P template on GitHub.

What counts as major?