@agentxjs/devtools 1.9.6-dev → 2.0.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/dist/index.d.ts CHANGED
@@ -15,9 +15,11 @@ import '@agentxjs/core/agent';
15
15
  * ```typescript
16
16
  * import { createDevtools } from "@agentxjs/devtools";
17
17
  *
18
+ * import { env } from "@agentxjs/devtools";
19
+ *
18
20
  * const devtools = createDevtools({
19
21
  * fixturesDir: "./fixtures",
20
- * apiKey: process.env.DEEPRACTICE_API_KEY,
22
+ * apiKey: env.apiKey,
21
23
  * });
22
24
  *
23
25
  * // Has fixture → playback (MockDriver)
@@ -196,12 +198,43 @@ interface VcrCreateDriverConfig {
196
198
  * // Before each test:
197
199
  * currentFixture = "test-scenario-name";
198
200
  *
199
- * // Use with server/provider:
200
- * const provider = await createNodeProvider({
201
+ * // Use with server:
202
+ * const platform = await createNodePlatform({...});
203
+ * const server = await createServer({
204
+ * platform,
201
205
  * createDriver: vcrCreateDriver,
202
206
  * });
203
207
  * ```
204
208
  */
205
209
  declare function createVcrCreateDriver(config: VcrCreateDriverConfig): CreateDriver;
206
210
 
207
- export { Devtools, type DevtoolsConfig, type DriverOptions, Fixture, type VcrCreateDriverConfig, createDevtools, createVcrCreateDriver };
211
+ /**
212
+ * Unified environment configuration for devtools
213
+ *
214
+ * Single source of truth for API credentials and model settings.
215
+ * Automatically loads .env / .env.local from monorepo root on import.
216
+ *
217
+ * All devtools modules (VCR, BDD, Devtools SDK) should use this
218
+ * instead of reading process.env directly.
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * import { env } from "@agentxjs/devtools";
223
+ *
224
+ * const driver = createMonoDriver({
225
+ * apiKey: env.apiKey!,
226
+ * baseUrl: env.baseUrl,
227
+ * model: env.model,
228
+ * });
229
+ * ```
230
+ */
231
+ declare const env: {
232
+ /** Deepractice API key */
233
+ readonly apiKey: string | undefined;
234
+ /** Deepractice API base URL */
235
+ readonly baseUrl: string | undefined;
236
+ /** Model identifier */
237
+ readonly model: string;
238
+ };
239
+
240
+ export { Devtools, type DevtoolsConfig, type DriverOptions, Fixture, type VcrCreateDriverConfig, createDevtools, createVcrCreateDriver, env };
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  MockDriver,
3
3
  createMockDriver
