How the Craft Agents Monorepo Is Structured with Bun Workspaces

The Craft Agents repository organizes its runtime components and user interfaces into a unified Bun-powered monorepo, using workspace glob patterns and a hoisted linker to manage dependencies across packages and applications.

The craft-ai-agents/craft-agents-oss repository leverages Bun workspaces to coordinate multiple interconnected codebases under a single root. This architecture groups core business logic into reusable packages while isolating deployable applications, enabling atomic updates and shared dependency resolution across the entire system.

Workspace Definition in Root package.json

The monorepo structure is declared in the root package.json using the workspaces field:

{
  "workspaces": [
    "packages/*",
    "apps/*",
    "!apps/online-docs"
  ]
}

This configuration instructs Bun to treat every subdirectory matching packages/* and apps/* as an independent workspace. The exclusion pattern !apps/online-docs removes the documentation site from the workspace graph, preventing it from participating in the shared dependency tree. According to the source code in craft-ai-agents/craft-agents-oss, this setup allows the package manager to install and link all workspaces from the repository root.

Hoisted Linker Configuration

Bun’s default isolated linker keeps each workspace’s dependencies strictly local, which breaks imports that transitively pull in shared libraries like i18next, @tiptap/*, or pdfjs-dist. To resolve this, the repository forces the hoisted linker in bunfig.toml:

[install]
linker = "hoisted"

With this configuration, all transitive dependencies appear at the repository root rather than within individual workspace node_modules directories. This arrangement matches the expectations of Vite and esbuild bundling used throughout the project, ensuring that shared libraries resolve correctly across workspace boundaries.

Package Layout: Packages vs. Applications

The monorepo divides code into two primary categories: runtime packages and executable applications.

Core Packages under packages/

The packages/ directory contains the foundational business logic and shared utilities:

  • packages/core – Core agent logic including agents, prompts, and utilities
  • packages/shared – Business logic shared across services such as authentication, configuration, and MCP (Model Context Protocol) integrations
  • packages/server – Main HTTP server that hosts the Electron application and API endpoints
  • packages/server-core – Low-level server helpers including web UI adapters and RPC functionality
  • packages/session-tools-core – Session tool utilities consumed by the agent runtime
  • packages/pi-agent-server – Server-side implementation of the Pi-AI integration
  • packages/messaging-gateway – Abstraction layer for messaging platforms including WhatsApp

Each package declares its own exports map in package.json (e.g., @craft-agent/shared in packages/shared/package.json) and exposes typed entry points for dependent workspaces.

Frontend Applications under apps/

The apps/ directory contains deployable user interfaces and command-line tools:

  • apps/electron – Desktop application wrapper that hosts the UI in an Electron shell
  • apps/webui – Standalone web interface that can be served independently
  • apps/viewer – Document viewer component used by the Electron application
  • apps/cli – Command-line interface for launching agents and managing sessions

Cross-Workspace Dependencies and Protocols

Internal dependencies between workspaces use the "workspace:*" protocol rather than semantic versions. This ensures that changes in one package immediately propagate to dependents without version bumps.

For example, importing shared utilities from another workspace:

// Inside any workspace (e.g., packages/ui)
import { Auth } from '@craft-agent/shared/auth';

The import resolves to packages/shared/src/auth/index.ts because packages/shared/package.json defines the @craft-agent/shared export map. This resolution works across the monorepo thanks to the hoisted node_modules structure.

Running Scripts Across Workspaces

Common development commands are defined in the root package.json and execute across all workspaces. The test script runs test suites throughout the monorepo:

"scripts": {
  "test": "bun test && for f in $(find . -name '*.isolated.ts' -not -path './node_modules/*'); do bun test \"$f\" || exit 1; done",
  "typecheck:all": "cd packages/core && bun run tsc --noEmit && cd ../shared && bun run tsc --noEmit && ..."
}

Because the hoisted linker places all dependencies at the root, these scripts can reference any sibling package without path gymnastics or duplicate installations.

Summary

  • The Craft Agents monorepo uses Bun workspaces defined in the root package.json to group packages/* and apps/* into a unified dependency graph.
  • The hoisted linker in bunfig.toml ensures transitive dependencies resolve at the root level, supporting Vite and esbuild bundling requirements.
  • Workspace protocols ("workspace:*") and export maps enable seamless internal imports between runtime packages and applications.
  • Cross-workspace scripts in the root package.json allow atomic testing and type-checking across the entire codebase.
  • The exclusion pattern !apps/online-docs demonstrates fine-grained control over which directories participate in the workspace graph.

Frequently Asked Questions

What is the difference between the packages and apps directories in the Craft Agents monorepo?

The packages directory contains reusable runtime libraries and server-side logic (such as @craft-agent/core and @craft-agent/shared), while the apps directory contains deployable applications like the Electron desktop client, web UI, and CLI. Packages are consumed as dependencies, whereas apps are build targets that consume those packages.

Why does the Craft Agents monorepo use a hoisted linker instead of Bun's default isolated linker?

The hoisted linker is required because the project's Vite and esbuild bundlers expect shared transitive dependencies (like i18next or @tiptap/*) to resolve from a single root node_modules directory. The isolated linker would keep these dependencies local to each workspace, causing build-time resolution failures when packages import shared libraries that exist in sibling workspaces.

How do I add a new workspace to the Craft Agents monorepo?

Create a new directory under packages/ or apps/ with a package.json containing a scoped name like @craft-agent/my-new-tool. The root package.json automatically includes it via the packages/* or apps/* glob pattern. Other workspaces can then reference it using bun add @craft-agent/my-new-tool@workspace:* to link the local source.

How does the workspace:* protocol affect dependency resolution in this monorepo?

The workspace:* protocol forces Bun to resolve the dependency using the local workspace source rather than querying the npm registry. This ensures that changes in dependency packages are immediately reflected in consuming applications without requiring version bumps or publishes, enabling rapid iteration across the monorepo.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →