2
6
Fork
You've already forked pear
1
Self-hosted, relational-first workspace where AI agents are first-class users. Built on SpacetimeDB. AGPLv3. https://pear.pro
  • TypeScript 73.6%
  • Rust 25.9%
  • Shell 0.2%
  • Dockerfile 0.1%
Kara 9bc908ed6b
feat(notion): import parity pass — native blocks, complete fetches, full fidelity
Fetcher:
- complete relation/people/rich_text/title property values past Notion's
 25-item inline cap via the property-item endpoint (relations flag
 has_more; the others truncate silently, so length-25 arrays are treated
 as possibly truncated)
- paginate comments past 100 per page and collect comment attachments
 into the re-upload pipeline
- graft the original's children under duplicate synced blocks whose
 source lives elsewhere; placeholder when the original isn't shared
- fetch the workspace user directory so people/created_by values and
 comment authors resolve to names instead of UUIDs
- test seam: injectable client, with a stubbed-client regression test
 covering pagination, completion, and grafting
Transformer:
- native BlockNote emission: toggles (collapsible), code blocks with
 language, quotes, dividers, callouts (icon + color), file/video blocks
 with filenames and captions, table header rows, heading levels 4-6
- images/audio now emit Pear's storageKey-based block shapes (imported
 media previously rendered as missing); hotlinked images ride a new
 externalUrl prop
- page/database @-mentions become internal links; text and background
 colors map to BlockNote styles
- unique_id keeps its prefix (TASK-42), place values format as text,
 null numbers stay empty instead of coercing to 0, comments carry
 original author names, meeting-notes blocks import title + available
 children
Editor:
- new callout block (icon + tinted panel, nine-color palette)
- image block accepts externalUrl for hotlinked images (http(s)-only)
Tests: parity suite with per-gap fixtures plus tripwires that fail when
a Notion block or property type is neither handled nor explicitly
triaged; 135 worker tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026年07月15日 16:49:59 -04:00
crates/pear-bridge
desktop
docs
extensions
packages
server fix(notion): native pageLink blocks + reserved id blocks for import remapping — v1.0.0-beta.14 2026年07月14日 17:07:02 -04:00
web feat(notion): import parity pass — native blocks, complete fetches, full fidelity 2026年07月15日 16:49:59 -04:00
worker feat(notion): import parity pass — native blocks, complete fetches, full fidelity 2026年07月15日 16:49:59 -04:00
.dockerignore
.env.example
.gitignore
Cargo.lock
Cargo.toml
docker-compose.yml
LICENSE
package.json
PEAR_MVP.md
pnpm-lock.yaml
pnpm-workspace.yaml
README.md
ROADMAP.md

Pear

A self-hosted, relational-first workspace where AI agents are first-class users — built on SpacetimeDB.

pear.pro · Roadmap · MCP server · API endpoints · AGPL-3.0

Primary repository: codeberg.org/Eclosion-Tech/pear. The GitHub repository is a mirror.

Pages and database rows are the same entity — a page viewed in a grid is a row, a row opened fully is a page. Real-time data syncs over SpacetimeDB subscriptions; documents are typed component trees with Yjs-backed rich text, synced live through the database.

Most "AI workspace" tools are either closed SaaS with AI bolted on, or open-source apps that treat AI as an external API call. Pear treats agents as native participants in the data model: AI users hold identities, permissions, persistent memory, and audited tool access alongside human users — a workspace your agents live in, on infrastructure you own.

A Pear database built by an AI user — Kira adds the schema via tool calls in the chat panel while the populated grid renders live


Philosophy

  • Pages are database entries. No distinction between a "page" and a "row". Same entity, different lens.
  • Relations are first-class. Linking two databases is a core primitive, not a power-user feature.
  • Agents are users. AI users get identities, permissions, memory, and audit trails in the same tables as humans — not a chat sidebar.
  • Self-hostable by default. Your data lives where you put it. No vendor lock-in.
  • The client is sovereign. A workspace is just a server you connect to. No central registry, no platform that can enumerate or revoke your access.

Stack

Layer Technology
Backend / sync SpacetimeDB (Rust module)
Frontend Next.js · React 19 · Tailwind CSS 3
Editor Component-tree editor (packages/pulp) — typed ComponentNode rows in SpacetimeDB, per-block Yjs rich text via ProseMirror. BlockNote retained as a legacy read path for unmigrated pages.
Worker Node (optional) — AI conversations, Orcha task execution, tool runtime
Auth Native SpacetimeDB email/password (default) · Any OIDC provider (optional)
Attachments S3-compatible storage (Garage in Docker Compose by default)
Containerisation Docker Compose

Features

