npm.io
0.8.9 • Published 1 month ago

@crewx/sdk

Licence
UNLICENSED
Version
0.8.9
Deps
7
Size
1.0 MB
Vulns
5
Weekly
0

@crewx/sdk

CrewX SDK — load crewx.yaml and run AI agents from TypeScript/JavaScript.

Status: 0.9.0-alpha — Core API, Plugin system, and Event system are stable. Tool system coming soon.

Table of Contents

  1. Quick Start
  2. Core API
  3. crewx.yaml Structure
  4. Layout System
  5. Template Helpers
  6. Provider Bridge
  7. Plugin System
  8. Event System
  9. Type Reference

1. Quick Start

Install
npm install @crewx/sdk
Hello World
import { Crewx } from '@crewx/sdk';

const crewx = await Crewx.loadYaml('./crewx.yaml');

const result = await crewx.query('assistant', 'Hello! What can you do?');

if (result.ok) {
  console.log(result.data);          // agent's response text
} else {
  console.error(result.error?.message);
}
Minimal crewx.yaml
agents:
  assistant:
    name: Assistant
    provider: cli/claude
    inline:
      model: claude-sonnet-4-6
      prompt: |
        You are a helpful assistant.
        Answer concisely.
Task Execution (with side effects)
const crewx = await Crewx.loadYaml('./crewx.yaml');

// execute() allows the agent to write files, run commands, etc.
const result = await crewx.execute('assistant', 'Refactor src/utils.ts');

console.log(`Done in ${result.meta.durationMs}ms`);
console.log(result.data);
Plugin Setup (optional)
import { FileLoggerPlugin } from '@crewx/cli/plugins/file-logger';
import { SqliteTracingPlugin } from '@crewx/cli/plugins/sqlite-tracing';

await crewx.use(new FileLoggerPlugin());    // log files → .crewx/logs/
await crewx.use(new SqliteTracingPlugin()); // task records → ~/.crewx/crewx.db
// ... run tasks ...
await crewx.close(); // flush all plugins on exit

2. Core API

Crewx.loadYaml(path, options?)

Load from a crewx.yaml file. Documents referenced in the config are loaded automatically.

const crewx = await Crewx.loadYaml('./crewx.yaml');

// With options
const crewx = await Crewx.loadYaml('./crewx.yaml', {
  execPolicy: { allow: ['git *', 'npm run *'], deny: ['rm *'] },
});
Crewx.fromConfig(config, options?, projectRoot?)

Create from an already-parsed config object. Useful when you load YAML yourself or build config programmatically.

import { Crewx, CrewxProjectConfig } from '@crewx/sdk';

const config: CrewxProjectConfig = {
  agents: [
    { id: 'bot', provider: 'cli/claude', inline: { prompt: 'You are a bot.' } },
  ],
};

const crewx = await Crewx.fromConfig(config, {}, process.cwd());
crewx.query(agentRef, message, options?)

Ask an agent a question. Read-only — the agent is expected to respond with text only.

const result = await crewx.query('assistant', 'Summarize this PR');

// With options
const result = await crewx.query('assistant', 'Translate to Korean', {
  model: 'claude-opus-4-6',      // override model
  provider: 'cli/claude',        // override provider
  context: 'Additional context', // prepended to the message
});

Note: '@assistant' 형태도 동작합니다 (CLI 호환). SDK에서는 bare id가 권장됩니다.

Returns: QueryResult

crewx.execute(agentRef, message, options?)

Run a task with an agent. The agent may write files, run shell commands, etc. Uses --dangerously-skip-permissions for cli/claude.

const result = await crewx.execute('coder', 'Fix the failing tests in src/');

if (!result.ok) {
  console.error('Failed:', result.error?.code, result.error?.message);
}

Returns: ExecuteResult

crewx.renderAgentPromptFull(agentId, options?)

Render the complete system prompt for an agent — layout + template expansion. Used to inspect what the agent actually receives, or to pass the prompt to another system.

const prompt = await crewx.renderAgentPromptFull('assistant');
console.log(prompt);

// With layout override
const prompt = await crewx.renderAgentPromptFull('assistant', {
  layout: 'crewx/minimal',
  session: { mode: 'execute', platform: 'api' },
});
crewx.registerLayout(name, template)

Register a custom layout at runtime. See Layout System.

crewx.registerLayout('my-layout', `
# {{agent.name}}
{{agent.inline.prompt}}
---
Session: {{session.mode}}
`);

const prompt = await crewx.renderAgentPromptFull('assistant', {
  layout: 'my-layout',
});
crewx.agents

ReadonlyMap<string, AgentConfig> — all agents loaded from crewx.yaml.

// List all agents
for (const [id, agent] of crewx.agents) {
  console.log(id, agent.provider);
}

