@crewx/sdk 0.8.0 → 0.8.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,584 +1,584 @@
1
- # @crewx/sdk
2
-
3
- CrewX SDK — load `crewx.yaml` and run AI agents from TypeScript/JavaScript.
4
-
5
- > **Status**: `0.9.0-alpha` — Core API, Plugin system, and Event system are stable. Tool system coming soon.
6
-
7
- ## Table of Contents
8
-
9
- 1. [Quick Start](#1-quick-start)
10
- 2. [Core API](#2-core-api)
11
- 3. [crewx.yaml Structure](#3-crewxyaml-structure)
12
- 4. [Layout System](#4-layout-system)
13
- 5. [Template Helpers](#5-template-helpers)
14
- 6. [Provider Bridge](#6-provider-bridge)
15
- 7. [Plugin System](#7-plugin-system)
16
- 8. [Event System](#8-event-system)
17
- 9. [Type Reference](#9-type-reference)
18
-
19
- ---
20
-
21
- ## 1. Quick Start
22
-
23
- ### Install
24
-
25
- ```bash
26
- npm install @crewx/sdk
27
- ```
28
-
29
- ### Hello World
30
-
31
- ```typescript
32
- import { Crewx } from '@crewx/sdk';
33
-
34
- const crewx = await Crewx.loadYaml('./crewx.yaml');
35
-
36
- const result = await crewx.query('assistant', 'Hello! What can you do?');
37
-
38
- if (result.ok) {
39
- console.log(result.data); // agent's response text
40
- } else {
41
- console.error(result.error?.message);
42
- }
43
- ```
44
-
45
- ### Minimal `crewx.yaml`
46
-
47
- ```yaml
48
- agents:
49
- assistant:
50
- name: Assistant
51
- provider: cli/claude
52
- inline:
53
- model: claude-sonnet-4-6
54
- prompt: |
55
- You are a helpful assistant.
56
- Answer concisely.
57
- ```
58
-
59
- ### Task Execution (with side effects)
60
-
61
- ```typescript
62
- const crewx = await Crewx.loadYaml('./crewx.yaml');
63
-
64
- // execute() allows the agent to write files, run commands, etc.
65
- const result = await crewx.execute('assistant', 'Refactor src/utils.ts');
66
-
67
- console.log(`Done in ${result.meta.durationMs}ms`);
68
- console.log(result.data);
69
- ```
70
-
71
- ### Plugin Setup (optional)
72
-
73
- ```typescript
74
- import { FileLoggerPlugin } from '@crewx/cli/plugins/file-logger';
75
- import { SqliteTracingPlugin } from '@crewx/cli/plugins/sqlite-tracing';
76
-
77
- await crewx.use(new FileLoggerPlugin()); // log files → .crewx/logs/
78
- await crewx.use(new SqliteTracingPlugin()); // task records → ~/.crewx/crewx.db
79
- // ... run tasks ...
80
- await crewx.close(); // flush all plugins on exit
81
- ```
82
-
83
- ---
84
-
85
- ## 2. Core API
86
-
87
- ### `Crewx.loadYaml(path, options?)`
88
-
89
- Load from a `crewx.yaml` file. Documents referenced in the config are loaded automatically.
90
-
91
- ```typescript
92
- const crewx = await Crewx.loadYaml('./crewx.yaml');
93
-
94
- // With options
95
- const crewx = await Crewx.loadYaml('./crewx.yaml', {
96
- execPolicy: { allow: ['git *', 'npm run *'], deny: ['rm *'] },
97
- });
98
- ```
99
-
100
- ### `Crewx.fromConfig(config, options?, projectRoot?)`
101
-
102
- Create from an already-parsed config object. Useful when you load YAML yourself or build config programmatically.
103
-
104
- ```typescript
105
- import { Crewx, CrewxProjectConfig } from '@crewx/sdk';
106
-
107
- const config: CrewxProjectConfig = {
108
- agents: [
109
- { id: 'bot', provider: 'cli/claude', inline: { prompt: 'You are a bot.' } },
110
- ],
111
- };
112
-
113
- const crewx = await Crewx.fromConfig(config, {}, process.cwd());
114
- ```
115
-
116
- ### `crewx.query(agentRef, message, options?)`
117
-
118
- Ask an agent a question. Read-only — the agent is expected to respond with text only.
119
-
120
- ```typescript
121
- const result = await crewx.query('assistant', 'Summarize this PR');
122
-
123
- // With options
124
- const result = await crewx.query('assistant', 'Translate to Korean', {
125
- model: 'claude-opus-4-6', // override model
126
- provider: 'cli/claude', // override provider
127
- context: 'Additional context', // prepended to the message
128
- });
129
- ```
130
-
131
- > **Note**: `'@assistant'` 형태도 동작합니다 (CLI 호환). SDK에서는 bare id가 권장됩니다.
132
-
133
- **Returns**: [`QueryResult`](#queryresult)
134
-
135
- ### `crewx.execute(agentRef, message, options?)`
136
-
137
- Run a task with an agent. The agent may write files, run shell commands, etc. Uses `--dangerously-skip-permissions` for `cli/claude`.
138
-
139
- ```typescript
140
- const result = await crewx.execute('coder', 'Fix the failing tests in src/');
141
-
142
- if (!result.ok) {
143
- console.error('Failed:', result.error?.code, result.error?.message);
144
- }
145
- ```
146
-
147
- **Returns**: [`ExecuteResult`](#executeresult)
148
-
149
- ### `crewx.renderAgentPromptFull(agentId, options?)`
150
-
151
- 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.
152
-
153
- ```typescript
154
- const prompt = await crewx.renderAgentPromptFull('assistant');
155
- console.log(prompt);
156
-
157
- // With layout override
158
- const prompt = await crewx.renderAgentPromptFull('assistant', {
159
- layout: 'crewx/minimal',
160
- session: { mode: 'execute', platform: 'api' },
161
- });
162
- ```
163
-
164
- ### `crewx.registerLayout(name, template)`
165
-
166
- Register a custom layout at runtime. See [Layout System](#4-layout-system).
167
-
168
- ```typescript
169
- crewx.registerLayout('my-layout', `
170
- # {{agent.name}}
171
- {{agent.inline.prompt}}
172
- ---
173
- Session: {{session.mode}}
174
- `);
175
-
176
- const prompt = await crewx.renderAgentPromptFull('assistant', {
177
- layout: 'my-layout',
178
- });
179
- ```
180
-
181
- ### `crewx.agents`
182
-
183
- `ReadonlyMap<string, AgentConfig>` — all agents loaded from `crewx.yaml`.
184
-
185
- ```typescript
186
- // List all agents
187
- for (const [id, agent] of crewx.agents) {
188
- console.log(id, agent.provider);
189
- }
190
-
191
- // Check if an agent exists
192
- if (crewx.agents.has('coder')) {
193
- // ...
194
- }
195
- ```
196
-
197
- ### `crewx.filterAgents(filters)`
198
-
199
- Filter agents by role, team, or provider. Supports glob patterns.
200
-
201
- ```typescript
202
- // All agents on the 'backend' team
203
- const agents = crewx.filterAgents({ team: 'backend' });
204
-
205
- // All Claude-based agents
206
- const agents = crewx.filterAgents({ provider: 'cli/claude' });
207
-
208
- // Wildcard: all cli/* providers
209
- const agents = crewx.filterAgents({ provider: 'cli/*' });
210
- ```
211
-
212
- ---
213
-
214
- ## 3. `crewx.yaml` Structure
215
-
216
- ```yaml
217
- # ─── Agents ──────────────────────────────────────────────────
218
- agents:
219
- my_agent:
220
- name: My Agent # Display name (optional)
221
- role: Backend Developer # Role label (optional)
222
- team: core # Team label (optional)
223
- provider: cli/claude # Provider (required)
224
- working_directory: . # Working dir for the agent
225
-
226
- inline: # Inline agent definition
227
- model: claude-sonnet-4-6 # Model override (optional)
228
- prompt: | # System prompt (Handlebars template)
229
- You are an expert {{agent.role}}.
230
- Today's context: {{{documents.guidelines.content}}}
231
- layout: crewx/minimal # Layout override for this agent (optional)
232
-
233
- # ─── Layouts ─────────────────────────────────────────────────
234
- layouts:
235
- default: crewx/minimal # Project-level default layout
236
-
237
- # ─── Documents ───────────────────────────────────────────────
238
- documents:
239
- guidelines:
240
- path: ./docs/guidelines.md # Loaded at startup, available as documents.guidelines
241
- api_spec:
242
- path: ./docs/api.md
243
- ```
244
-
245
- ### Multiple providers
246
-
247
- ```yaml
248
- agents:
249
- polyglot:
250
- provider: # Array = first is primary, rest are fallbacks (future)
251
- - cli/claude
252
- - cli/gemini
253
- ```
254
-
255
- ---
256
-
257
- ## 4. Layout System
258
-
259
- Layouts wrap the agent's raw prompt with structure — identity blocks, session info, available tools, etc.
260
-
261
- ### Built-in Layouts
262
-
263
- | ID | Description |
264
- |----|-------------|
265
- | `crewx/default` | Full structured layout (identity + session + prompt) |
266
- | `crewx/minimal` | Minimal wrapper — just the agent prompt |
267
-
268
- ### Resolution Priority
269
-
270
- When calling `renderAgentPromptFull()`, the layout is resolved in this order (first match wins):
271
-
272
- 1. `options.layout` — call-site override
273
- 2. `agent.inline.layout` — per-agent definition in `crewx.yaml`
274
- 3. `config.layouts.default` — project-level default
275
- 4. `crewx/default` — SDK built-in fallback
276
-
277
- ### Custom Layout via `registerLayout()`
278
-
279
- ```typescript
280
- crewx.registerLayout('compact', `
281
- ## {{agent.name}} ({{agent.role}})
282
- {{agent.inline.prompt}}
283
- `);
284
-
285
- const prompt = await crewx.renderAgentPromptFull('assistant', {
286
- layout: 'compact',
287
- });
288
- ```
289
-
290
- ### Inline Layout in `crewx.yaml`
291
-
292
- ```yaml
293
- agents:
294
- assistant:
295
- provider: cli/claude
296
- inline:
297
- prompt: You are helpful.
298
- layout:
299
- id: crewx/default
300
- props:
301
- show_skills: false # Pass props to the layout template
302
- ```
303
-
304
- ### Inline Template String
305
-
306
- ```typescript
307
- const prompt = await crewx.renderAgentPromptFull('assistant', {
308
- layout: { template: 'SYSTEM: {{agent.inline.prompt}}' },
309
- });
310
- ```
311
-
312
- ---
313
-
314
- ## 5. Template Helpers
315
-
316
- The `inline.prompt` field in `crewx.yaml` is a **Handlebars template**. These helpers are available:
317
-
318
- ### P0 Helpers (Core)
319
-
320
- | Helper | Usage | Description |
321
- |--------|-------|-------------|
322
- | `exec` | `{{exec "git log --oneline -5"}}` | Run shell command and inline output |
323
- | `include` | `{{include someVar}}` | Include a string variable as-is (no escaping) |
324
- | `fenced_code` | `{{fenced_code content lang="ts"}}` | Wrap content in Markdown code block |
325
-
326
- ### Condition Helpers
327
-
328
- ```handlebars
329
- {{#if (eq agent.team "backend")}}Backend mode{{/if}}
330
- {{#if (and featureA featureB)}}Both enabled{{/if}}
331
- ```
332
-
333
- Available: `eq`, `ne`, `and`, `or`, `not`, `contains`
334
-
335
- ### Utility Helpers
336
-
337
- | Helper | Description |
338
- |--------|-------------|
339
- | `truncate text len` | Truncate string to N chars |
340
- | `length array` | Array/string length |
341
- | `escapeHandlebars text` | Escape `{{` in content |
342
- | `formatFileSize bytes` | `1048576` → `1 MB` |
343
- | `formatTimestamp ms` | Unix ms → readable date |
344
-
345
- ### Document Access in Templates
346
-
347
- Documents defined in `crewx.yaml` are automatically available:
348
-
349
- ```handlebars
350
- # Inline the full document
351
- {{{documents.guidelines.content}}}
352
-
353
- # Access metadata
354
- Path: {{documents.guidelines.path}}
355
- ```
356
-
357
- ### `exec` Security Policy
358
-
359
- Control which shell commands are allowed in templates:
360
-
361
- ```typescript
362
- const crewx = await Crewx.loadYaml('./crewx.yaml', {
363
- execPolicy: {
364
- allow: ['git *', 'npm run *', 'cat *'],
365
- deny: ['rm *', 'curl *'],
366
- },
367
- });
368
- ```
369
-
370
- Or set it in `crewx.yaml`:
371
-
372
- ```yaml
373
- settings:
374
- template:
375
- exec:
376
- allow:
377
- - "git *"
378
- - "npm run *"
379
- deny:
380
- - "rm *"
381
- ```
382
-
383
- Glob syntax: `*` matches within a segment, `**` matches across path segments.
384
-
385
- ---
386
-
387
- ## 6. Provider Bridge
388
-
389
- Each agent has a `provider` field that tells the SDK which AI backend to use.
390
-
391
- ### Supported Providers
392
-
393
- | Provider | CLI Command | Notes |
394
- |----------|-------------|-------|
395
- | `cli/claude` | `claude` | Claude Code CLI |
396
- | `cli/gemini` | `gemini` | Gemini CLI |
397
- | `cli/copilot` | `gh copilot suggest` | GitHub Copilot |
398
- | `cli/codex` | `codex` | OpenAI Codex CLI |
399
-
400
- > **Coming Soon**: `api/claude`, `api/openai` — direct API providers without CLI dependency.
401
-
402
- ### Provider Override at Call Site
403
-
404
- ```typescript
405
- // Use a different provider for one call
406
- const result = await crewx.query('assistant', 'Hello', {
407
- provider: 'cli/gemini',
408
- model: 'gemini-2.0-flash',
409
- });
410
- ```
411
-
412
- ### `query` vs `execute` Mode
413
-
414
- | | `query()` | `execute()` |
415
- |--|-----------|-------------|
416
- | Intent | Read-only Q&A | Task with side effects |
417
- | cli/claude flags | `-p --output-format stream-json --verbose` | + `--dangerously-skip-permissions` |
418
- | Use when | Asking questions, generating text | Writing files, running commands |
419
-
420
- ---
421
-
422
- ## 7. Plugin System
423
-
424
- Plugins extend `CrewxPlugin` from `@crewx/sdk` and attach event listeners in `attach()`. Register with `crewx.use(plugin)` and release resources by calling `crewx.close()`.
425
-
426
- ### Lifecycle
427
-
428
- ```typescript
429
- await crewx.use(plugin); // calls plugin.attach(crewx) — subscribe events, open DB, etc.
430
- await crewx.close(); // calls plugin.detach(crewx) on all plugins in LIFO order
431
- ```
432
-
433
- Same plugin instance registered twice is silently ignored.
434
-
435
- ### Built-in Plugins (`@crewx/cli`)
436
-
437
- | Plugin | Import path | Storage |
438
- |--------|-------------|---------|
439
- | `FileLoggerPlugin` | `@crewx/cli/plugins/file-logger` | `.crewx/logs/{ts}_{traceId}.log` (one file per task) |
440
- | `SqliteTracingPlugin` | `@crewx/cli/plugins/sqlite-tracing` | `~/.crewx/crewx.db` — `tasks` table |
441
-
442
- > The `crewx` CLI auto-attaches both plugins for every run. No setup needed when using the CLI directly.
443
-
444
- ### Custom Plugin
445
-
446
- ```typescript
447
- import { CrewxPlugin } from '@crewx/sdk';
448
- import type { Crewx } from '@crewx/sdk';
449
-
450
- class CostTrackerPlugin extends CrewxPlugin {
451
- readonly name = 'cost-tracker';
452
-
453
- attach(crewx: Crewx) {
454
- crewx.on('task:end', (e) => {
455
- if (e.costUsd) console.log(`[cost] ${e.agentRef}: $${e.costUsd.toFixed(4)}`);
456
- });
457
- }
458
- // detach() is optional — base class no-op is sufficient if no cleanup needed
459
- }
460
-
461
- await crewx.use(new CostTrackerPlugin());
462
- ```
463
-
464
- ---
465
-
466
- ## 8. Event System
467
-
468
- The `Crewx` class extends `TypedEventEmitter`. Events are emitted automatically during `query()` and `execute()`.
469
-
470
- ### Event Catalog
471
-
472
- | Event | When | Key Payload Fields |
473
- |-------|------|--------------------|
474
- | `task:start` | `query()`/`execute()` begins | `traceId`, `agentRef`, `mode`, `pid`, `model`, `provider`, `message`, `timestamp` |
475
- | `task:end` | Call completes (success or failure) | `traceId`, `agentRef`, `durationMs`, `result`, `error`, `inputTokens`, `outputTokens`, `costUsd`, `model` |
476
- | `task:output` | Each line of provider output | `traceId`, `agentRef`, `output`, `level` (`stdout`\|`stderr`\|`info`) |
477
-
478
- All events share `traceId` (format: `tsk_XXXXXXXX`) and `timestamp` inherited from `BaseEvent`.
479
-
480
- ### `crewx.on(event, listener)` → `UnsubscribeFn`
481
-
482
- ```typescript
483
- import type { TaskEndEvent } from '@crewx/sdk';
484
-
485
- const unsub = crewx.on('task:end', (e: TaskEndEvent) => {
486
- console.log(`${e.agentRef} finished in ${e.durationMs}ms | tokens: ${e.inputTokens}+${e.outputTokens}`);
487
- });
488
-
489
- // Remove listener when no longer needed
490
- unsub();
491
- ```
492
-
493
- `crewx.once(event, listener)` fires exactly once and auto-unsubscribes.
494
-
495
- > **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.
496
-
497
- ---
498
-
499
- ## 9. Type Reference
500
-
501
- ### `CrewxOptions`
502
-
503
- ```typescript
504
- interface CrewxOptions {
505
- workspaceRoot?: string;
506
- platform?: 'cli' | 'slack' | 'api';
507
- execPolicy?: ExecPolicy; // Allowed/denied shell commands in {{exec}}
508
- }
509
- ```
510
-
511
- ### `QueryOptions` / `ExecuteOptions`
512
-
513
- ```typescript
514
- interface QueryOptions {
515
- model?: string; // Override model (e.g. 'claude-opus-4-6')
516
- provider?: string; // Override provider (e.g. 'cli/gemini')
517
- context?: string; // Extra context prepended to message
518
- metadata?: Record<string, unknown>;
519
- }
520
- // ExecuteOptions has the same shape
521
- ```
522
-
523
- ### `QueryResult` / `ExecuteResult`
524
-
525
- ```typescript
526
- interface QueryResult {
527
- ok: boolean;
528
- data: string; // Agent's response text
529
- error?: {
530
- code: string; // 'AGENT_NOT_FOUND' | 'PROVIDER_ERROR' | 'QUERY_FAILED'
531
- message: string;
532
- };
533
- meta: {
534
- agentId: string;
535
- provider: string;
536
- model?: string;
537
- durationMs: number;
538
- };
539
- }
540
- // ExecuteResult has the same shape
541
- ```
542
-
543
- ### `AgentConfig`
544
-
545
- ```typescript
546
- interface AgentConfig {
547
- id: string;
548
- name?: string;
549
- role?: string;
550
- team?: string;
551
- provider: string | string[];
552
- working_directory?: string;
553
- description?: string;
554
- inline?: {
555
- model?: string;
556
- system_prompt?: string;
557
- prompt?: string;
558
- layout?: string | { id: string; props?: Record<string, unknown> };
559
- };
560
- }
561
- ```
562
-
563
- ### `ExecPolicy`
564
-
565
- ```typescript
566
- interface ExecPolicy {
567
- allow: string[]; // Glob patterns for allowed commands
568
- deny: string[]; // Glob patterns for denied commands (takes precedence)
569
- }
570
- ```
571
-
572
- ---
573
-
574
- ## Coming Soon
575
-
576
- - **Tool System** — attach custom tools/functions to agents (§3 of design spec)
577
- - **`api/*` Providers** — direct API calls without CLI dependency
578
-
579
- ---
580
-
581
- ## Requirements
582
-
583
- - Node.js >= 20.19.0
584
- - At least one CLI provider installed (e.g. `npm i -g @anthropic-ai/claude-code`)
1
+ # @crewx/sdk
2
+
3
+ CrewX SDK — load `crewx.yaml` and run AI agents from TypeScript/JavaScript.
4
+
5
+ > **Status**: `0.9.0-alpha` — Core API, Plugin system, and Event system are stable. Tool system coming soon.
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Quick Start](#1-quick-start)
10
+ 2. [Core API](#2-core-api)
11
+ 3. [crewx.yaml Structure](#3-crewxyaml-structure)
12
+ 4. [Layout System](#4-layout-system)
13
+ 5. [Template Helpers](#5-template-helpers)
14
+ 6. [Provider Bridge](#6-provider-bridge)
15
+ 7. [Plugin System](#7-plugin-system)
16
+ 8. [Event System](#8-event-system)
17
+ 9. [Type Reference](#9-type-reference)
18
+
19
+ ---
20
+
21
+ ## 1. Quick Start
22
+
23
+ ### Install
24
+
25
+ ```bash
26
+ npm install @crewx/sdk
27
+ ```
28
+
29
+ ### Hello World
30
+
31
+ ```typescript
32
+ import { Crewx } from '@crewx/sdk';
33
+
34
+ const crewx = await Crewx.loadYaml('./crewx.yaml');
35
+
36
+ const result = await crewx.query('assistant', 'Hello! What can you do?');
37
+
38
+ if (result.ok) {
39
+ console.log(result.data); // agent's response text
40
+ } else {
41
+ console.error(result.error?.message);
42
+ }
43
+ ```
44
+
45
+ ### Minimal `crewx.yaml`
46
+
47
+ ```yaml
48
+ agents:
49
+ assistant:
50
+ name: Assistant
51
+ provider: cli/claude
52
+ inline:
53
+ model: claude-sonnet-4-6
54
+ prompt: |
55
+ You are a helpful assistant.
56
+ Answer concisely.
57
+ ```
58
+
59
+ ### Task Execution (with side effects)
60
+
61
+ ```typescript
62
+ const crewx = await Crewx.loadYaml('./crewx.yaml');
63
+
64
+ // execute() allows the agent to write files, run commands, etc.
65
+ const result = await crewx.execute('assistant', 'Refactor src/utils.ts');
66
+
67
+ console.log(`Done in ${result.meta.durationMs}ms`);
68
+ console.log(result.data);
69
+ ```
70
+
71
+ ### Plugin Setup (optional)
72
+
73
+ ```typescript
74
+ import { FileLoggerPlugin } from '@crewx/cli/plugins/file-logger';
75
+ import { SqliteTracingPlugin } from '@crewx/cli/plugins/sqlite-tracing';
76
+
77
+ await crewx.use(new FileLoggerPlugin()); // log files → .crewx/logs/
78
+ await crewx.use(new SqliteTracingPlugin()); // task records → ~/.crewx/crewx.db
79
+ // ... run tasks ...
80
+ await crewx.close(); // flush all plugins on exit
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 2. Core API
86
+
87
+ ### `Crewx.loadYaml(path, options?)`
88
+
89
+ Load from a `crewx.yaml` file. Documents referenced in the config are loaded automatically.
90
+
91
+ ```typescript
92
+ const crewx = await Crewx.loadYaml('./crewx.yaml');
93
+
94
+ // With options
95
+ const crewx = await Crewx.loadYaml('./crewx.yaml', {
96
+ execPolicy: { allow: ['git *', 'npm run *'], deny: ['rm *'] },
97
+ });
98
+ ```
99
+
100
+ ### `Crewx.fromConfig(config, options?, projectRoot?)`
101
+
102
+ Create from an already-parsed config object. Useful when you load YAML yourself or build config programmatically.
103
+
104
+ ```typescript
105
+ import { Crewx, CrewxProjectConfig } from '@crewx/sdk';
106
+
107
+ const config: CrewxProjectConfig = {
108
+ agents: [
109
+ { id: 'bot', provider: 'cli/claude', inline: { prompt: 'You are a bot.' } },
110
+ ],
111
+ };
112
+
113
+ const crewx = await Crewx.fromConfig(config, {}, process.cwd());
114
+ ```
115
+
116
+ ### `crewx.query(agentRef, message, options?)`
117
+
118
+ Ask an agent a question. Read-only — the agent is expected to respond with text only.
119
+
120
+ ```typescript
121
+ const result = await crewx.query('assistant', 'Summarize this PR');
122
+
123
+ // With options
124
+ const result = await crewx.query('assistant', 'Translate to Korean', {
125
+ model: 'claude-opus-4-6', // override model
126
+ provider: 'cli/claude', // override provider
127
+ context: 'Additional context', // prepended to the message
128
+ });
129
+ ```
130
+
131
+ > **Note**: `'@assistant'` 형태도 동작합니다 (CLI 호환). SDK에서는 bare id가 권장됩니다.
132
+
133
+ **Returns**: [`QueryResult`](#queryresult)
134
+
135
+ ### `crewx.execute(agentRef, message, options?)`
136
+
137
+ Run a task with an agent. The agent may write files, run shell commands, etc. Uses `--dangerously-skip-permissions` for `cli/claude`.
138
+
139
+ ```typescript
140
+ const result = await crewx.execute('coder', 'Fix the failing tests in src/');
141
+
142
+ if (!result.ok) {
143
+ console.error('Failed:', result.error?.code, result.error?.message);
144
+ }
145
+ ```
146
+
147
+ **Returns**: [`ExecuteResult`](#executeresult)
148
+
149
+ ### `crewx.renderAgentPromptFull(agentId, options?)`
150
+
151
+ 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.
152
+
153
+ ```typescript
154
+ const prompt = await crewx.renderAgentPromptFull('assistant');
155
+ console.log(prompt);
156
+
157
+ // With layout override
158
+ const prompt = await crewx.renderAgentPromptFull('assistant', {
159
+ layout: 'crewx/minimal',
160
+ session: { mode: 'execute', platform: 'api' },
161
+ });
162
+ ```
163
+
164
+ ### `crewx.registerLayout(name, template)`
165
+
166
+ Register a custom layout at runtime. See [Layout System](#4-layout-system).
167
+
168
+ ```typescript
169
+ crewx.registerLayout('my-layout', `
170
+ # {{agent.name}}
171
+ {{agent.inline.prompt}}
172
+ ---
173
+ Session: {{session.mode}}
174
+ `);
175
+
176
+ const prompt = await crewx.renderAgentPromptFull('assistant', {
177
+ layout: 'my-layout',
178
+ });
179
+ ```
180
+
181
+ ### `crewx.agents`
182
+
183
+ `ReadonlyMap<string, AgentConfig>` — all agents loaded from `crewx.yaml`.
184
+
185
+ ```typescript
186
+ // List all agents
187
+ for (const [id, agent] of crewx.agents) {
188
+ console.log(id, agent.provider);
189
+ }
190
+
191
+ // Check if an agent exists
192
+ if (crewx.agents.has('coder')) {
193
+ // ...
194
+ }
195
+ ```
196
+
197
+ ### `crewx.filterAgents(filters)`
198
+
199
+ Filter agents by role, team, or provider. Supports glob patterns.
200
+
201
+ ```typescript
202
+ // All agents on the 'backend' team
203
+ const agents = crewx.filterAgents({ team: 'backend' });
204
+
205
+ // All Claude-based agents
206
+ const agents = crewx.filterAgents({ provider: 'cli/claude' });
207
+
208
+ // Wildcard: all cli/* providers
209
+ const agents = crewx.filterAgents({ provider: 'cli/*' });
210
+ ```
211
+
212
+ ---
213
+
214
+ ## 3. `crewx.yaml` Structure
215
+
216
+ ```yaml
217
+ # ─── Agents ──────────────────────────────────────────────────
218
+ agents:
219
+ my_agent:
220
+ name: My Agent # Display name (optional)
221
+ role: Backend Developer # Role label (optional)
222
+ team: core # Team label (optional)
223
+ provider: cli/claude # Provider (required)
224
+ working_directory: . # Working dir for the agent
225
+
226
+ inline: # Inline agent definition
227
+ model: claude-sonnet-4-6 # Model override (optional)
228
+ prompt: | # System prompt (Handlebars template)
229
+ You are an expert {{agent.role}}.
230
+ Today's context: {{{documents.guidelines.content}}}
231
+ layout: crewx/minimal # Layout override for this agent (optional)
232
+
233
+ # ─── Layouts ─────────────────────────────────────────────────
234
+ layouts:
235
+ default: crewx/minimal # Project-level default layout
236
+
237
+ # ─── Documents ───────────────────────────────────────────────
238
+ documents:
239
+ guidelines:
240
+ path: ./docs/guidelines.md # Loaded at startup, available as documents.guidelines
241
+ api_spec:
242
+ path: ./docs/api.md
243
+ ```
244
+
245
+ ### Multiple providers
246
+
247
+ ```yaml
248
+ agents:
249
+ polyglot:
250
+ provider: # Array = first is primary, rest are fallbacks (future)
251
+ - cli/claude
252
+ - cli/gemini
253
+ ```
254
+
255
+ ---
256
+
257
+ ## 4. Layout System
258
+
259
+ Layouts wrap the agent's raw prompt with structure — identity blocks, session info, available tools, etc.
260
+
261
+ ### Built-in Layouts
262
+
263
+ | ID | Description |
264
+ |----|-------------|
265
+ | `crewx/default` | Full structured layout (identity + session + prompt) |
266
+ | `crewx/minimal` | Minimal wrapper — just the agent prompt |
267
+
268
+ ### Resolution Priority
269
+
270
+ When calling `renderAgentPromptFull()`, the layout is resolved in this order (first match wins):
271
+
272
+ 1. `options.layout` — call-site override
273
+ 2. `agent.inline.layout` — per-agent definition in `crewx.yaml`
274
+ 3. `config.layouts.default` — project-level default
275
+ 4. `crewx/default` — SDK built-in fallback
276
+
277
+ ### Custom Layout via `registerLayout()`
278
+
279
+ ```typescript
280
+ crewx.registerLayout('compact', `
281
+ ## {{agent.name}} ({{agent.role}})
282
+ {{agent.inline.prompt}}
283
+ `);
284
+
285
+ const prompt = await crewx.renderAgentPromptFull('assistant', {
286
+ layout: 'compact',
287
+ });
288
+ ```
289
+
290
+ ### Inline Layout in `crewx.yaml`
291
+
292
+ ```yaml
293
+ agents:
294
+ assistant:
295
+ provider: cli/claude
296
+ inline:
297
+ prompt: You are helpful.
298
+ layout:
299
+ id: crewx/default
300
+ props:
301
+ show_skills: false # Pass props to the layout template
302
+ ```
303
+
304
+ ### Inline Template String
305
+
306
+ ```typescript
307
+ const prompt = await crewx.renderAgentPromptFull('assistant', {
308
+ layout: { template: 'SYSTEM: {{agent.inline.prompt}}' },
309
+ });
310
+ ```
311
+
312
+ ---
313
+
314
+ ## 5. Template Helpers
315
+
316
+ The `inline.prompt` field in `crewx.yaml` is a **Handlebars template**. These helpers are available:
317
+
318
+ ### P0 Helpers (Core)
319
+
320
+ | Helper | Usage | Description |
321
+ |--------|-------|-------------|
322
+ | `exec` | `{{exec "git log --oneline -5"}}` | Run shell command and inline output |
323
+ | `include` | `{{include someVar}}` | Include a string variable as-is (no escaping) |
324
+ | `fenced_code` | `{{fenced_code content lang="ts"}}` | Wrap content in Markdown code block |
325
+
326
+ ### Condition Helpers
327
+
328
+ ```handlebars
329
+ {{#if (eq agent.team "backend")}}Backend mode{{/if}}
330
+ {{#if (and featureA featureB)}}Both enabled{{/if}}
331
+ ```
332
+
333
+ Available: `eq`, `ne`, `and`, `or`, `not`, `contains`
334
+
335
+ ### Utility Helpers
336
+
337
+ | Helper | Description |
338
+ |--------|-------------|
339
+ | `truncate text len` | Truncate string to N chars |
340
+ | `length array` | Array/string length |
341
+ | `escapeHandlebars text` | Escape `{{` in content |
342
+ | `formatFileSize bytes` | `1048576` → `1 MB` |
343
+ | `formatTimestamp ms` | Unix ms → readable date |
344
+
345
+ ### Document Access in Templates
346
+
347
+ Documents defined in `crewx.yaml` are automatically available:
348
+
349
+ ```handlebars
350
+ # Inline the full document
351
+ {{{documents.guidelines.content}}}
352
+
353
+ # Access metadata
354
+ Path: {{documents.guidelines.path}}
355
+ ```
356
+
357
+ ### `exec` Security Policy
358
+
359
+ Control which shell commands are allowed in templates:
360
+
361
+ ```typescript
362
+ const crewx = await Crewx.loadYaml('./crewx.yaml', {
363
+ execPolicy: {
364
+ allow: ['git *', 'npm run *', 'cat *'],
365
+ deny: ['rm *', 'curl *'],
366
+ },
367
+ });
368
+ ```
369
+
370
+ Or set it in `crewx.yaml`:
371
+
372
+ ```yaml
373
+ settings:
374
+ template:
375
+ exec:
376
+ allow:
377
+ - "git *"
378
+ - "npm run *"
379
+ deny:
380
+ - "rm *"
381
+ ```
382
+
383
+ Glob syntax: `*` matches within a segment, `**` matches across path segments.
384
+
385
+ ---
386
+
387
+ ## 6. Provider Bridge
388
+
389
+ Each agent has a `provider` field that tells the SDK which AI backend to use.
390
+
391
+ ### Supported Providers
392
+
393
+ | Provider | CLI Command | Notes |
394
+ |----------|-------------|-------|
395
+ | `cli/claude` | `claude` | Claude Code CLI |
396
+ | `cli/gemini` | `gemini` | Gemini CLI |
397
+ | `cli/copilot` | `gh copilot suggest` | GitHub Copilot |
398
+ | `cli/codex` | `codex` | OpenAI Codex CLI |
399
+
400
+ > **Coming Soon**: `api/claude`, `api/openai` — direct API providers without CLI dependency.
401
+
402
+ ### Provider Override at Call Site
403
+
404
+ ```typescript
405
+ // Use a different provider for one call
406
+ const result = await crewx.query('assistant', 'Hello', {
407
+ provider: 'cli/gemini',
408
+ model: 'gemini-2.0-flash',
409
+ });
410
+ ```
411
+
412
+ ### `query` vs `execute` Mode
413
+
414
+ | | `query()` | `execute()` |
415
+ |--|-----------|-------------|
416
+ | Intent | Read-only Q&A | Task with side effects |
417
+ | cli/claude flags | `-p --output-format stream-json --verbose` | + `--dangerously-skip-permissions` |
418
+ | Use when | Asking questions, generating text | Writing files, running commands |
419
+
420
+ ---
421
+
422
+ ## 7. Plugin System
423
+
424
+ Plugins extend `CrewxPlugin` from `@crewx/sdk` and attach event listeners in `attach()`. Register with `crewx.use(plugin)` and release resources by calling `crewx.close()`.
425
+
426
+ ### Lifecycle
427
+
428
+ ```typescript
429
+ await crewx.use(plugin); // calls plugin.attach(crewx) — subscribe events, open DB, etc.
430
+ await crewx.close(); // calls plugin.detach(crewx) on all plugins in LIFO order
431
+ ```
432
+
433
+ Same plugin instance registered twice is silently ignored.
434
+
435
+ ### Built-in Plugins (`@crewx/cli`)
436
+
437
+ | Plugin | Import path | Storage |
438
+ |--------|-------------|---------|
439
+ | `FileLoggerPlugin` | `@crewx/cli/plugins/file-logger` | `.crewx/logs/{ts}_{traceId}.log` (one file per task) |
440
+ | `SqliteTracingPlugin` | `@crewx/cli/plugins/sqlite-tracing` | `~/.crewx/crewx.db` — `tasks` table |
441
+
442
+ > The `crewx` CLI auto-attaches both plugins for every run. No setup needed when using the CLI directly.
443
+
444
+ ### Custom Plugin
445
+
446
+ ```typescript
447
+ import { CrewxPlugin } from '@crewx/sdk';
448
+ import type { Crewx } from '@crewx/sdk';
449
+
450
+ class CostTrackerPlugin extends CrewxPlugin {
451
+ readonly name = 'cost-tracker';
452
+
453
+ attach(crewx: Crewx) {
454
+ crewx.on('task:end', (e) => {
455
+ if (e.costUsd) console.log(`[cost] ${e.agentRef}: $${e.costUsd.toFixed(4)}`);
456
+ });
457
+ }
458
+ // detach() is optional — base class no-op is sufficient if no cleanup needed
459
+ }
460
+
461
+ await crewx.use(new CostTrackerPlugin());
462
+ ```
463
+
464
+ ---
465
+
466
+ ## 8. Event System
467
+
468
+ The `Crewx` class extends `TypedEventEmitter`. Events are emitted automatically during `query()` and `execute()`.
469
+
470
+ ### Event Catalog
471
+
472
+ | Event | When | Key Payload Fields |
473
+ |-------|------|--------------------|
474
+ | `task:start` | `query()`/`execute()` begins | `traceId`, `agentRef`, `mode`, `pid`, `model`, `provider`, `message`, `timestamp` |
475
+ | `task:end` | Call completes (success or failure) | `traceId`, `agentRef`, `durationMs`, `result`, `error`, `inputTokens`, `outputTokens`, `costUsd`, `model` |
476
+ | `task:output` | Each line of provider output | `traceId`, `agentRef`, `output`, `level` (`stdout`\|`stderr`\|`info`) |
477
+
478
+ All events share `traceId` (format: `tsk_XXXXXXXX`) and `timestamp` inherited from `BaseEvent`.
479
+
480
+ ### `crewx.on(event, listener)` → `UnsubscribeFn`
481
+
482
+ ```typescript
483
+ import type { TaskEndEvent } from '@crewx/sdk';
484
+
485
+ const unsub = crewx.on('task:end', (e: TaskEndEvent) => {
486
+ console.log(`${e.agentRef} finished in ${e.durationMs}ms | tokens: ${e.inputTokens}+${e.outputTokens}`);
487
+ });
488
+
489
+ // Remove listener when no longer needed
490
+ unsub();
491
+ ```
492
+
493
+ `crewx.once(event, listener)` fires exactly once and auto-unsubscribes.
494
+
495
+ > **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.
496
+
497
+ ---
498
+
499
+ ## 9. Type Reference
500
+
501
+ ### `CrewxOptions`
502
+
503
+ ```typescript
504
+ interface CrewxOptions {
505
+ workspaceRoot?: string;
506
+ platform?: 'cli' | 'slack' | 'api';
507
+ execPolicy?: ExecPolicy; // Allowed/denied shell commands in {{exec}}
508
+ }
509
+ ```
510
+
511
+ ### `QueryOptions` / `ExecuteOptions`
512
+
513
+ ```typescript
514
+ interface QueryOptions {
515
+ model?: string; // Override model (e.g. 'claude-opus-4-6')
516
+ provider?: string; // Override provider (e.g. 'cli/gemini')
517
+ context?: string; // Extra context prepended to message
518
+ metadata?: Record<string, unknown>;
519
+ }
520
+ // ExecuteOptions has the same shape
521
+ ```
522
+
523
+ ### `QueryResult` / `ExecuteResult`
524
+
525
+ ```typescript
526
+ interface QueryResult {
527
+ ok: boolean;
528
+ data: string; // Agent's response text
529
+ error?: {
530
+ code: string; // 'AGENT_NOT_FOUND' | 'PROVIDER_ERROR' | 'QUERY_FAILED'
531
+ message: string;
532
+ };
533
+ meta: {
534
+ agentId: string;
535
+ provider: string;
536
+ model?: string;
537
+ durationMs: number;
538
+ };
539
+ }
540
+ // ExecuteResult has the same shape
541
+ ```
542
+
543
+ ### `AgentConfig`
544
+
545
+ ```typescript
546
+ interface AgentConfig {
547
+ id: string;
548
+ name?: string;
549
+ role?: string;
550
+ team?: string;
551
+ provider: string | string[];
552
+ working_directory?: string;
553
+ description?: string;
554
+ inline?: {
555
+ model?: string;
556
+ system_prompt?: string;
557
+ prompt?: string;
558
+ layout?: string | { id: string; props?: Record<string, unknown> };
559
+ };
560
+ }
561
+ ```
562
+
563
+ ### `ExecPolicy`
564
+
565
+ ```typescript
566
+ interface ExecPolicy {
567
+ allow: string[]; // Glob patterns for allowed commands
568
+ deny: string[]; // Glob patterns for denied commands (takes precedence)
569
+ }
570
+ ```
571
+
572
+ ---
573
+
574
+ ## Coming Soon
575
+
576
+ - **Tool System** — attach custom tools/functions to agents (§3 of design spec)
577
+ - **`api/*` Providers** — direct API calls without CLI dependency
578
+
579
+ ---
580
+
581
+ ## Requirements
582
+
583
+ - Node.js >= 20.19.0
584
+ - At least one CLI provider installed (e.g. `npm i -g @anthropic-ai/claude-code`)