AI users, agents & extensions

  • AI users — Configurable model providers; conversations attached to pages or anchored to individual blocks (@mention a block to start a thread), persisted in SpacetimeDB.
  • Agent edit review — AI edits are bracketed with before/after snapshots; review and accept/reject them per page.
  • Orcha — Job/task coordination tables and reducers embedded in the same module (or point workers at an external Orcha DB). Page-scoped jobs from the in-app panel.
  • Extensions — Install MCP servers and config bundles; permissioned tool execution with audit logging. Built-in workspace tools extension seeded for new databases.
  • MCP server — Let external AI models (Claude Code, Claude Desktop, Cursor, ...) use Pear as a persistent memory store and notes backend over stdio or HTTP. Each client acts as a Pear AI user with its own private memory subtree. See docs/MCP_SERVER.md.

All AI features are optional — Pear runs fully without the worker or any model provider configured.

Workspace & navigation

  • Sidebar — Drag to reorder pages, collapse/expand subtrees, soft delete & restore from trash.
  • Quick switcher⌘K / Ctrl+K fuzzy search by title, plus semantic search over page embeddings when the query is long enough.
  • Breadcrumbs — Hierarchy in the header (workspace → parent → current).
  • Page icons — Emoji per page in the sidebar and headers.

Documents

  • Component-tree editor — every block is a typed ComponentNode row in SpacetimeDB. Slash commands, drag handles, multi-block selection, turn-into, undo/redo.
  • Rich text — per-block Yjs documents (ProseMirror), merged through the server; inline page links (live title sync).
  • Blocks — Headings, images and audio (uploads to blob storage), code, markdown, forms (inputs, buttons, selects), embedded AI conversations.
  • Audio — Record or upload; inline playback; transcript via Web Speech API where the browser supports it.
  • History — Snapshot timeline, preview, one-click restore; snapshots capture the full component tree.
  • Legacy pages — pages created before the component tree still render via BlockNote and migrate in place.

Databases

  • Grid, list & board views — Column resize persisted per view; frozen first column; filters and multi-column sorts; board view grouped by a Select property.
  • Property types — Text, Number, Date, Select (conditional options, per-option colors), Multi-select, Checkbox, URL, Relation, Person, Formula (client-evaluated expressions), Rollup, and AI columns (model-computed values).
  • Editing — Inline cells, fill handle, keyboard navigation (arrows, Tab, Enter), multi-row selection with bulk delete.
  • Rows — Open any row as a full page (routable URL) with properties panel.

Files

  • Attachments — Presigned upload/download via the web app; metadata in SpacetimeDB (Attachment table).

Import & API

  • Custom API endpoints — Expose any database as a versioned REST API (/api/e/{slug}) with field-level mappings, per-property type coercion, optional API-key auth, and an auto-generated OpenAPI 3.1 spec. See docs/API_ENDPOINTS.md.
  • Notion import — The import_notion reducer rebuilds a Notion workspace (pages, databases, properties, content) into an empty workspace; the bundled settings panel drives it through an external OAuth + transform service.

For a finer-grained shipped vs planned list, see ROADMAP.md.


Project structure

Pear/
├── server/
│ ├── spacetimedb/
│ │ └── src/ # Tables, reducers, types (lib.rs + modules; source of truth for API surface)
│ ├── docker/
│ │ └── entrypoint.sh # Starts SpacetimeDB, publishes WASM module
│ └── spacetime.json # Module path, TS bindings output dir
├── web/ # Next.js app (UI, API routes, embeddings, uploads)
├── worker/ # Optional Node worker (AI / Orcha / tools)
├── crates/
│ └── pear-bridge/ # Local-shell execution daemon (device pairing, allowlisted tool_bash)
├── packages/
│ └── pulp/ # Storage-agnostic component-tree editor library
├── extensions/ # Example / built-in extension manifests
├── docs/ # Public feature docs (e.g. API endpoints)
├── docker-compose.yml
└── pnpm-workspace.yaml

Standalone OSS: this repository is self-contained. Install dependencies with pnpm from the repo root (see below). It does not rely on the Pear Cloud app’s node_modules or any package from a parent monorepo.


Getting started

Prerequisites

That's it for the Docker path. Rust, the SpacetimeDB CLI, and pnpm are only needed for local development.

1. Start the stack

docker compose up -d

Docker builds the Next.js client and the SpacetimeDB WASM module; the entrypoint publishes the module once SpacetimeDB is ready.

Service URL
SpacetimeDB http://localhost:3000
Web client http://localhost:3001

Compose also brings up Garage (or your configured S3-compatible backend) for attachment uploads — see docker-compose.yml and .env.example for variables.

2. Create an account

Open http://localhost:3001 and register with any email and password. Authentication is handled natively by the SpacetimeDB module — no external auth service required.

3. Optional: AI worker

If you use AI users, conversations, or Orcha task execution, run the worker from the repo root:

pnpm worker