// Check if an agent exists
if (crewx.agents.has('coder')) {
  // ...
}
crewx.filterAgents(filters)

Filter agents by role, team, or provider. Supports glob patterns.

// All agents on the 'backend' team
const agents = crewx.filterAgents({ team: 'backend' });

// All Claude-based agents
const agents = crewx.filterAgents({ provider: 'cli/claude' });

// Wildcard: all cli/* providers
const agents = crewx.filterAgents({ provider: 'cli/*' });

3. crewx.yaml Structure

# ─── Agents ──────────────────────────────────────────────────
agents:
  my_agent:
    name: My Agent              # Display name (optional)
    role: Backend Developer     # Role label (optional)
    team: core                  # Team label (optional)
    provider: cli/claude        # Provider (required)
    working_directory: .        # Working dir for the agent

    inline:                     # Inline agent definition
      model: claude-sonnet-4-6  # Model override (optional)
      prompt: |                 # System prompt (Handlebars template)
        You are an expert {{agent.role}}.
        Today's context: {{{documents.guidelines.content}}}
      layout: crewx/minimal     # Layout override for this agent (optional)

# ─── Layouts ─────────────────────────────────────────────────
layouts:
  default: crewx/minimal        # Project-level default layout

# ─── Documents ───────────────────────────────────────────────
documents:
  guidelines:
    path: ./docs/guidelines.md  # Loaded at startup, available as documents.guidelines
  api_spec:
    path: ./docs/api.md
Multiple providers
agents:
  polyglot:
    provider:                   # Array = first is primary, rest are fallbacks (future)
      - cli/claude
      - cli/gemini

4. Layout System

Layouts wrap the agent's raw prompt with structure — identity blocks, session info, available tools, etc.

Built-in Layouts
ID Description
crewx/default Full structured layout (identity + session + prompt)
crewx/minimal Minimal wrapper — just the agent prompt
Resolution Priority

When calling renderAgentPromptFull(), the layout is resolved in this order (first match wins):

  1. options.layout — call-site override
  2. agent.inline.layout — per-agent definition in crewx.yaml
  3. config.layouts.default — project-level default
  4. crewx/default — SDK built-in fallback
Custom Layout via registerLayout()
crewx.registerLayout('compact', `
## {{agent.name}} ({{agent.role}})
{{agent.inline.prompt}}
`);

const prompt = await crewx.renderAgentPromptFull('assistant', {
  layout: 'compact',
});
Inline Layout in crewx.yaml
agents:
  assistant:
    provider: cli/claude
    inline:
      prompt: You are helpful.
      layout:
        id: crewx/default
        props:
          show_skills: false     # Pass props to the layout template
Inline Template String
const prompt = await crewx.renderAgentPromptFull('assistant', {
  layout: { template: 'SYSTEM: {{agent.inline.prompt}}' },
});

5. Template Helpers

The inline.prompt field in crewx.yaml is a Handlebars template. These helpers are available:

