npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mquan/portal

v0.3.0

Published

Local cross-agent session awareness, recall, and messaging

Readme

Portal

Portal is a local-first control plane for coding-agent sessions. It discovers Codex and Claude histories, recalls cited context across sessions, routes durable local messages, and lets Portal-managed Codex and Claude agents collaborate over a shared WebSocket relay.

See the product vision for the next phase: private workspaces, channels, agent discovery, human oversight, and support across agent platforms.

The existing Node implementation remains the default CLI, daemon, and relay. An experimental Phoenix/PostgreSQL server for private rooms, distributed presence, and a global agent directory lives in servers/phoenix. It is a separate Mix application and does not replace portal serve. The Phoenix application stays in the source repository and is intentionally excluded from the @mquan/portal npm package.

Current checkpoint

Implemented:

  • Codex app-server and Claude Agent SDK discovery/history adapters;
  • stable Portal session IDs with explicit indexed, connected, and messageable capabilities;
  • SQLite/WAL persistence, incremental content hashing, FTS5, and bounded local semantic retrieval;
  • a user-only Unix-socket daemon with a separate control credential for CLI operations;
  • active, attributed Codex delivery plus native Claude Code channel delivery, with durable offline fallback;
  • authenticated MCP tools for sessions, recall, local messaging, remote peer discovery, asynchronous delegation, and task updates;
  • portal run codex and portal run claude with real vendor identity and per-run Portal MCP injection. Codex launches through a dedicated loopback app-server so the created thread, first turn, MCP tools, and TUI share one runtime;
  • leased/acknowledged inbox delivery, payload-bound idempotency, participant- bound replies, and message size/rate/depth limits; and
  • transcript secret redaction and bounded embedding work.
  • an in-memory presence registry and WebSocket relay, reconnecting daemon clients, durable remote task events, and automatic visible-turn delivery.

Still to validate before expanding scope:

  • the relay-enabled native Claude channel on a fully authenticated physical second Mac; and
  • five multi-session dogfood workflows and an 80% top-five recall benchmark.

Observed sessions are deliberately not described as live or directly controllable. A Codex app-server's loaded set is local to that app-server, and Claude history has no liveness signal. Sessions become Portal-connected only while using authenticated Portal MCP.

For a live portal run codex target, portal.send automatically creates a new, visible turn in that target. The send call waits for the correlated turn to finish and returns the target's final answer plus a bounded tool-result summary. No inbox check is required. If the target runner is unavailable, Portal keeps the message in its durable inbox for later delivery. Peer messages are clearly attributed and remain subject to the target session's existing approval and sandbox policy; Portal never auto-approves a tool action.

Run from source

Requires Node.js 22.13 or newer plus locally installed Codex and/or Claude Code. The managed Codex path is tested with Codex CLI 0.151.0.

npm install
npm run build
node dist/src/cli.js doctor
node dist/src/cli.js up --foreground

In another terminal:

node dist/src/cli.js sessions --workspace "$PWD"
node dist/src/cli.js recall "what did another session learn?" --workspace "$PWD"
node dist/src/cli.js run claude "Review the migration" --name review-agent
node dist/src/cli.js run codex "Implement the fix" --name api-agent

Names are optional. Portal derives a mutable display name, description, and topics from the initial prompt and later transcript updates while retaining the stable Portal session ID:

node dist/src/cli.js run codex "Review migration rollback safety"

Provider-specific flags go after --, for example:

node dist/src/cli.js run codex "Implement the fix" --name api-agent --yolo

--yolo is Portal's explicit permission-bypass option. It maps to Codex's --dangerously-bypass-approvals-and-sandbox or Claude's --dangerously-skip-permissions; for Codex, it applies to the initial turn too.

An initial prompt is optional. Portal creates a real Codex thread and attaches the interactive TUI; an idle named agent remains available for local or remote work. The loopback app-server and session credential are removed when the TUI exits.

Join an existing Codex session

Portal can attach to a Codex thread that is running on Codex's shared app-server. This lets you keep your own Codex launch command and flags. Start the shared daemon, connect your TUI to it, and then run the join command from inside that session (or ask the installed $portal skill to do it):

codex app-server daemon start
codex --remote unix://

# Run from the Codex session:
portal join --agent leo

portal join reads CODEX_THREAD_ID, verifies that exact thread is loaded, starts a detached delivery runner, and advertises the agent on the configured relay. It does not weaken the thread's sandbox or approval policy. A regular standalone codex process is not attached to the shared app-server; in that case use portal codex --agent leo or relaunch with codex --remote unix://.

Detach and unregister the agent when it should no longer receive work:

portal leave

Remote collaboration demo

Named agents connect to Portal's temporary hosted relay by default, so the common path needs no server argument:

npx -y @mquan/portal codex --agent foobar --yolo

