Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-pi-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@browserbasehq/pi": minor
---

Publish the native Pi extension as an installable Pi package, bundling the private facade implementation.
2 changes: 2 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ _preview commit:

_publish-typescript:
pnpm --filter ./packages/sdk-ts build
pnpm exec turbo run build --filter @browserbasehq/pi
pnpm exec changeset publish

# Publishes a commit-addressed alpha of the TypeScript SDK (`<next>-alpha-<sha>`)
Expand All @@ -95,6 +96,7 @@ _publish-typescript:
_publish-typescript-alpha:
pnpm exec changeset version --snapshot
pnpm --filter ./packages/sdk-ts build
pnpm exec turbo run build --filter @browserbasehq/pi
pnpm exec changeset publish --tag alpha --no-git-tag

# Rewrites the Python project to the commit-addressed alpha (`<next>a0.dev<N>`)
Expand Down
32 changes: 15 additions & 17 deletions packages/docs/v4/integrations/pi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,24 @@ description: "Give the Pi coding agent native Stagehand browser tools registered
The Pi integration gives a Pi agent native `run`, `snapshot`, and `screenshot` tools backed by one persistent Stagehand browser per Pi session. Pi ships without built-in MCP by design; extensions register tools directly, so this integration runs in process without an MCP server or bridge. The tool descriptions, runtime validators, and system prompt come from the shared facade contract, so the tools behave the same as the MCP-based integrations.

<Note>
Stagehand ships this experimental integration from the repository rather than publishing it as a standalone adapter.
The extension uses Pi’s official [package format](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md). Browserbase must publish the first `@browserbasehq/pi` release before you can use the npm install below; use the source install below while testing an unreleased change.
</Note>

## Prerequisites

- Node.js 24 or newer
- pnpm 11.10.0
- A model-provider credential for Pi (`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`)
- A current Google Chrome installation for local browser mode

## Quickstart

<Steps>
<Step title="Clone and build Stagehand">
<Step title="Install the Pi package">
```bash
git clone https://github.com/browserbase/stagehand.git
cd stagehand
pnpm install --frozen-lockfile
pnpm exec turbo run build \
--filter @browserbasehq/stagehand-integrations
pi install npm:@browserbasehq/pi
```

Use `pi install -l npm:@browserbasehq/pi` to install only for the current project. Restart Pi after installing so it loads the extension.
</Step>
<Step title="Configure the Pi model">
```bash
Expand All @@ -45,27 +42,28 @@ export BROWSERBASE_API_KEY="your-browserbase-api-key"
</Step>
<Step title="Run a browser task">
```bash
cd packages/integrations/pi
pnpm start "Open https://example.com and report the page title." </dev/null
pi --no-session -p "Open https://example.com and report the page title." </dev/null
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
```

The `start` script runs `pi -e ./extensions/stagehand.ts --no-session -p`, so no install step is required; Pi loads TypeScript directly and resolves `@browserbasehq/stagehand-integrations` through the workspace.
</Step>
</Step>
</Steps>

<Note>
Two headless gotchas: print mode reads piped stdin (always redirect `</dev/null`), and non-interactive runs never show the project-trust prompt. Load the extension with `-e` as above, or use `-a` after trusting the project.
Print mode reads piped stdin, so redirect `</dev/null` when running a one-shot task. A user-level `pi install` makes the extension available to future sessions. For project-local installs, complete Pi’s project trust flow interactively before running headless.
</Note>

## Install permanently
## Try the extension from source

To register the extension for every Pi session instead of loading it per invocation:
For development before the first release, build and install the package from the repository. The build bundles the private facade so installed consumers do not depend on the monorepo.

```bash
git clone https://github.com/browserbase/stagehand.git
cd stagehand
pnpm install --frozen-lockfile
pnpm exec turbo run build --filter @browserbasehq/pi
pi install ./packages/integrations/pi
```

The `pi` manifest key in the package's `package.json` points at the extension.
Use `pi list` to inspect installed packages, `pi update npm:@browserbasehq/pi` to update a published installation, and `pi remove npm:@browserbasehq/pi` to remove it.

## Configuration

Expand Down
53 changes: 15 additions & 38 deletions packages/integrations/pi/README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,26 @@
# pi + Stagehand facade (native tools)
# Stagehand for Pi

[pi](https://pi.dev) has no built-in MCP by design — extensions register tools directly. This
package is a pi extension exposing the Stagehand facade tools (`run`, `snapshot`,
`screenshot`) natively, with descriptions, validation, and agent guidance imported from
`@browserbasehq/stagehand-integrations/facade`.
Install the native extension with Pi’s package manager:

## Setup

Use Node.js 24 or later. From the repository root, build the integrations package first:

```bash
pnpm install
pnpm exec turbo run build --filter @browserbasehq/stagehand-integrations
```sh
pi install npm:@browserbasehq/pi
pi
```

