Skip to main content

Package Overview

Craft Agents uses workspace packages to share code across apps without publishing to npm. The two primary packages are:

@craft-agent/core

Shared TypeScript types and interfaces

@craft-agent/shared

Business logic, agent implementation, auth, config

@craft-agent/core

The type definition layer for the entire monorepo. It provides zero-runtime-cost type safety.

Purpose

  • Shared TypeScript types used by Electron app and shared packages
  • No runtime code (pure types)
  • Peer dependencies on SDKs to avoid bundling duplicates

Directory Structure

packages/core/
├── src/
│   ├── index.ts           # Main entry point
│   ├── types/
│   │   ├── index.ts       # Type re-exports
│   │   ├── workspace.ts   # Workspace, auth, config types
│   │   ├── session.ts     # Session, metadata types
│   │   └── message.ts     # Message, token, event types
│   └── utils/
│       ├── index.ts       # Utility re-exports
│       └── debug.ts       # Debug logging stub
├── package.json
├── tsconfig.json
└── CLAUDE.md              # Agent-readable docs

Type Categories

Workspace Types (types/workspace.ts)

TypeDescription
WorkspaceWorkspace configuration with MCP URL and auth
McpAuthTypeMCP authentication: workspace_oauth, workspace_bearer, public
AuthTypeAPI setup method: api_key, oauth_token
OAuthCredentialsOAuth tokens from authentication flow
StoredConfigFull application configuration
CumulativeUsageGlobal token/cost tracking

Session Types (types/session.ts)

TypeDescription
SessionConversation scope with SDK session binding
StoredSessionSession with persisted messages and tokens
SessionMetadataLightweight session info for listings

Message Types (types/message.ts)

TypeDescription
MessageRuntime message with all fields
StoredMessagePersisted message format
MessageRoleuser, assistant, tool, error, status
ToolStatuspending, executing, completed, error
TokenUsageInput/output/cache token counts and cost
AgentEventEvents from agent during chat

Exports

// Main export
import type { Workspace, Session, Message } from '@craft-agent/core';

// Subpath exports
import type { Session } from '@craft-agent/core/types';
import { generateMessageId } from '@craft-agent/core/utils';
{
  "exports": {
    ".": "./src/index.ts",
    "./types": "./src/types/index.ts",
    "./utils": "./src/utils/index.ts"
  }
}

Design Decisions

Sessions are the primary boundary, not workspaces. Each session has a unique ID and maps 1:1 with an SDK session.
MCP Auth Separation: Craft OAuth (craft_oauth::global) is ONLY for the Craft API. Each MCP server has its own OAuth via workspace_oauth::{workspaceId}.

Future Migration

Currently, @craft-agent/core only exports types. The migration plan:
  1. Phase 1 (Current): Types only
  2. Phase 2: Move storage logic to core
  3. Phase 3: Move auth, credentials, MCP client
  4. Phase 4: Move agent logic, prompts

@craft-agent/shared

The business logic package containing agent implementation, auth, config, MCP integration, and utilities.

Directory Structure

packages/shared/
├── src/
│   ├── agent/              # Agent backends, permissions, tools
│   │   ├── backend/        # Claude/Pi abstraction layer
│   │   ├── claude-agent.ts # Claude SDK implementation
│   │   ├── pi-agent.ts     # Pi SDK implementation
│   │   ├── mode-manager.ts # Permission mode logic
│   │   └── session-scoped-tools.ts
│   ├── auth/               # OAuth, tokens, auth state
│   ├── config/             # Storage, preferences, themes
│   ├── credentials/        # AES-256-GCM encrypted storage
│   ├── mcp/                # MCP client, pool, validation
│   ├── sessions/           # Session persistence, index
│   ├── sources/            # Source types, storage, service
│   ├── automations/        # Event-driven automations
│   ├── skills/             # Skill storage and loading
│   ├── utils/              # Debug, summarization, file handling
│   └── branding.ts         # Brand constants
├── package.json
├── tsconfig.json
└── CLAUDE.md

Key Modules

Agent (src/agent/)

claude-agent.ts

Wraps Claude Agent SDK with permissions and MCP

pi-agent.ts

Wraps Pi SDK with unified interface

mode-manager.ts

Permission mode logic (safe/ask/allow-all)

session-scoped-tools.ts

Tools available within sessions
Backend Abstraction: The backend/ directory provides a unified interface for both Claude and Pi agents:
// packages/shared/src/agent/backend/types.ts
export interface AgentBackend {
  initialize(options: InitOptions): Promise<void>;
  chat(prompt: string, options?: ChatOptions): AsyncGenerator<AgentEvent>;
  abort(): Promise<void>;
  cleanup(): Promise<void>;
}

Permission Modes

Three-level permission system per session:
ModeDisplay NameBehavior
'safe'ExploreRead-only, blocks write operations
'ask'Ask to EditPrompts for bash commands (default)
'allow-all'AutoAuto-approves all commands
import { setPermissionMode, getPermissionMode } from '@craft-agent/shared/agent';

setPermissionMode(sessionId, 'safe');
const mode = getPermissionMode(sessionId); // 'safe'
Permission modes are per-session, not global. SHIFT+TAB cycles modes in the UI.

MCP (src/mcp/)

MCP (Model Context Protocol) integration:
FilePurpose
mcp-pool.tsCentralized MCP client pool (main process)
client.tsMCP client wrapper
validation.tsConnection validation
api-source-pool-client.tsIn-process API source client
McpClientPool Architecture:
// packages/shared/src/mcp/mcp-pool.ts
export class McpClientPool {
  // Sync MCP sources (stdio/SSE)
  async sync(servers: McpServerConfig[]): Promise<void>;
  