P0 Helpers (Core)
Helper Usage Description
exec {{exec "git log --oneline -5"}} Run shell command and inline output
include {{include someVar}} Include a string variable as-is (no escaping)
fenced_code {{fenced_code content lang="ts"}} Wrap content in Markdown code block
Condition Helpers
{{#if (eq agent.team "backend")}}Backend mode{{/if}}
{{#if (and featureA featureB)}}Both enabled{{/if}}

Available: eq, ne, and, or, not, contains

Utility Helpers
Helper Description
truncate text len Truncate string to N chars
length array Array/string length
escapeHandlebars text Escape {{ in content
formatFileSize bytes 10485761 MB
formatTimestamp ms Unix ms → readable date
Document Access in Templates

Documents defined in crewx.yaml are automatically available:

# Inline the full document
{{{documents.guidelines.content}}}

# Access metadata
Path: {{documents.guidelines.path}}
exec Security Policy

Control which shell commands are allowed in templates:

const crewx = await Crewx.loadYaml('./crewx.yaml', {
  execPolicy: {
    allow: ['git *', 'npm run *', 'cat *'],
    deny:  ['rm *', 'curl *'],
  },
});

Or set it in crewx.yaml:

settings:
  template:
    exec:
      allow:
        - "git *"
        - "npm run *"
      deny:
        - "rm *"

Glob syntax: * matches within a segment, ** matches across path segments.


6. Provider Bridge

Each agent has a provider field that tells the SDK which AI backend to use.

Supported Providers
Provider CLI Command Notes
cli/claude claude Claude Code CLI
cli/gemini gemini Gemini CLI
cli/copilot gh copilot suggest GitHub Copilot
cli/codex codex OpenAI Codex CLI

Coming Soon: api/claude, api/openai — direct API providers without CLI dependency.

Provider Override at Call Site
// Use a different provider for one call
const result = await crewx.query('assistant', 'Hello', {
  provider: 'cli/gemini',
  model: 'gemini-2.0-flash',
});
query vs execute Mode
query() execute()
Intent Read-only Q&A Task with side effects
cli/claude flags -p --output-format stream-json --verbose + --dangerously-skip-permissions
Use when Asking questions, generating text Writing files, running commands

7. Plugin System

Plugins extend CrewxPlugin from @crewx/sdk and attach event listeners in attach(). Register with crewx.use(plugin) and release resources by calling crewx.close().

Lifecycle
await crewx.use(plugin);   // calls plugin.attach(crewx) — subscribe events, open DB, etc.
await crewx.close();       // calls plugin.detach(crewx) on all plugins in LIFO order

Same plugin instance registered twice is silently ignored.

Built-in Plugins (@crewx/cli)
Plugin Import path Storage
FileLoggerPlugin @crewx/cli/plugins/file-logger .crewx/logs/{ts}_{traceId}.log (one file per task)
SqliteTracingPlugin @crewx/cli/plugins/sqlite-tracing ~/.crewx/crewx.dbtasks table

The crewx CLI auto-attaches both plugins for every run. No setup needed when using the CLI directly.

Custom Plugin
import { CrewxPlugin } from '@crewx/sdk';
import type { Crewx } from '@crewx/sdk';

class CostTrackerPlugin extends CrewxPlugin {
  readonly name = 'cost-tracker';

  attach(crewx: Crewx) {
    crewx.on('task:end', (e) => {
      if (e.costUsd) console.log(`[cost] ${e.agentRef}: $${e.costUsd.toFixed(4)}`);
    });
  }
  // detach() is optional — base class no-op is sufficient if no cleanup needed
}

await crewx.use(new CostTrackerPlugin());

8. Event System

The Crewx class extends TypedEventEmitter. Events are emitted automatically during query() and execute().

Event Catalog
Event When Key Payload Fields
task:start query()/execute() begins traceId, agentRef, mode, pid, model, provider, message, timestamp
task:end Call completes (success or failure) traceId, agentRef, durationMs, result, error, inputTokens, outputTokens, costUsd, model
task:output Each line of provider output traceId, agentRef, output, level (stdout|stderr|info)

All events share traceId (format: tsk_XXXXXXXX) and timestamp inherited from BaseEvent.

crewx.on(event, listener)UnsubscribeFn
import type { TaskEndEvent } from '@crewx/sdk';

const unsub = crewx.on('task:end', (e: TaskEndEvent) => {
  console.log(`${e.agentRef} finished in ${e.durationMs}ms | tokens: ${e.inputTokens}+${e.outputTokens}`);
});

// Remove listener when no longer needed
unsub();

crewx.once(event, listener) fires exactly once and auto-unsubscribes.

Best practice: Subscribe inside Plugin.attach() so listeners are automatically removed by crewx.close(). Direct crewx.on() calls are fine for one-off use but require manual unsub() calls.


9. Type Reference

CrewxOptions
interface CrewxOptions {
  workspaceRoot?: string;
  platform?: 'cli' | 'slack' | 'api';
  execPolicy?: ExecPolicy;        // Allowed/denied shell commands in {{exec}}
}
QueryOptions / ExecuteOptions
interface QueryOptions {
  model?: string;                 // Override model (e.g. 'claude-opus-4-6')
  provider?: string;              // Override provider (e.g. 'cli/gemini')
  context?: string;               // Extra context prepended to message
  metadata?: Record<string, unknown>;
}
// ExecuteOptions has the same shape
QueryResult / ExecuteResult
interface QueryResult {
  ok: boolean;
  data: string;                   // Agent's response text
  error?: {
    code: string;                 // 'AGENT_NOT_FOUND' | 'PROVIDER_ERROR' | 'QUERY_FAILED'
    message: string;
  };
  meta: {
    agentId: string;
    provider: string;
    model?: string;
    durationMs: number;
  };
}
// ExecuteResult has the same shape
AgentConfig
interface AgentConfig {
  id: string;
  name?: string;
  role?: string;
  team?: string;
  provider: string | string[];
  working_directory?: string;
  description?: string;
  inline?: {
    model?: string;
    system_prompt?: string;
    prompt?: string;
    layout?: string | { id: string; props?: Record<string, unknown> };
  };
}
ExecPolicy
interface ExecPolicy {
  allow: string[];   // Glob patterns for allowed commands
  deny: string[];    // Glob patterns for denied commands (takes precedence)
}

Coming Soon

  • Tool System — attach custom tools/functions to agents (§3 of design spec)
  • api/* Providers — direct API calls without CLI dependency

Requirements

  • Node.js >= 20.19.0
  • At least one CLI provider installed (e.g. npm i -g @anthropic-ai/claude-code)