4
- } from "./chunk-SQDCFUA3.js";
4
+ } from "./chunk-J6L73HM5.js";
5
5
  import {
6
6
  RecordingDriver,
7
7
  createRecordingDriver
8
- } from "./chunk-YRTTCKHM.js";
8
+ } from "./chunk-DR45HEV4.js";
9
9
  import {
10
10
  BUILTIN_FIXTURES,
11
11
  EMPTY_RESPONSE,
@@ -16,6 +16,10 @@ import {
16
16
  getFixture,
17
17
  listFixtures
18
18
  } from "./chunk-6OHXS7LW.js";
19
+ import {
20
+ env
21
+ } from "./chunk-S7J75AXG.js";
22
+ import "./chunk-DGUM43GV.js";
19
23
 
20
24
  // src/Devtools.ts
21
25
  import { createLogger } from "commonxjs/logger";
@@ -173,16 +177,7 @@ function createDevtools(config) {
173
177
  return new Devtools(config);
174
178
  }
175
179
  function createVcrCreateDriver(config) {
176
- const {
177
- fixturesDir,
178
- getFixtureName,
179
- apiKey,
180
- baseUrl,
181
- model,
182
- onPlayback,
183
- onRecording,
184
- onSaved
185
- } = config;
180
+ const { fixturesDir, getFixtureName, apiKey, baseUrl, model, onPlayback, onRecording, onSaved } = config;
186
181
  const realCreateDriver = config.createRealDriver || null;
187
182
  return (driverConfig) => {
188
183
  const fixtureName = getFixtureName();
@@ -267,6 +262,7 @@ export {
267
262
  createMockDriver,
268
263
  createRecordingDriver,
269
264
  createVcrCreateDriver,
265
+ env,
270
266
  getFixture,
271
267
  listFixtures
272
268
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Devtools.ts"],"sourcesContent":["/**\n * Devtools SDK - VCR-style fixture management\n *\n * Automatically uses existing fixtures or records new ones on-the-fly.\n *\n * Usage:\n * ```typescript\n * import { createDevtools } from \"@agentxjs/devtools\";\n *\n * const devtools = createDevtools({\n * fixturesDir: \"./fixtures\",\n * apiKey: process.env.DEEPRACTICE_API_KEY,\n * });\n *\n * // Has fixture → playback (MockDriver)\n * // No fixture → call API, record, save, return MockDriver\n * const driver = await devtools.driver(\"hello-test\", {\n * message: \"Hello!\",\n * });\n *\n * await driver.initialize();\n * for await (const event of driver.receive({ content: \"Hello\" })) {\n * console.log(event);\n * }\n * ```\n */\n\nimport type { Driver, CreateDriver, DriverConfig } from \"@agentxjs/core/driver\";\nimport type { Fixture } from \"./types\";\nimport { MockDriver } from \"./mock/MockDriver\";\nimport { RecordingDriver } from \"./recorder/RecordingDriver\";\nimport { createLogger } from \"commonxjs/logger\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\nconst logger = createLogger(\"devtools/Devtools\");\n\n/**\n * Devtools configuration\n */\nexport interface DevtoolsConfig {\n /**\n * Directory to store/load fixtures\n */\n fixturesDir: string;\n\n /**\n * API key for recording (required if recording)\n */\n apiKey?: string;\n\n /**\n * API base URL\n */\n baseUrl?: string;\n\n /**\n * Default model\n */\n model?: string;\n\n /**\n * Default system prompt\n */\n systemPrompt?: string;\n\n /**\n * Working directory for tool execution\n */\n cwd?: string;\n\n /**\n * Real driver factory for recording\n * If not provided, will try to use @agentxjs/claude-driver\n */\n createDriver?: CreateDriver;\n}\n\n/**\n * Options for getting a driver\n */\nexport interface DriverOptions {\n /**\n * Message to send if recording is needed\n */\n message: string;\n\n /**\n * Override system prompt\n */\n systemPrompt?: string;\n\n /**\n * Override working directory\n */\n cwd?: string;\n\n /**\n * Force re-record even if fixture exists\n */\n forceRecord?: boolean;\n}\n\n/**\n * Devtools SDK\n */\nexport class Devtools {\n private config: DevtoolsConfig;\n private realCreateDriver: CreateDriver | null = null;\n\n constructor(config: DevtoolsConfig) {\n this.config = config;\n logger.info(\"Devtools initialized\", { fixturesDir: config.fixturesDir });\n }\n\n /**\n * Get a driver for the given fixture name\n *\n * - If fixture exists → returns MockDriver with playback\n * - If fixture doesn't exist → records, saves, returns MockDriver\n */\n async driver(name: string, options: DriverOptions): Promise<Driver> {\n const fixturePath = this.getFixturePath(name);\n\n // Check if fixture exists\n if (!options.forceRecord && existsSync(fixturePath)) {\n logger.info(\"Loading existing fixture\", { name, path: fixturePath });\n const fixture = await this.loadFixture(fixturePath);\n return new MockDriver({ fixture });\n }\n\n // Need to record\n logger.info(\"Recording new fixture\", { name, message: options.message });\n const fixture = await this.record(name, options);\n return new MockDriver({ fixture });\n }\n\n /**\n * Get a CreateDriver function that uses a pre-loaded fixture\n *\n * NOTE: This loads the fixture synchronously, so the fixture must exist.\n * For async loading/recording, use driver() instead.\n */\n createDriverForFixture(fixturePath: string): CreateDriver {\n // Load fixture synchronously (requires existing fixture)\n const content = readFileSync(this.getFixturePath(fixturePath), \"utf-8\");\n const fixture = JSON.parse(content) as Fixture;\n\n return (_config: DriverConfig) => {\n return new MockDriver({ fixture });\n };\n }\n\n /**\n * Record a fixture\n */\n async record(name: string, options: DriverOptions): Promise<Fixture> {\n const createDriver = await this.getRealCreateDriver();\n\n const agentId = `record-${name}`;\n\n // Create driver config\n const driverConfig: DriverConfig = {\n apiKey: this.config.apiKey!,\n baseUrl: this.config.baseUrl,\n agentId,\n model: this.config.model,\n systemPrompt: options.systemPrompt || this.config.systemPrompt || \"You are a helpful assistant.\",\n cwd: options.cwd || this.config.cwd || process.cwd(),\n };\n\n // Create real driver\n const realDriver = createDriver(driverConfig);\n\n // Wrap with recorder\n const recorder = new RecordingDriver({\n driver: realDriver,\n name,\n description: `Recording of: \"${options.message}\"`,\n });\n\n // Initialize\n await recorder.initialize();\n\n try {\n // Build user message\n const userMessage = {\n id: `msg_${Date.now()}`,\n role: \"user\" as const,\n subtype: \"user\" as const,\n content: options.message,\n timestamp: Date.now(),\n };\n\n // Send message and collect all events\n for await (const event of recorder.receive(userMessage)) {\n logger.debug(\"Recording event\", { type: event.type });\n\n // Check for completion\n if (event.type === \"message_stop\") {\n break;\n }\n\n // Check for error\n if (event.type === \"error\") {\n const errorData = event.data as { message?: string };\n throw new Error(`Recording error: ${errorData.message}`);\n }\n }\n\n // Get fixture\n const fixture = recorder.getFixture();\n\n // Save fixture\n await this.saveFixture(name, fixture);\n\n return fixture;\n } finally {\n // Cleanup\n await recorder.dispose();\n }\n }\n\n /**\n * Load a fixture by name\n */\n async load(name: string): Promise<Fixture> {\n const fixturePath = this.getFixturePath(name);\n return this.loadFixture(fixturePath);\n }\n\n /**\n * Check if a fixture exists\n */\n exists(name: string): boolean {\n return existsSync(this.getFixturePath(name));\n }\n\n /**\n * Delete a fixture\n */\n async delete(name: string): Promise<void> {\n const fixturePath = this.getFixturePath(name);\n if (existsSync(fixturePath)) {\n const { unlink } = await import(\"node:fs/promises\");\n await unlink(fixturePath);\n logger.info(\"Fixture deleted\", { name });\n }\n }\n\n // ==================== Private ====================\n\n private getFixturePath(name: string): string {\n // If name is already a path, use it directly\n if (name.endsWith(\".json\")) {\n return name;\n }\n return join(this.config.fixturesDir, `${name}.json`);\n }\n\n private async loadFixture(path: string): Promise<Fixture> {\n const content = await readFile(path, \"utf-8\");\n return JSON.parse(content) as Fixture;\n }\n\n private async saveFixture(name: string, fixture: Fixture): Promise<void> {\n const path = this.getFixturePath(name);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, JSON.stringify(fixture, null, 2), \"utf-8\");\n logger.info(\"Fixture saved\", { name, path, eventCount: fixture.events.length });\n }\n\n private async getRealCreateDriver(): Promise<CreateDriver> {\n if (this.realCreateDriver) {\n return this.realCreateDriver;\n }\n\n if (this.config.createDriver) {\n this.realCreateDriver = this.config.createDriver;\n return this.realCreateDriver;\n }\n\n // Validate API key\n if (!this.config.apiKey) {\n throw new Error(\n \"apiKey is required for recording. Set it in DevtoolsConfig or provide a createDriver.\"\n );\n }\n\n // Try to import claude-driver\n try {\n const { createClaudeDriver } = await import(\"@agentxjs/claude-driver\");\n this.realCreateDriver = createClaudeDriver;\n return this.realCreateDriver;\n } catch {\n throw new Error(\"@agentxjs/claude-driver not found. Install it or provide a createDriver.\");\n }\n }\n}\n\n/**\n * Create a Devtools instance\n */\nexport function createDevtools(config: DevtoolsConfig): Devtools {\n return new Devtools(config);\n}\n\n// ============================================================================\n// VCR CreateDriver Factory\n// ============================================================================\n\n/**\n * Configuration for VCR-aware CreateDriver\n */\nexport interface VcrCreateDriverConfig {\n /**\n * Directory to store/load fixtures\n */\n fixturesDir: string;\n\n /**\n * Get current fixture name. Return null to skip VCR (use real driver).\n * Called when driver is created.\n */\n getFixtureName: () => string | null;\n\n /**\n * API key for recording\n */\n apiKey?: string;\n\n /**\n * API base URL\n */\n baseUrl?: string;\n\n /**\n * Default model\n */\n model?: string;\n\n /**\n * Real driver factory (optional, defaults to @agentxjs/claude-driver)\n */\n createRealDriver?: CreateDriver;\n\n /**\n * Called when playback mode is used\n */\n onPlayback?: (fixtureName: string) => void;\n\n /**\n * Called when recording mode is used\n */\n onRecording?: (fixtureName: string) => void;\n\n /**\n * Called when fixture is saved\n */\n onSaved?: (fixtureName: string, eventCount: number) => void;\n}\n\n/**\n * Create a VCR-aware CreateDriver function\n *\n * VCR logic (hardcoded):\n * - Fixture exists → Playback (MockDriver)\n * - Fixture missing → Recording (RecordingDriver) → Auto-save on dispose\n *\n * @example\n * ```typescript\n * let currentFixture: string | null = null;\n *\n * const vcrCreateDriver = createVcrCreateDriver({\n * fixturesDir: \"./fixtures\",\n * getFixtureName: () => currentFixture,\n * apiKey: process.env.API_KEY,\n * });\n *\n * // Before each test:\n * currentFixture = \"test-scenario-name\";\n *\n * // Use with server/provider:\n * const provider = await createNodeProvider({\n * createDriver: vcrCreateDriver,\n * });\n * ```\n */\nexport function createVcrCreateDriver(config: VcrCreateDriverConfig): CreateDriver {\n const {\n fixturesDir,\n getFixtureName,\n apiKey,\n baseUrl,\n model,\n onPlayback,\n onRecording,\n onSaved,\n } = config;\n\n // Real driver factory (must be provided or pre-loaded)\n const realCreateDriver: CreateDriver | null = config.createRealDriver || null;\n\n return (driverConfig: DriverConfig): Driver => {\n const fixtureName = getFixtureName();\n\n // No fixture name → use real driver without VCR\n if (!fixtureName) {\n if (!apiKey) {\n throw new Error(\"No fixture name and no API key. Cannot create driver.\");\n }\n\n // Sync: we need to return immediately, so we create the driver with merged config\n // Note: This path is for non-VCR scenarios\n const createDriver = realCreateDriver || config.createRealDriver;\n if (!createDriver) {\n throw new Error(\"No createRealDriver provided and claude-driver not loaded yet.\");\n }\n\n return createDriver({\n ...driverConfig,\n apiKey,\n baseUrl,\n model,\n });\n }\n\n const fixturePath = join(fixturesDir, `${fixtureName}.json`);\n\n // Fixture exists → Playback (MockDriver)\n if (existsSync(fixturePath)) {\n onPlayback?.(fixtureName);\n logger.info(\"VCR Playback\", { fixtureName });\n\n const fixture = JSON.parse(readFileSync(fixturePath, \"utf-8\")) as Fixture;\n return new MockDriver({ fixture });\n }\n\n // No fixture → Recording (RecordingDriver)\n if (!apiKey) {\n throw new Error(\n `No fixture found for \"${fixtureName}\" and no API key for recording. ` +\n `Either create the fixture or provide an API key.`\n );\n }\n\n onRecording?.(fixtureName);\n logger.info(\"VCR Recording\", { fixtureName });\n\n // Get real driver factory (sync - must be pre-loaded or provided)\n const createDriver = realCreateDriver || config.createRealDriver;\n if (!createDriver) {\n throw new Error(\n \"createRealDriver not available. For async loading, ensure claude-driver is pre-loaded.\"\n );\n }\n\n // Create real driver with merged config\n const realDriver = createDriver({\n ...driverConfig,\n apiKey,\n baseUrl,\n model,\n });\n\n // Wrap with RecordingDriver\n const recorder = new RecordingDriver({\n driver: realDriver,\n name: fixtureName,\n description: `VCR recording: ${fixtureName}`,\n });\n\n // Auto-save fixture on dispose\n let fixtureSaved = false;\n const originalDispose = recorder.dispose.bind(recorder);\n\n recorder.dispose = async () => {\n if (!fixtureSaved && recorder.eventCount > 0) {\n try {\n const fixture = recorder.getFixture();\n\n // Ensure directory exists\n const { mkdir, writeFile } = await import(\"node:fs/promises\");\n await mkdir(dirname(fixturePath), { recursive: true });\n await writeFile(fixturePath, JSON.stringify(fixture, null, 2), \"utf-8\");\n\n fixtureSaved = true;\n onSaved?.(fixtureName, recorder.eventCount);\n logger.info(\"VCR Saved\", { fixtureName, eventCount: recorder.eventCount });\n } catch (e) {\n logger.error(\"VCR Save failed\", { fixtureName, error: e });\n }\n }\n\n return originalDispose();\n };\n\n return recorder;\n };\n}\n"],"mappings":"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","names":["fixture","createDriver","mkdir","writeFile"]}
1
+ {"version":3,"sources":["../src/Devtools.ts"],"sourcesContent":["/**\n * Devtools SDK - VCR-style fixture management\n *\n * Automatically uses existing fixtures or records new ones on-the-fly.\n *\n * Usage:\n * ```typescript\n * import { createDevtools } from \"@agentxjs/devtools\";\n *\n * import { env } from \"@agentxjs/devtools\";\n *\n * const devtools = createDevtools({\n * fixturesDir: \"./fixtures\",\n * apiKey: env.apiKey,\n * });\n *\n * // Has fixture → playback (MockDriver)\n * // No fixture → call API, record, save, return MockDriver\n * const driver = await devtools.driver(\"hello-test\", {\n * message: \"Hello!\",\n * });\n *\n * await driver.initialize();\n * for await (const event of driver.receive({ content: \"Hello\" })) {\n * console.log(event);\n * }\n * ```\n */\n\nimport type { Driver, CreateDriver, DriverConfig } from \"@agentxjs/core/driver\";\nimport type { Fixture } from \"./types\";\nimport { MockDriver } from \"./mock/MockDriver\";\nimport { RecordingDriver } from \"./recorder/RecordingDriver\";\nimport { createLogger } from \"commonxjs/logger\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\nconst logger = createLogger(\"devtools/Devtools\");\n\n/**\n * Devtools configuration\n */\nexport interface DevtoolsConfig {\n /**\n * Directory to store/load fixtures\n */\n fixturesDir: string;\n\n /**\n * API key for recording (required if recording)\n */\n apiKey?: string;\n\n /**\n * API base URL\n */\n baseUrl?: string;\n\n /**\n * Default model\n */\n model?: string;\n\n /**\n * Default system prompt\n */\n systemPrompt?: string;\n\n /**\n * Working directory for tool execution\n */\n cwd?: string;\n\n /**\n * Real driver factory for recording\n * If not provided, will try to use @agentxjs/claude-driver\n */\n createDriver?: CreateDriver;\n}\n\n/**\n * Options for getting a driver\n */\nexport interface DriverOptions {\n /**\n * Message to send if recording is needed\n */\n message: string;\n\n /**\n * Override system prompt\n */\n systemPrompt?: string;\n\n /**\n * Override working directory\n */\n cwd?: string;\n\n /**\n * Force re-record even if fixture exists\n */\n forceRecord?: boolean;\n}\n\n/**\n * Devtools SDK\n */\nexport class Devtools {\n private config: DevtoolsConfig;\n private realCreateDriver: CreateDriver | null = null;\n\n constructor(config: DevtoolsConfig) {\n this.config = config;\n logger.info(\"Devtools initialized\", { fixturesDir: config.fixturesDir });\n }\n\n /**\n * Get a driver for the given fixture name\n *\n * - If fixture exists → returns MockDriver with playback\n * - If fixture doesn't exist → records, saves, returns MockDriver\n */\n async driver(name: string, options: DriverOptions): Promise<Driver> {\n const fixturePath = this.getFixturePath(name);\n\n // Check if fixture exists\n if (!options.forceRecord && existsSync(fixturePath)) {\n logger.info(\"Loading existing fixture\", { name, path: fixturePath });\n const fixture = await this.loadFixture(fixturePath);\n return new MockDriver({ fixture });\n }\n\n // Need to record\n logger.info(\"Recording new fixture\", { name, message: options.message });\n const fixture = await this.record(name, options);\n return new MockDriver({ fixture });\n }\n\n /**\n * Get a CreateDriver function that uses a pre-loaded fixture\n *\n * NOTE: This loads the fixture synchronously, so the fixture must exist.\n * For async loading/recording, use driver() instead.\n */\n createDriverForFixture(fixturePath: string): CreateDriver {\n // Load fixture synchronously (requires existing fixture)\n const content = readFileSync(this.getFixturePath(fixturePath), \"utf-8\");\n const fixture = JSON.parse(content) as Fixture;\n\n return (_config: DriverConfig) => {\n return new MockDriver({ fixture });\n };\n }\n\n /**\n * Record a fixture\n */\n async record(name: string, options: DriverOptions): Promise<Fixture> {\n const createDriver = await this.getRealCreateDriver();\n\n const agentId = `record-${name}`;\n\n // Create driver config\n const driverConfig: DriverConfig = {\n apiKey: this.config.apiKey!,\n baseUrl: this.config.baseUrl,\n agentId,\n model: this.config.model,\n systemPrompt:\n options.systemPrompt || this.config.systemPrompt || \"You are a helpful assistant.\",\n cwd: options.cwd || this.config.cwd || process.cwd(),\n };\n\n // Create real driver\n const realDriver = createDriver(driverConfig);\n\n // Wrap with recorder\n const recorder = new RecordingDriver({\n driver: realDriver,\n name,\n description: `Recording of: \"${options.message}\"`,\n });\n\n // Initialize\n await recorder.initialize();\n\n try {\n // Build user message\n const userMessage = {\n id: `msg_${Date.now()}`,\n role: \"user\" as const,\n subtype: \"user\" as const,\n content: options.message,\n timestamp: Date.now(),\n };\n\n // Send message and collect all events\n for await (const event of recorder.receive(userMessage)) {\n logger.debug(\"Recording event\", { type: event.type });\n\n // Check for completion\n if (event.type === \"message_stop\") {\n break;\n }\n\n // Check for error\n if (event.type === \"error\") {\n const errorData = event.data as { message?: string };\n throw new Error(`Recording error: ${errorData.message}`);\n }\n }\n\n // Get fixture\n const fixture = recorder.getFixture();\n\n // Save fixture\n await this.saveFixture(name, fixture);\n\n return fixture;\n } finally {\n // Cleanup\n await recorder.dispose();\n }\n }\n\n /**\n * Load a fixture by name\n */\n async load(name: string): Promise<Fixture> {\n const fixturePath = this.getFixturePath(name);\n return this.loadFixture(fixturePath);\n }\n\n /**\n * Check if a fixture exists\n */\n exists(name: string): boolean {\n return existsSync(this.getFixturePath(name));\n }\n\n /**\n * Delete a fixture\n */\n async delete(name: string): Promise<void> {\n const fixturePath = this.getFixturePath(name);\n if (existsSync(fixturePath)) {\n const { unlink } = await import(\"node:fs/promises\");\n await unlink(fixturePath);\n logger.info(\"Fixture deleted\", { name });\n }\n }\n\n // ==================== Private ====================\n\n private getFixturePath(name: string): string {\n // If name is already a path, use it directly\n if (name.endsWith(\".json\")) {\n return name;\n }\n return join(this.config.fixturesDir, `${name}.json`);\n }\n\n private async loadFixture(path: string): Promise<Fixture> {\n const content = await readFile(path, \"utf-8\");\n return JSON.parse(content) as Fixture;\n }\n\n private async saveFixture(name: string, fixture: Fixture): Promise<void> {\n const path = this.getFixturePath(name);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, JSON.stringify(fixture, null, 2), \"utf-8\");\n logger.info(\"Fixture saved\", { name, path, eventCount: fixture.events.length });\n }\n\n private async getRealCreateDriver(): Promise<CreateDriver> {\n if (this.realCreateDriver) {\n return this.realCreateDriver;\n }\n\n if (this.config.createDriver) {\n this.realCreateDriver = this.config.createDriver;\n return this.realCreateDriver;\n }\n\n // Validate API key\n if (!this.config.apiKey) {\n throw new Error(\n \"apiKey is required for recording. Set it in DevtoolsConfig or provide a createDriver.\"\n );\n }\n\n // Try to import claude-driver\n try {\n const { createClaudeDriver } = await import(\"@agentxjs/claude-driver\");\n this.realCreateDriver = createClaudeDriver;\n return this.realCreateDriver;\n } catch {\n throw new Error(\"@agentxjs/claude-driver not found. Install it or provide a createDriver.\");\n }\n }\n}\n\n/**\n * Create a Devtools instance\n */\nexport function createDevtools(config: DevtoolsConfig): Devtools {\n return new Devtools(config);\n}\n\n// ============================================================================\n// VCR CreateDriver Factory\n// ============================================================================\n\n/**\n * Configuration for VCR-aware CreateDriver\n */\nexport interface VcrCreateDriverConfig {\n /**\n * Directory to store/load fixtures\n */\n fixturesDir: string;\n\n /**\n * Get current fixture name. Return null to skip VCR (use real driver).\n * Called when driver is created.\n */\n getFixtureName: () => string | null;\n\n /**\n * API key for recording\n */\n apiKey?: string;\n\n /**\n * API base URL\n */\n baseUrl?: string;\n\n /**\n * Default model\n */\n model?: string;\n\n /**\n * Real driver factory (optional, defaults to @agentxjs/claude-driver)\n */\n createRealDriver?: CreateDriver;\n\n /**\n * Called when playback mode is used\n */\n onPlayback?: (fixtureName: string) => void;\n\n /**\n * Called when recording mode is used\n */\n onRecording?: (fixtureName: string) => void;\n\n /**\n * Called when fixture is saved\n */\n onSaved?: (fixtureName: string, eventCount: number) => void;\n}\n\n/**\n * Create a VCR-aware CreateDriver function\n *\n * VCR logic (hardcoded):\n * - Fixture exists → Playback (MockDriver)\n * - Fixture missing → Recording (RecordingDriver) → Auto-save on dispose\n *\n * @example\n * ```typescript\n * let currentFixture: string | null = null;\n *\n * const vcrCreateDriver = createVcrCreateDriver({\n * fixturesDir: \"./fixtures\",\n * getFixtureName: () => currentFixture,\n * apiKey: process.env.API_KEY,\n * });\n *\n * // Before each test:\n * currentFixture = \"test-scenario-name\";\n *\n * // Use with server:\n * const platform = await createNodePlatform({...});\n * const server = await createServer({\n * platform,\n * createDriver: vcrCreateDriver,\n * });\n * ```\n */\nexport function createVcrCreateDriver(config: VcrCreateDriverConfig): CreateDriver {\n const { fixturesDir, getFixtureName, apiKey, baseUrl, model, onPlayback, onRecording, onSaved } =\n config;\n\n // Real driver factory (must be provided or pre-loaded)\n const realCreateDriver: CreateDriver | null = config.createRealDriver || null;\n\n return (driverConfig: DriverConfig): Driver => {\n const fixtureName = getFixtureName();\n\n // No fixture name → use real driver without VCR\n if (!fixtureName) {\n if (!apiKey) {\n throw new Error(\"No fixture name and no API key. Cannot create driver.\");\n }\n\n // Sync: we need to return immediately, so we create the driver with merged config\n // Note: This path is for non-VCR scenarios\n const createDriver = realCreateDriver || config.createRealDriver;\n if (!createDriver) {\n throw new Error(\"No createRealDriver provided and claude-driver not loaded yet.\");\n }\n\n return createDriver({\n ...driverConfig,\n apiKey,\n baseUrl,\n model,\n });\n }\n\n const fixturePath = join(fixturesDir, `${fixtureName}.json`);\n\n // Fixture exists → Playback (MockDriver)\n if (existsSync(fixturePath)) {\n onPlayback?.(fixtureName);\n logger.info(\"VCR Playback\", { fixtureName });\n\n const fixture = JSON.parse(readFileSync(fixturePath, \"utf-8\")) as Fixture;\n return new MockDriver({ fixture });\n }\n\n // No fixture → Recording (RecordingDriver)\n if (!apiKey) {\n throw new Error(\n `No fixture found for \"${fixtureName}\" and no API key for recording. ` +\n `Either create the fixture or provide an API key.`\n );\n }\n\n onRecording?.(fixtureName);\n logger.info(\"VCR Recording\", { fixtureName });\n\n // Get real driver factory (sync - must be pre-loaded or provided)\n const createDriver = realCreateDriver || config.createRealDriver;\n if (!createDriver) {\n throw new Error(\n \"createRealDriver not available. For async loading, ensure claude-driver is pre-loaded.\"\n );\n }\n\n // Create real driver with merged config\n const realDriver = createDriver({\n ...driverConfig,\n apiKey,\n baseUrl,\n model,\n });\n\n // Wrap with RecordingDriver\n const recorder = new RecordingDriver({\n driver: realDriver,\n name: fixtureName,\n description: `VCR recording: ${fixtureName}`,\n });\n\n // Auto-save fixture on dispose\n let fixtureSaved = false;\n const originalDispose = recorder.dispose.bind(recorder);\n\n recorder.dispose = async () => {\n if (!fixtureSaved && recorder.eventCount > 0) {\n try {\n const fixture = recorder.getFixture();\n\n // Ensure directory exists\n const { mkdir, writeFile } = await import(\"node:fs/promises\");\n await mkdir(dirname(fixturePath), { recursive: true });\n await writeFile(fixturePath, JSON.stringify(fixture, null, 2), \"utf-8\");\n\n fixtureSaved = true;\n onSaved?.(fixtureName, recorder.eventCount);\n logger.info(\"VCR Saved\", { fixtureName, eventCount: recorder.eventCount });\n } catch (e) {\n logger.error(\"VCR Save failed\", { fixtureName, error: e });\n }\n }\n\n return originalDispose();\n };\n\n return recorder;\n };\n}\n"],"mappings":"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","names":["fixture","createDriver","mkdir","writeFile"]}
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  MockDriver,
3
3
  createMockDriver
4
- } from "../chunk-SQDCFUA3.js";
4
+ } from "../chunk-J6L73HM5.js";
5
5
  import "../chunk-6OHXS7LW.js";