  // Sync API sources (REST, Google, Slack)
  async syncApiServers(servers: ApiServerConfig[]): Promise<void>;
  
  // Call tool on any connected source
  async callTool(slug: string, toolName: string, args: object): Promise<any>;
}
  1. Main process creates a single McpClientPool instance
  2. pool.sync(mcpServers) connects new MCP sources, disconnects removed ones
  3. pool.syncApiServers(apiServers) connects API sources as in-process clients
  4. Claude backend: proxy tools created via createSourceProxyServers(pool)
  5. Pi backend: proxy tool definitions sent to subprocess

Configuration (src/config/)

FilePurpose
storage.tsMulti-workspace config at ~/.craft-agent/config.json
preferences.tsUser preferences (theme, cache TTL, etc.)
theme.tsCascading theme system (app → workspace)
watcher.tsFile watcher for live config updates
Theme System:
import { resolveTheme, themeToCSS } from '@craft-agent/shared/config/theme';

const theme = resolveTheme(workspaceId); // Merges app + workspace themes
const css = themeToCSS(theme);           // Generate CSS variables
6-color system: background, foreground, accent, info, success, destructive

Credentials (src/credentials/)

Secure credential storage with AES-256-GCM encryption:
import { getCredentialManager } from '@craft-agent/shared/credentials';

const manager = getCredentialManager();

// Store encrypted credential
await manager.set('anthropic_api_key::global', {
  type: 'api_key',
  apiKey: 'sk-ant-...',
});

// Read decrypted credential
const cred = await manager.get('anthropic_api_key::global');
All credentials stored in ~/.craft-agent/credentials.enc.

Sessions (src/sessions/)

FilePurpose
storage.tsSession CRUD, JSONL persistence
index.tsSession listing and metadata
persistence-queue.tsDebounced async writes (500ms)
JSONL Format: Sessions are stored as JSON Lines (one message per line):
{"type":"user","content":"Hello","timestamp":1234567890}
{"type":"assistant","content":"Hi there!","timestamp":1234567891}
JSONL is streaming-friendly and append-only, making it easy to debug and inspect.

Subpath Exports

@craft-agent/shared uses subpath exports for clean imports:
// Agent
import { CraftAgent } from '@craft-agent/shared/agent';
import { createBackend } from '@craft-agent/shared/agent/backend';

// Config
import { loadStoredConfig } from '@craft-agent/shared/config';
import { resolveTheme } from '@craft-agent/shared/config/theme';

// Credentials
import { getCredentialManager } from '@craft-agent/shared/credentials';

// MCP
import { McpClientPool } from '@craft-agent/shared/mcp';

// Sources
import { loadWorkspaceSources } from '@craft-agent/shared/sources';

// Utils
import { debug } from '@craft-agent/shared/utils';
{
  "exports": {
    ".": "./src/index.ts",
    "./agent": "./src/agent/index.ts",
    "./agent/modes": "./src/agent/mode-types.ts",
    "./agent/backend": "./src/agent/backend/index.ts",
    "./auth": "./src/auth/index.ts",
    "./config": "./src/config/index.ts",
    "./config/theme": "./src/config/theme.ts",
    "./credentials": "./src/credentials/index.ts",
    "./mcp": "./src/mcp/index.ts",
    "./sessions": "./src/sessions/index.ts",
    "./sources": "./src/sources/index.ts",
    "./utils": "./src/utils/index.ts"
  }
}

Internal Structure

Agent Implementation

Both ClaudeAgent and PiAgent implement the AgentBackend interface:
// packages/shared/src/agent/claude-agent.ts
export class ClaudeAgent implements AgentBackend {
  async initialize(options: InitOptions): Promise<void> {
    // 1. Connect to MCP servers
    // 2. Set up permission hooks
    // 3. Initialize SDK session
  }

  async *chat(prompt: string): AsyncGenerator<AgentEvent> {
    // 1. Validate permission mode
    // 2. Stream agent responses
    // 3. Handle tool calls with PreToolUse hook
    // 4. Summarize large results with PostToolUse hook
    for await (const event of this.sdk.chat(prompt)) {
      yield event;
    }
  }
}

MCP Source Architecture

All source connections (MCP and API) are managed through a centralized pool:
┌─────────────────────────────────────────────────────────────┐
│ McpClientPool (main process)                                 │
│ - Manages all MCP and API connections                       │
│ - Routes tool calls to correct source                       │
└─────────────────────────────────────────────────────────────┘

            ├─→ MCP Source (stdio)    [Linear, GitHub, custom]
            ├─→ MCP Source (SSE)      [Remote MCP servers]
            └─→ API Source (in-proc)  [Gmail, Slack, Microsoft]

Dependencies

@craft-agent/core

{
  "peerDependencies": {
    "@anthropic-ai/claude-agent-sdk": ">=0.2.19",
    "@modelcontextprotocol/sdk": ">=1.0.0"
  }
}

@craft-agent/shared

{
  "dependencies": {
    "@craft-agent/core": "workspace:*",
    "@craft-agent/session-tools-core": "workspace:*",
    "croner": "^10.0.1",
    "shell-quote": "^1.8.3"
  },
  "peerDependencies": {
    "@anthropic-ai/claude-agent-sdk": "^0.2.19",
    "@modelcontextprotocol/sdk": ">=1.0.0",
    "zod": ">=3.0.0"
  }
}
Peer dependencies prevent bundling duplicates of large SDKs.

Type Checking

# Check individual package
cd packages/core && bun run tsc --noEmit
cd packages/shared && bun run tsc --noEmit

# Check all packages from root
bun run typecheck:all

Next Steps

Agent Backends

Learn how Claude and Pi SDKs are abstracted

Electron App

See how packages are used in the main app

Build docs developers (and LLMs) love