Copy and adjust variables from .env.example (and web/.env.local as needed).


Development (without Docker)

Prerequisites

Backend

# Start a local SpacetimeDB instance
spacetime start
# Build the module and publish
cd server/spacetimedb
cargo build --release --target wasm32-unknown-unknown
cd ..
spacetime publish -s local pear-dev
spacetime call -s local --yes pear-dev run_pending_migrations

Frontend

From the repository root (pnpm workspace — installs web/, worker/, packages/pulp, etc.):

pnpm install
pnpm --filter @pear/web dev # or: cd web && pnpm dev → http://localhost:3001

Do not symlink another project’s node_modules into web/; the lockfile in this repo is the source of truth.

Set the SpacetimeDB URI in web/.env.local if it differs from the default:

NEXT_PUBLIC_SPACETIMEDB_URI=ws://localhost:3000
NEXT_PUBLIC_SPACETIMEDB_DB_NAME=pear-dev

Regenerate TypeScript bindings

Run this after any change to the module source under server/spacetimedb/src/:

cd server
spacetime generate

Output is written to web/src/module_bindings/ (tracked in git).


Authentication

Default — native SpacetimeDB auth

Register and log in via the built-in email/password flow. No additional services required.

Optional — OIDC

Point Pear at any OpenID Connect provider (Authentik, Keycloak, Auth0, etc.) by setting these environment variables on the web service:

NEXT_PUBLIC_AUTH_MODE=oidc
NEXT_PUBLIC_OIDC_AUTHORITY=https://your-provider.example.com
NEXT_PUBLIC_OIDC_CLIENT_ID=pear

IdP requirements

For the Pear module to populate the workspace User row and auto-promote the first authenticated user to admin, the ID token (not the userinfo endpoint) must contain at minimum one of email, name, or preferred_username. SpacetimeDB validates the JWT against your provider's JWKS and the client_connected reducer reads the profile claims directly off the JWT payload — there is no userinfo round-trip from the module.

Both placements are valid per OIDC Core §5, but many IdPs default to the minimal-ID-token configuration (claims live in userinfo only). If your IdP does this, configure it to also emit the claims in the ID token when the corresponding scopes are granted:

Scope ID-token claims
email email, email_verified
profile name, preferred_username (others optional: given_name, family_name, picture, updated_at)

Provider-specific notes:

  • Keycloak — Client → Client scopesemail/profileMappers → ensure each mapper has "Add to ID token" enabled.
  • Authentik — Provider → Property mappings → make sure email and profile scope mappings have userinfo: true and id_token: true.
  • Auth0 — Action triggered on Post-Login: api.idToken.setCustomClaim('email', event.user.email) (built-in claims are normally already in the ID token by default for openid email profile scopes).
  • Cognito — User pool → App integration → App client → ensure email and name are checked under "Allowed read attributes" and the corresponding scopes are requested.

Verifying the ID token

After login, decode the ID token (JWT) at https://jwt.io or with jq:

echo "$ID_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

The payload should include email, name, and/or preferred_username alongside the standard iss/sub/aud/iat/exp. If only the standard claims are present, the User row will land is_authenticated=false and the Members section in workspace settings will appear empty — that's the smoke test.

Custom auth bridges

If you're wiring a non-standard auth provider (or building Pear into another product), pass the JWT to SpacetimeDB at connect time:

import { DbConnection } from "./module_bindings";
const conn = DbConnection.builder()
 .withUri(wsUri)
 .withDatabaseName(dbName)
 .withToken(idToken) // your IdP's ID token (RS256 JWT, validated by your IdP's JWKS)
 .onConnect((c, identity) => { /* ... */ })
 .build();

The identity SpacetimeDB derives is SHA-256(iss ‖ sub), so the same user from the same IdP always maps to the same Identity across browsers/devices. The first authenticated user on a fresh database is auto-promoted to admin; subsequent admin changes go through set_user_admin and are gated on the caller already being admin.


Backend API surface

The SpacetimeDB module is large and evolving. Rather than duplicating every reducer here:

  • Source of truth: server/spacetimedb/src/ — all #[reducer] functions and #[table] definitions (entry point lib.rs, grouped into modules).
  • High-level groups: authentication (register, login, ...), pages & Yjs (save_yjs_state, update_page_content, ...), component tree (insert_component, update_component_props, move_component, ...), snapshots, attachments, embeddings (set_page_embedding), database schema & property values & views, AI users & conversations (create_ai_user, send_message, record_compaction, ...), Orcha (create_job, claim_task, submit_result, ...), extensions (publish_extension, install_extension, permission/audit reducers, ...), HTTP API endpoints (endpoint CRUD reducers), and import (import_notion, ...).

Client call sites use the generated bindings under web/src/module_bindings/.


License

AGPL-3.0