6
+ import "../chunk-DGUM43GV.js";
6
7
  export {
7
8
  MockDriver,
8
9
  createMockDriver
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  RecordingDriver,
3
3
  createRecordingDriver
4
- } from "../chunk-YRTTCKHM.js";
4
+ } from "../chunk-DR45HEV4.js";
5
+ import "../chunk-DGUM43GV.js";
5
6
  export {
6
7
  RecordingDriver,
7
8
  createRecordingDriver
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@agentxjs/devtools",
3
- "version": "1.9.6-dev",
3
+ "version": "2.0.0",
4
4
  "description": "Development tools for AgentX - MockDriver, RecordingDriver, Fixtures",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "bdd": "./dist/bdd/cli.js"
10
+ },
8
11
  "exports": {
9
12
  ".": {
10
13
  "types": "./dist/index.d.ts",
@@ -25,6 +28,11 @@
25
28
  "types": "./dist/fixtures/index.d.ts",
26
29
  "import": "./dist/fixtures/index.js",
27
30
  "default": "./dist/fixtures/index.js"
31
+ },
32
+ "./bdd": {
33
+ "types": "./dist/bdd/index.d.ts",
34
+ "import": "./dist/bdd/index.js",
35
+ "default": "./dist/bdd/index.js"
28
36
  }
29
37
  },