Export the browser credentials (Browserbase is the default and recommended backend) and a model
key pi supports:
Requires Node.js 24+, an authenticated Pi model provider, and local Chrome (or `BROWSERBASE_API_KEY` for Browserbase). The initial npm release is pending; before publishing, use the development build below. No private workspace dependency is needed by the installed package.

```bash
export BROWSERBASE_API_KEY=bb_live_...
export OPENAI_API_KEY=sk-... # or ANTHROPIC_API_KEY
```
The extension registers `run`, `snapshot`, and `screenshot` directly through Pi’s extension API. It launches a browser on the first call, reuses it throughout the session, and closes it on shutdown.

## Run
## Develop from source

One-off, pointing pi at the extension file (no install needed; pi loads TypeScript directly and
resolves `@browserbasehq/stagehand-integrations` through the workspace):
From the Stagehand repository root:

```bash
cd packages/integrations/pi
pi -e ./extensions/stagehand.ts --no-session -p "Use your browser tools: open https://example.com, snapshot it, and report the heading citing the snapshot ID." </dev/null
```sh
pnpm install --frozen-lockfile
pnpm exec turbo run build --filter @browserbasehq/pi
pi install ./packages/integrations/pi
```

Two headless gotchas: print mode reads piped stdin (always redirect `</dev/null`), and
non-interactive runs never show the project-trust prompt — use `-e` as above, or `-a` after
trusting the project.

To install permanently instead: `pi install ./packages/integrations/pi` (the `pi` manifest key
in `package.json` points at the extension).

## Security model
The package uses the official `pi.extensions` manifest and `pi-package` catalog keyword. The build bundles the private Core facade and leaves Stagehand (including its browser extension assets) as a runtime dependency.

The `run` tool executes model-authored JavaScript inside the Stagehand browser extension's
service worker — browser-side, never in the pi process. Browserbase is the recommended
isolation boundary: the privileged execution environment is a disposable cloud browser. The
browser launches lazily on first tool use and closes on session shutdown; only
`STAGEHAND_*`/`BROWSERBASE_*` variables configure it, and pi's model credentials never reach
the browser session.
[Setup and configuration](https://docs.stagehand.dev/v4/integrations/pi) · [Pi package format](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md)
31 changes: 25 additions & 6 deletions packages/integrations/pi/package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
{
"name": "@browserbasehq/stagehand-integrations-example-pi-facade",
"version": "4.0.3",
"private": true,
"name": "@browserbasehq/pi",
"version": "0.0.0",
"description": "Native Stagehand browser tools for the Pi coding agent.",
"keywords": [
"pi-package"
],
"homepage": "https://docs.stagehand.dev/v4/integrations/pi",
"license": "MIT",
"author": "Browserbase",
"repository": {
"type": "git",
"url": "git+https://github.com/browserbase/stagehand.git",
"directory": "packages/integrations/pi"
},
"files": [
"dist",
"README.md"
],
"type": "module",
"publishConfig": {
"access": "public"
},
"scripts": {
"start": "pi -e ./extensions/stagehand.ts --no-session -p",
"test": "pnpm -w exec turbo run build --filter @browserbasehq/stagehand-integrations && vitest run",
"test:unit": "vitest run",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"build": "tsdown",
"prepack": "pnpm run build"
},
"dependencies": {
"@browserbasehq/stagehand": "workspace:*",
"@browserbasehq/stagehand-integrations": "workspace:*"
"zod": "catalog:"
},
"devDependencies": {
"@browserbasehq/stagehand-integrations": "workspace:*",
"@earendil-works/pi-coding-agent": "catalog:",
"@types/node": "catalog:",
"tsdown": "catalog:",
"typebox": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
Expand All @@ -32,7 +51,7 @@
},
"pi": {
"extensions": [
"./extensions/stagehand.ts"
"./dist/stagehand.mjs"
]
}
}
12 changes: 12 additions & 0 deletions packages/integrations/pi/tsdown.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from "tsdown";

export default defineConfig({
entry: { stagehand: "extensions/stagehand.ts" },
format: ["esm"],
platform: "node",
target: "node24",
// Core is private. Bundle its implementation, while preserving the SDK's assets.
deps: { alwaysBundle: [/@browserbasehq\/stagehand-integrations/] },
sourcemap: true,
outDir: "dist",
});
8 changes: 7 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scripts/release/check-changesets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe("validateChangeset", () => {
"@browserbasehq/stagehand-go": patch
"@browserbasehq/stagehand-extension": patch
"browse": patch
"@browserbasehq/pi": minor
---

Release the SDKs.
Expand Down
1 change: 1 addition & 0 deletions scripts/release/check-changesets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const allowedPackages = new Set([
"@browserbasehq/stagehand-python",
"@browserbasehq/stagehand-extension",
"browse",
"@browserbasehq/pi",
]);

export function validateChangeset(contents: string, file: string): void {
Expand Down
37 changes: 36 additions & 1 deletion scripts/release/consolidate-changelogs.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { execFileSync } from "node:child_process";
import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
Expand Down Expand Up @@ -175,3 +176,37 @@ describe("shouldPreservePackageChangelogs", () => {
expect(shouldPreservePackageChangelogs(value)).toBe(expected);
});
});

describe("integration package release", () => {
it("consolidates the Pi extension changelog through the release entrypoint", async () => {
const directory = await mkdtemp(path.join(os.tmpdir(), "stagehand-integration-release-"));
try {
const scripts = path.join(directory, "scripts/release");
const packagePath = path.join(directory, "packages/integrations/pi");
await mkdir(scripts, { recursive: true });
await mkdir(packagePath, { recursive: true });
const scriptPath = path.join(scripts, "consolidate-changelogs.ts");
await copyFile(new URL("./consolidate-changelogs.ts", import.meta.url), scriptPath);
await writeFile(
path.join(directory, "CHANGELOG.md"),
"# Stagehand\n\n## 4.1.0\n\nExisting release.\n",
);
await writeFile(
path.join(packagePath, "CHANGELOG.md"),
"# Integration\n\n## 0.1.0\n\nFirst package release.\n",
);
execFileSync(process.execPath, [scriptPath], {
env: { ...process.env, CHANGESETS_ACTION_PRESERVE_CHANGELOGS: "false" },
});
const result = await readFile(path.join(directory, "CHANGELOG.md"), "utf8");
expect(result).toContain("## Pi extension 0.1.0");
expect(result).toContain("First package release.");
expect(result).toContain("## 4.1.0\n\nExisting release.");
await expect(readFile(path.join(packagePath, "CHANGELOG.md"))).rejects.toMatchObject({
code: "ENOENT",
});
} finally {
await rm(directory, { recursive: true, force: true });
}
});
});
4 changes: 4 additions & 0 deletions scripts/release/consolidate-changelogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { pathToFileURL } from "node:url";
const repositoryRoot = path.resolve(import.meta.dirname, "../..");
const rootChangelogPath = path.join(repositoryRoot, "CHANGELOG.md");
const packageChangelogs = [
{
label: "Pi extension",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new Pi changelog entry has no regression coverage. Add a release-helper test that exercises a Pi package changelog through this package list and verifies the Pi extension heading is consolidated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/release/consolidate-changelogs.ts, line 9:

<comment>The new Pi changelog entry has no regression coverage. Add a release-helper test that exercises a Pi package changelog through this package list and verifies the `Pi extension` heading is consolidated.</comment>

<file context>
@@ -5,6 +5,10 @@ import { pathToFileURL } from "node:url";
 const rootChangelogPath = path.join(repositoryRoot, "CHANGELOG.md");
 const packageChangelogs = [
+  {
+    label: "Pi extension",
+    path: path.join(repositoryRoot, "packages/integrations/pi/CHANGELOG.md"),
+  },
</file context>

path: path.join(repositoryRoot, "packages/integrations/pi/CHANGELOG.md"),
},
{
label: "TypeScript SDK",
path: path.join(repositoryRoot, "packages/sdk-ts/CHANGELOG.md"),
Expand Down
7 changes: 6 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
"browse#lint": {
"dependsOn": ["@browserbasehq/stagehand#build"]
},
"@browserbasehq/pi#build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", "!dist/**"],
"outputs": ["dist/**"]
},
"@browserbasehq/stagehand-integrations#build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", "!dist/**"],
Expand Down Expand Up @@ -243,7 +248,7 @@
"@browserbasehq/stagehand-integrations-example-vercel-ai-facade#typecheck": {
"dependsOn": ["^build"]
},
"@browserbasehq/stagehand-integrations-example-pi-facade#typecheck": {
"@browserbasehq/pi#typecheck": {
"dependsOn": ["^build"]
},
"@browserbasehq/stagehand-integrations-example-claude-code-facade#typecheck": {
Expand Down
Loading