@pi-outpost/embed
v0.23.1
Published
Mount pi-outpost as a Shadow-DOM-isolated widget inside another web app.
Maintainers
Readme
@pi-outpost/embed
Mount pi-outpost — a web chat UI for the pi coding agent — as a widget inside any web app.
The widget renders into a Shadow DOM, fully isolated from the host app's CSS in both directions: Tailwind's reset never touches the host page, and the host page's styles never bleed into the widget. React is a peer dependency (supplied by the host); everything else — Tailwind, markdown, mermaid, highlight.js, the wire protocol — is compiled into the package.
It talks to a pi-outpost server over WebSocket, so you need one running:
npx pi-outpost init # writes a starter config
npx pi-outpost # http://127.0.0.1:3141/See the main README for configuring it.
Install
npm install @pi-outpost/embedRequires react and react-dom ≥ 19 in the host app.
Usage
import { mount } from "@pi-outpost/embed";
const widget = mount(document.getElementById("assistant"), {
serverUrl: "https://your-pi-outpost-server", // omit for same-origin
theme: "dark", // optional; falls back to the server's branding.defaultTheme, then "system"
token: "…", // only for servers with `server.token` set
workspace: "/srv/projects/example", // optional resolved root of an already-open project
});
widget.setTheme("light"); // change the theme at runtime
widget.unmount(); // tear down the React treemount(container, options?) returns { unmount(), setTheme(theme) }. The container itself stays in the DOM after unmount(), with an empty shadow root.
When workspace is set, the host binds that widget to the named open project and the widget
offers no project switcher. If the root is not open, the server falls back to its default
project rather than leaving the widget disconnected.
Which theme wins
Strongest first:
setTheme(), a{ type: "pi-outpost:set-theme", theme }message, or the reader using the widget's own toggle — whatever was chosen while it was on screen;- the
themeyou pass tomount(). Naming it means you get it: a reader who once used the toggle on this origin does not overrule the page that embeds the widget; - a theme this browser remembered from an earlier visit;
- the server's
branding.defaultTheme, itself falling back to"system".
So pass theme when your page has a look the widget has to match, and leave it out when the deployment's own branding.defaultTheme should decide.
Server-rendered apps
Importing the package is safe anywhere, but mount() needs a real DOM: it attaches a shadow root to the element you give it. In Next.js, Remix or Astro, call it from an effect (client-side only):
useEffect(() => {
const widget = mount(ref.current);
return () => widget.unmount();
}, []);In Next.js, the component doing this must be a client component ("use client").
Server-side configuration
Configure this on the pi-outpost server, whatever the deployment topology:
server.allowedOrigins— the widget carries the host page's origin (e.g.https://your-app.example.com), not pi-outpost's own. Add it explicitly; even same-domain deployments need this (onlylocalhost/127.0.0.1are trusted automatically). Listing it is all a cross-origin mount needs: the server answers those origins with the CORS headers the browser requires on every HTTP route, and applies the same allowlist to the WebSocket handshake. It grants no authority of its own — a token-protected route still wants its token.embed.workspaceControls— when the host does not passworkspace, choose what the widget exposes:"settings"(the one-project default),"root"(a compact root chooser) or"projects"(open/switch/close controls).workspaceLockstill overrides this and pins the deployment to one project.
License
MIT