30
38
  "files": [
@@ -35,22 +43,36 @@
35
43
  "scripts": {
36
44
  "build": "tsup",
37
45
  "typecheck": "tsc --noEmit",
38
- "test": "bun test"
46
+ "test": "echo 'No tests yet'",
47
+ "bdd": "bdd"
39
48
  },
40
49
  "dependencies": {
41
- "@agentxjs/core": "1.9.6-dev",
50
+ "@agentxjs/core": "^2.0.0",
42
51
  "commonxjs": "^0.1.1"
43
52
  },
44
53
  "devDependencies": {
45
- "@agentxjs/claude-driver": "1.9.6-dev",
54
+ "@agentxjs/claude-driver": "^2.0.0",
55
+ "@agentxjs/mono-driver": "^2.0.0",
46
56
  "typescript": "^5.3.3"
47
57
  },
48
58
  "peerDependencies": {
49
- "@agentxjs/claude-driver": "1.9.6-dev"
59
+ "agentxjs": "^2.0.0",
60
+ "@agentxjs/claude-driver": "^2.0.0",
61
+ "@playwright/test": "^1.50.0",
62
+ "@cucumber/cucumber": "^11.0.0"
50
63
  },
51
64
  "peerDependenciesMeta": {
65
+ "agentxjs": {
66
+ "optional": true
67
+ },
52
68
  "@agentxjs/claude-driver": {
53
69
  "optional": true
70
+ },
71
+ "@playwright/test": {
72
+ "optional": true
73
+ },
74
+ "@cucumber/cucumber": {
75
+ "optional": true
54
76
  }
55
77
  }
56
78
  }