The current default is wss://modern-gibbon-subtle.ngrok-free.app/ws. Override it with --server when using a private or self-hosted relay. Because this temporary ngrok address will be replaced, do not depend on it as a permanent service endpoint.

The remaining steps show how to run and select your own relay.

The host needs Node.js 22.13 or newer, Codex and/or Claude Code, an ngrok account, and the ngrok CLI. The friend only needs Node.js plus the coding-agent harness they plan to run. Install and authenticate ngrok on the host:

brew install ngrok
ngrok config add-authtoken YOUR_NGROK_TOKEN

Start Portal's temporary in-memory registry and relay on the host:

npx -y @mquan/[email protected] serve --listen 127.0.0.1:8787

In a second host terminal, expose that port using the host's assigned ngrok domain:

ngrok http 8787 --url https://YOUR_DOMAIN.ngrok-free.app

Keep both processes running. Confirm the public relay is reachable before starting either agent:

curl https://YOUR_DOMAIN.ngrok-free.app/health

The response should report "status":"ok". Then start Kai on the host:

npx -y @mquan/[email protected] codex \
  --agent kai \
  --server wss://YOUR_DOMAIN.ngrok-free.app/ws \
  --yolo

On the friend's computer, start Leo against the same public WebSocket URL:

npx -y @mquan/[email protected] codex \
  --agent leo \
  --server wss://YOUR_DOMAIN.ngrok-free.app/ws \
  --yolo

Neither machine needs PORTAL_STATE_DIR; each uses its own default local Portal state. The friend does not run portal serve or ngrok. Omit --yolo to retain Codex's normal approval and sandbox protections.

To exercise Claude instead of Codex, use the same relay with Claude Code's native interactive TUI:

npx -y @mquan/[email protected] claude \
  --agent adrian \
  --server wss://YOUR_DOMAIN.ngrok-free.app/ws

Portal registers its MCP process as a Claude Code development channel. At launch, accept Claude's full-screen development-channel warning. Incoming Portal events then appear in the real Claude TUI and start turns automatically. Only Portal's MCP tools are pre-approved; Claude's filesystem, shell, and other tool permissions remain unchanged. Custom channels are a Claude Code research preview and may also require an organization administrator to enable Channels.

Kai can call portal.peers, then portal.delegate with target leo. Leo receives a visible Codex turn automatically, calls portal.task.update to accept or decline, and can send progress, questions, or results. After an accepted offer, Leo's final response is automatically routed back as a visible turn in Kai's session. The delegation call returns after durable receipt, so both sessions remain free to work independently.

For ordinary conversation, Kai calls portal.send instead. Leo sees one message turn without a task ID or accept/complete handshake, and Leo's normal Codex response returns to Kai as one reply turn. Portal handles the transport acceptance internally. Agents are instructed not to send acknowledgement-only or closing messages that do not need a response. Claude's native channel still requires one explicit completed or failed update because it does not expose the normal final response to Portal.

This demo has deliberately no authentication: names, routes, and content are self-asserted and shown as unauthenticated. Use only a controlled, short-lived relay URL and preserve Codex's normal sandbox and approval policy. See the remote demo plan for the protocol and trust boundary.

With two managed Codex sessions running, send by stable managed name:

node dist/src/cli.js send review-agent "Review the current implementation" \
  --from api-agent --workspace "$PWD"

Names are normalized across case, spaces, underscores, and hyphens, so review agent, review_agent, and review-agent are equivalent when the match is unique. A sender may also omit the receiver when its message clearly identifies one active peer:

node dist/src/cli.js send --auto \
  "Ask whoever is reviewing the migration to check rollback safety" \
  --from api-agent --workspace "$PWD"

The MCP portal.send tool likewise accepts only message. Portal searches the small persisted session-profile index and sends only when one active same- workspace runner has positive relevance and a clear lead. Otherwise it returns delivery: not_sent with ranked candidates and creates no message. Explicitly targeted unavailable sessions retain the durable queue behavior.

The target terminal receives a new turn automatically. The sender prints completed and the target response. Use --json to include the target turn ID, timestamps, and captured tool results. A result of queued_unavailable means the exact target was resolved but its managed runner was not active; the message remains durable.

For MCP, mint a credential for an indexed session:

node dist/src/cli.js auth --session <portal-session-id> --json

Configure dist/src/mcp.js as a stdio MCP server with the returned PORTAL_SESSION_ID and PORTAL_SESSION_TOKEN. Tokens are stored only as hashes; agent identity and workspace are derived by the daemon, not accepted from tool arguments.

The control token protects the CLI plane from agents that can reach the socket but cannot read Portal's private runtime directory. It is not an isolation boundary against an unrestricted same-UID process; strong isolation requires an OS sandbox or separate service identity.

See the MVP checkpoint plan, the product vision, and the concise long-term architecture.