package/src/Devtools.ts CHANGED
@@ -7,9 +7,11 @@
7
7
  * ```typescript
8
8
  * import { createDevtools } from "@agentxjs/devtools";
9
9
  *
10
+ * import { env } from "@agentxjs/devtools";
11
+ *
10
12
  * const devtools = createDevtools({
11
13
  * fixturesDir: "./fixtures",
12
- * apiKey: process.env.DEEPRACTICE_API_KEY,
14
+ * apiKey: env.apiKey,
13
15
  * });
14
16
  *
15
17
  * // Has fixture → playback (MockDriver)
@@ -166,7 +168,8 @@ export class Devtools {
166
168
  baseUrl: this.config.baseUrl,
167
169
  agentId,
168
170
  model: this.config.model,
169
- systemPrompt: options.systemPrompt || this.config.systemPrompt || "You are a helpful assistant.",
171
+ systemPrompt:
172
+ options.systemPrompt || this.config.systemPrompt || "You are a helpful assistant.",
170
173
  cwd: options.cwd || this.config.cwd || process.cwd(),
171
174
  };
172
175
 
@@ -381,23 +384,17 @@ export interface VcrCreateDriverConfig {
381
384
  * // Before each test:
382
385
  * currentFixture = "test-scenario-name";
383
386
  *
384
- * // Use with server/provider:
385
- * const provider = await createNodeProvider({
387
+ * // Use with server:
388
+ * const platform = await createNodePlatform({...});
389
+ * const server = await createServer({
390
+ * platform,
386
391
  * createDriver: vcrCreateDriver,
387
392
  * });
388
393
  * ```
389
394
  */
390
395
  export function createVcrCreateDriver(config: VcrCreateDriverConfig): CreateDriver {
391
- const {
392
- fixturesDir,
393
- getFixtureName,
394
- apiKey,
395
- baseUrl,
396
- model,
397
- onPlayback,
398
- onRecording,
399
- onSaved,
400
- } = config;
396
+ const { fixturesDir, getFixtureName, apiKey, baseUrl, model, onPlayback, onRecording, onSaved } =
397
+ config;
401
398
 
402
399
  // Real driver factory (must be provided or pre-loaded)
403
400
  const realCreateDriver: CreateDriver | null = config.createRealDriver || null;
@@ -0,0 +1,130 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { env } from "../env";
3
+
4
+ const SYSTEM_PROMPT = `You are a documentation reviewer evaluating documents from the reader's experience.
5
+
6
+ EVALUATION DIMENSIONS:
7
+ 1. Completeness — All required information is present. Nothing critical is missing.
8
+ 2. Logic — Structure flows naturally. Concepts build on each other without jumps.
9
+ 3. Readability — A newcomer can follow without confusion. No unexplained jargon.
10
+
11
+ RULES:
12
+ - Read the provided document carefully
13
+ - Evaluate each requirement listed in the prompt against ALL three dimensions
14
+ - Be strict but fair — the document should genuinely help the reader achieve the stated goal
15
+ - Output your result as a single line: PASS or FAIL followed by a brief reason
16
+ - If FAIL, list which specific requirements are not met and which dimension they violate`;
17
+
18
+ export interface DocTestResult {
19
+ passed: boolean;
20
+ output: string;
21
+ }
22
+
23
+ export interface DocTesterOptions {
24
+ /** LLM provider (default: "anthropic") */
25
+ provider?: string;
26
+ /** Model name */
27
+ model?: string;
28
+ /** API key (reads from env if not provided) */
29
+ apiKey?: string;
30
+ /** Base URL (reads from env if not provided) */
31
+ baseUrl?: string;
32
+ /** Timeout in ms */
33
+ timeout?: number;
34
+ }
35
+
36
+ /**
37
+ * Evaluate a document against requirements using AgentX.
38
+ *
39
+ * Uses agentxjs local mode — no subprocess, no CLI, no auth issues.
40
+ * Requires `agentxjs` as a peer dependency.
41
+ */
42
+ export async function agentDocTester(
43
+ options: {
44
+ files: string[];
45
+ requirements: string;
46
+ },
47
+ testerOptions: DocTesterOptions = {}
48
+ ): Promise<DocTestResult> {
49
+ const {
50
+ provider = process.env.AGENTX_PROVIDER || "anthropic",
51
+ model = env.model,
52
+ apiKey = env.apiKey || "",
53
+ baseUrl = env.baseUrl,
54
+ timeout = 120_000,
55
+ } = testerOptions;
56
+
57
+ const docContents = options.files
58
+ .map((filePath) => {
59
+ if (!existsSync(filePath)) {
60
+ return `--- ${filePath} ---\n[FILE NOT FOUND]`;
61
+ }
62
+ return `--- ${filePath} ---\n${readFileSync(filePath, "utf-8")}`;
63
+ })
64
+ .join("\n\n");
65
+
66
+ const userPrompt = [
67
+ "Evaluate the following document(s) against the requirements below.",
68
+ "",
69
+ "DOCUMENTS:",
70
+ docContents,
71
+ "",
72
+ "REQUIREMENTS:",
73
+ options.requirements,
74
+ "",
75
+ "Evaluate each requirement. Output PASS if all are met, FAIL if any are not.",
76
+ ].join("\n");
77
+
78
+ // Dynamic import to avoid circular dependency (devtools ↔ agentxjs)
79
+ // Use variable to prevent TypeScript DTS from resolving the module
80
+ const moduleName = "agentxjs";
81
+ const agentxjs: any = await import(/* @vite-ignore */ moduleName);
82
+ const createAgentX: (...args: any[]) => Promise<any> = agentxjs.createAgentX;
83
+
84
+ let agentx: any = null;
85
+
86
+ try {
87
+ agentx = await createAgentX({
88
+ apiKey,
89
+ provider,
90
+ model,
91
+ baseUrl,
92
+ logLevel: "silent",
93
+ });
94
+
95
+ await agentx.containers.create("doc-tester");
96
+
97
+ const { record: image } = await agentx.images.create({
98
+ containerId: "doc-tester",
99
+ systemPrompt: SYSTEM_PROMPT,
100
+ });
101
+
102
+ const { agentId } = await agentx.agents.create({ imageId: image.imageId });
103
+
104
+ // Collect response text
105
+ let output = "";
106
+ agentx.on("text_delta", (e: any) => {
107
+ output += e.data.text;
108
+ });
109
+
110
+ // Send prompt and wait for completion
111
+ await Promise.race([
112
+ agentx.sessions.send(agentId, userPrompt),
113
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeout)),
114
+ ]);
115
+
116
+ output = output.trim();
117
+ const passed = /\*{0,2}PASS\*{0,2}\b/m.test(output);
118
+ return { passed, output };
119
+ } catch (error: any) {
120
+ return { passed: false, output: error.message || "Unknown error" };
121
+ } finally {
122
+ if (agentx) {
123
+ try {
124
+ await agentx.shutdown();
125
+ } catch {
126
+ // ignore shutdown errors
127
+ }
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,88 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+
8
+ const SKILL_PATH = resolve(__dirname, "../../../../.claude/skills/agent-browser/SKILL.md");
9
+
10
+ function loadSystemPrompt(headed = false): string {
11
+ let skillContent = "";
12
+ try {
13
+ skillContent = readFileSync(SKILL_PATH, "utf-8");
14
+ } catch {
15
+ // Skill file not found, continue without it
16
+ }
17
+
18
+ return `You are a UI tester. You test web application scenarios using the agent-browser CLI.
19
+
20
+ RULES:
21
+ - ONLY use agent-browser commands via Bash tool
22
+ - Use ${headed ? "--headed " : ""}--executable-path "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" for all commands
23
+ - After each navigation or click, run: agent-browser snapshot -i
24
+ - Refs (@e1, @e2) are invalidated after page changes — always re-snapshot
25
+ - At the end, close the browser with: agent-browser close
26
+ - Output your result as a single line: PASS or FAIL followed by a brief reason
27
+
28
+ ${skillContent ? `AGENT-BROWSER REFERENCE:\n${skillContent}` : ""}`;
29
+ }
30
+
31
+ export interface UiTestResult {
32
+ passed: boolean;
33
+ output: string;
34
+ }
35
+
36
+ export interface UiTesterOptions {
37
+ model?: string;
38
+ baseUrl?: string;
39
+ timeout?: number;
40
+ /** Show browser window (default: false) */
41
+ headed?: boolean;
42
+ }
43
+
44
+ /**
45
+ * Run a UI test scenario using Claude Code CLI + agent-browser.
46
+ *
47
+ * BDD scripts must run under Node.js (not Bun) to avoid claude CLI auth bug.
48
+ */
49
+ export function agentUiTester(prompt: string, options: UiTesterOptions = {}): UiTestResult {
50
+ const { model = "haiku", baseUrl, timeout = 300_000, headed = false } = options;
51
+
52
+ const fullPrompt = baseUrl ? `Base URL: ${baseUrl}\n\n${prompt}` : prompt;
53
+
54
+ const systemPrompt = loadSystemPrompt(headed);
55
+
56
+ // Filter out CLAUDE* env vars to avoid auth conflicts when spawned from Claude Code
57
+ const cleanEnv = Object.fromEntries(
58
+ Object.entries(process.env).filter(([k]) => !k.startsWith("CLAUDE"))
59
+ );
60
+
61
+ try {
62
+ const output = execFileSync(
63
+ "claude",
64
+ [
65
+ "-p",
66
+ fullPrompt,
67
+ "--model",
68
+ model,
69
+ "--append-system-prompt",
70
+ systemPrompt,
71
+ "--allowedTools",
72
+ "Bash(agent-browser:*)",
73
+ ],
74
+ {
75
+ encoding: "utf-8",
76
+ timeout,
77
+ env: cleanEnv,
78
+ maxBuffer: 10 * 1024 * 1024,
79
+ }
80
+ ).trim();
81
+
82
+ const passed = /\*{0,2}PASS\*{0,2}\b/m.test(output);
83
+ return { passed, output };
84
+ } catch (error: any) {
85
+ const output = error.stdout || error.stderr || error.message || "Unknown error";
86
+ return { passed: false, output: output.trim() };
87
+ }
88
+ }