elasticdash-sdk 0.2.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.
Files changed (349) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +775 -0
  3. package/dist/browser-ui.d.ts +43 -0
  4. package/dist/browser-ui.d.ts.map +1 -0
  5. package/dist/browser-ui.js +246 -0
  6. package/dist/browser-ui.js.map +1 -0
  7. package/dist/capture/event.d.ts +33 -0
  8. package/dist/capture/event.d.ts.map +1 -0
  9. package/dist/capture/event.js +2 -0
  10. package/dist/capture/event.js.map +1 -0
  11. package/dist/capture/index.d.ts +4 -0
  12. package/dist/capture/index.d.ts.map +1 -0
  13. package/dist/capture/index.js +4 -0
  14. package/dist/capture/index.js.map +1 -0
  15. package/dist/capture/recorder.d.ts +24 -0
  16. package/dist/capture/recorder.d.ts.map +1 -0
  17. package/dist/capture/recorder.js +46 -0
  18. package/dist/capture/recorder.js.map +1 -0
  19. package/dist/capture/replay.d.ts +20 -0
  20. package/dist/capture/replay.d.ts.map +1 -0
  21. package/dist/capture/replay.js +47 -0
  22. package/dist/capture/replay.js.map +1 -0
  23. package/dist/ci/api-client.d.ts +38 -0
  24. package/dist/ci/api-client.d.ts.map +1 -0
  25. package/dist/ci/api-client.js +96 -0
  26. package/dist/ci/api-client.js.map +1 -0
  27. package/dist/ci/benchmark.d.ts +33 -0
  28. package/dist/ci/benchmark.d.ts.map +1 -0
  29. package/dist/ci/benchmark.js +213 -0
  30. package/dist/ci/benchmark.js.map +1 -0
  31. package/dist/ci/ed-runner.d.ts +48 -0
  32. package/dist/ci/ed-runner.d.ts.map +1 -0
  33. package/dist/ci/ed-runner.js +260 -0
  34. package/dist/ci/ed-runner.js.map +1 -0
  35. package/dist/ci/executor.d.ts +13 -0
  36. package/dist/ci/executor.d.ts.map +1 -0
  37. package/dist/ci/executor.js +542 -0
  38. package/dist/ci/executor.js.map +1 -0
  39. package/dist/ci/git-info.d.ts +17 -0
  40. package/dist/ci/git-info.d.ts.map +1 -0
  41. package/dist/ci/git-info.js +102 -0
  42. package/dist/ci/git-info.js.map +1 -0
  43. package/dist/ci/index.d.ts +6 -0
  44. package/dist/ci/index.d.ts.map +1 -0
  45. package/dist/ci/index.js +4 -0
  46. package/dist/ci/index.js.map +1 -0
  47. package/dist/ci/measurement.d.ts +9 -0
  48. package/dist/ci/measurement.d.ts.map +1 -0
  49. package/dist/ci/measurement.js +15 -0
  50. package/dist/ci/measurement.js.map +1 -0
  51. package/dist/ci/replay.d.ts +31 -0
  52. package/dist/ci/replay.d.ts.map +1 -0
  53. package/dist/ci/replay.js +96 -0
  54. package/dist/ci/replay.js.map +1 -0
  55. package/dist/ci/reporters/default.d.ts +8 -0
  56. package/dist/ci/reporters/default.d.ts.map +1 -0
  57. package/dist/ci/reporters/default.js +46 -0
  58. package/dist/ci/reporters/default.js.map +1 -0
  59. package/dist/ci/reporters/index.d.ts +8 -0
  60. package/dist/ci/reporters/index.d.ts.map +1 -0
  61. package/dist/ci/reporters/index.js +14 -0
  62. package/dist/ci/reporters/index.js.map +1 -0
  63. package/dist/ci/reporters/json.d.ts +8 -0
  64. package/dist/ci/reporters/json.d.ts.map +1 -0
  65. package/dist/ci/reporters/json.js +14 -0
  66. package/dist/ci/reporters/json.js.map +1 -0
  67. package/dist/ci/reporters/junit.d.ts +8 -0
  68. package/dist/ci/reporters/junit.d.ts.map +1 -0
  69. package/dist/ci/reporters/junit.js +48 -0
  70. package/dist/ci/reporters/junit.js.map +1 -0
  71. package/dist/ci/runner.d.ts +3 -0
  72. package/dist/ci/runner.d.ts.map +1 -0
  73. package/dist/ci/runner.js +187 -0
  74. package/dist/ci/runner.js.map +1 -0
  75. package/dist/ci/test-discovery.d.ts +5 -0
  76. package/dist/ci/test-discovery.d.ts.map +1 -0
  77. package/dist/ci/test-discovery.js +11 -0
  78. package/dist/ci/test-discovery.js.map +1 -0
  79. package/dist/ci/test-loader.d.ts +19 -0
  80. package/dist/ci/test-loader.d.ts.map +1 -0
  81. package/dist/ci/test-loader.js +149 -0
  82. package/dist/ci/test-loader.js.map +1 -0
  83. package/dist/ci/test-registry.d.ts +42 -0
  84. package/dist/ci/test-registry.d.ts.map +1 -0
  85. package/dist/ci/test-registry.js +18 -0
  86. package/dist/ci/test-registry.js.map +1 -0
  87. package/dist/ci/trace-schema.d.ts +30 -0
  88. package/dist/ci/trace-schema.d.ts.map +1 -0
  89. package/dist/ci/trace-schema.js +66 -0
  90. package/dist/ci/trace-schema.js.map +1 -0
  91. package/dist/ci/trace-writer.d.ts +16 -0
  92. package/dist/ci/trace-writer.d.ts.map +1 -0
  93. package/dist/ci/trace-writer.js +108 -0
  94. package/dist/ci/trace-writer.js.map +1 -0
  95. package/dist/ci/types.d.ts +108 -0
  96. package/dist/ci/types.d.ts.map +1 -0
  97. package/dist/ci/types.js +3 -0
  98. package/dist/ci/types.js.map +1 -0
  99. package/dist/ci/upload-client.d.ts +74 -0
  100. package/dist/ci/upload-client.d.ts.map +1 -0
  101. package/dist/ci/upload-client.js +195 -0
  102. package/dist/ci/upload-client.js.map +1 -0
  103. package/dist/cli.d.ts +3 -0
  104. package/dist/cli.d.ts.map +1 -0
  105. package/dist/cli.js +716 -0
  106. package/dist/cli.js.map +1 -0
  107. package/dist/core/agent-state.d.ts +47 -0
  108. package/dist/core/agent-state.d.ts.map +1 -0
  109. package/dist/core/agent-state.js +137 -0
  110. package/dist/core/agent-state.js.map +1 -0
  111. package/dist/core/judge-utils.d.ts +22 -0
  112. package/dist/core/judge-utils.d.ts.map +1 -0
  113. package/dist/core/judge-utils.js +211 -0
  114. package/dist/core/judge-utils.js.map +1 -0
  115. package/dist/core/registry.d.ts +28 -0
  116. package/dist/core/registry.d.ts.map +1 -0
  117. package/dist/core/registry.js +52 -0
  118. package/dist/core/registry.js.map +1 -0
  119. package/dist/dashboard-server.d.ts +65 -0
  120. package/dist/dashboard-server.d.ts.map +1 -0
  121. package/dist/dashboard-server.js +3940 -0
  122. package/dist/dashboard-server.js.map +1 -0
  123. package/dist/execution/tool-runner.d.ts +26 -0
  124. package/dist/execution/tool-runner.d.ts.map +1 -0
  125. package/dist/execution/tool-runner.js +316 -0
  126. package/dist/execution/tool-runner.js.map +1 -0
  127. package/dist/html/dashboard.html +2218 -0
  128. package/dist/http.d.ts +14 -0
  129. package/dist/http.d.ts.map +1 -0
  130. package/dist/http.js +13 -0
  131. package/dist/http.js.map +1 -0
  132. package/dist/index.cjs +8102 -0
  133. package/dist/index.d.ts +61 -0
  134. package/dist/index.d.ts.map +1 -0
  135. package/dist/index.js +67 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/interceptors/ai-interceptor.d.ts +26 -0
  138. package/dist/interceptors/ai-interceptor.d.ts.map +1 -0
  139. package/dist/interceptors/ai-interceptor.js +756 -0
  140. package/dist/interceptors/ai-interceptor.js.map +1 -0
  141. package/dist/interceptors/db-auto.d.ts +8 -0
  142. package/dist/interceptors/db-auto.d.ts.map +1 -0
  143. package/dist/interceptors/db-auto.js +217 -0
  144. package/dist/interceptors/db-auto.js.map +1 -0
  145. package/dist/interceptors/db.d.ts +23 -0
  146. package/dist/interceptors/db.d.ts.map +1 -0
  147. package/dist/interceptors/db.js +137 -0
  148. package/dist/interceptors/db.js.map +1 -0
  149. package/dist/interceptors/http.d.ts +28 -0
  150. package/dist/interceptors/http.d.ts.map +1 -0
  151. package/dist/interceptors/http.js +356 -0
  152. package/dist/interceptors/http.js.map +1 -0
  153. package/dist/interceptors/side-effects.d.ts +7 -0
  154. package/dist/interceptors/side-effects.d.ts.map +1 -0
  155. package/dist/interceptors/side-effects.js +72 -0
  156. package/dist/interceptors/side-effects.js.map +1 -0
  157. package/dist/interceptors/telemetry-push.d.ts +142 -0
  158. package/dist/interceptors/telemetry-push.d.ts.map +1 -0
  159. package/dist/interceptors/telemetry-push.js +463 -0
  160. package/dist/interceptors/telemetry-push.js.map +1 -0
  161. package/dist/interceptors/tool.d.ts +2 -0
  162. package/dist/interceptors/tool.d.ts.map +1 -0
  163. package/dist/interceptors/tool.js +274 -0
  164. package/dist/interceptors/tool.js.map +1 -0
  165. package/dist/interceptors/workflow-ai.d.ts +5 -0
  166. package/dist/interceptors/workflow-ai.d.ts.map +1 -0
  167. package/dist/interceptors/workflow-ai.js +382 -0
  168. package/dist/interceptors/workflow-ai.js.map +1 -0
  169. package/dist/internals/conditional-recorder.d.ts +21 -0
  170. package/dist/internals/conditional-recorder.d.ts.map +1 -0
  171. package/dist/internals/conditional-recorder.js +54 -0
  172. package/dist/internals/conditional-recorder.js.map +1 -0
  173. package/dist/internals/mock-resolver.d.ts +146 -0
  174. package/dist/internals/mock-resolver.d.ts.map +1 -0
  175. package/dist/internals/mock-resolver.js +427 -0
  176. package/dist/internals/mock-resolver.js.map +1 -0
  177. package/dist/matchers/index.d.ts +96 -0
  178. package/dist/matchers/index.d.ts.map +1 -0
  179. package/dist/matchers/index.js +668 -0
  180. package/dist/matchers/index.js.map +1 -0
  181. package/dist/observability.d.ts +82 -0
  182. package/dist/observability.d.ts.map +1 -0
  183. package/dist/observability.js +471 -0
  184. package/dist/observability.js.map +1 -0
  185. package/dist/portal-executor.d.ts +30 -0
  186. package/dist/portal-executor.d.ts.map +1 -0
  187. package/dist/portal-executor.js +324 -0
  188. package/dist/portal-executor.js.map +1 -0
  189. package/dist/portal-server.d.ts +3 -0
  190. package/dist/portal-server.d.ts.map +1 -0
  191. package/dist/portal-server.js +279 -0
  192. package/dist/portal-server.js.map +1 -0
  193. package/dist/proxy/llm-capture.d.ts +14 -0
  194. package/dist/proxy/llm-capture.d.ts.map +1 -0
  195. package/dist/proxy/llm-capture.js +264 -0
  196. package/dist/proxy/llm-capture.js.map +1 -0
  197. package/dist/reporter.d.ts +3 -0
  198. package/dist/reporter.d.ts.map +1 -0
  199. package/dist/reporter.js +72 -0
  200. package/dist/reporter.js.map +1 -0
  201. package/dist/runWorkflowSubprocess.d.ts +14 -0
  202. package/dist/runWorkflowSubprocess.d.ts.map +1 -0
  203. package/dist/runWorkflowSubprocess.js +66 -0
  204. package/dist/runWorkflowSubprocess.js.map +1 -0
  205. package/dist/runner.d.ts +16 -0
  206. package/dist/runner.d.ts.map +1 -0
  207. package/dist/runner.js +138 -0
  208. package/dist/runner.js.map +1 -0
  209. package/dist/socket-connector.d.ts +22 -0
  210. package/dist/socket-connector.d.ts.map +1 -0
  211. package/dist/socket-connector.js +104 -0
  212. package/dist/socket-connector.js.map +1 -0
  213. package/dist/telemetry-batcher.d.ts +56 -0
  214. package/dist/telemetry-batcher.d.ts.map +1 -0
  215. package/dist/telemetry-batcher.js +143 -0
  216. package/dist/telemetry-batcher.js.map +1 -0
  217. package/dist/test-setup.d.ts +12 -0
  218. package/dist/test-setup.d.ts.map +1 -0
  219. package/dist/test-setup.js +13 -0
  220. package/dist/test-setup.js.map +1 -0
  221. package/dist/tool-registry.d.ts +31 -0
  222. package/dist/tool-registry.d.ts.map +1 -0
  223. package/dist/tool-registry.js +73 -0
  224. package/dist/tool-registry.js.map +1 -0
  225. package/dist/tool-runner-worker.d.ts +2 -0
  226. package/dist/tool-runner-worker.d.ts.map +1 -0
  227. package/dist/tool-runner-worker.js +215 -0
  228. package/dist/tool-runner-worker.js.map +1 -0
  229. package/dist/trace-adapter/context.d.ts +72 -0
  230. package/dist/trace-adapter/context.d.ts.map +1 -0
  231. package/dist/trace-adapter/context.js +80 -0
  232. package/dist/trace-adapter/context.js.map +1 -0
  233. package/dist/tracing.d.ts +2 -0
  234. package/dist/tracing.d.ts.map +1 -0
  235. package/dist/tracing.js +59 -0
  236. package/dist/tracing.js.map +1 -0
  237. package/dist/trigger-executor.d.ts +12 -0
  238. package/dist/trigger-executor.d.ts.map +1 -0
  239. package/dist/trigger-executor.js +130 -0
  240. package/dist/trigger-executor.js.map +1 -0
  241. package/dist/types/portal.d.ts +76 -0
  242. package/dist/types/portal.d.ts.map +1 -0
  243. package/dist/types/portal.js +2 -0
  244. package/dist/types/portal.js.map +1 -0
  245. package/dist/utils/debug.d.ts +3 -0
  246. package/dist/utils/debug.d.ts.map +1 -0
  247. package/dist/utils/debug.js +8 -0
  248. package/dist/utils/debug.js.map +1 -0
  249. package/dist/utils/license-error.d.ts +23 -0
  250. package/dist/utils/license-error.d.ts.map +1 -0
  251. package/dist/utils/license-error.js +42 -0
  252. package/dist/utils/license-error.js.map +1 -0
  253. package/dist/utils/redact.d.ts +7 -0
  254. package/dist/utils/redact.d.ts.map +1 -0
  255. package/dist/utils/redact.js +26 -0
  256. package/dist/utils/redact.js.map +1 -0
  257. package/dist/workflow-runner-worker.d.ts +2 -0
  258. package/dist/workflow-runner-worker.d.ts.map +1 -0
  259. package/dist/workflow-runner-worker.js +329 -0
  260. package/dist/workflow-runner-worker.js.map +1 -0
  261. package/dist/workflow-runner.d.ts +14 -0
  262. package/dist/workflow-runner.d.ts.map +1 -0
  263. package/dist/workflow-runner.js +34 -0
  264. package/dist/workflow-runner.js.map +1 -0
  265. package/docs/agent-coding-instructions.md +138 -0
  266. package/docs/agent-integration-guide.md +564 -0
  267. package/docs/agents.md +140 -0
  268. package/docs/dashboard.md +394 -0
  269. package/docs/deno.md +69 -0
  270. package/docs/instrumentation.md +424 -0
  271. package/docs/langfuse-trace-structure.md +145 -0
  272. package/docs/matchers.md +173 -0
  273. package/docs/observability_contract.md +192 -0
  274. package/docs/observability_mode.md +195 -0
  275. package/docs/quickstart.md +621 -0
  276. package/docs/security-compliance.md +566 -0
  277. package/docs/test-writing-guidelines.md +444 -0
  278. package/docs/tools.md +165 -0
  279. package/docs/workflow-modes.md +253 -0
  280. package/package.json +76 -0
  281. package/src/browser-ui.ts +281 -0
  282. package/src/capture/event.ts +30 -0
  283. package/src/capture/index.ts +3 -0
  284. package/src/capture/recorder.ts +62 -0
  285. package/src/capture/replay.ts +55 -0
  286. package/src/ci/api-client.ts +136 -0
  287. package/src/ci/benchmark.ts +257 -0
  288. package/src/ci/ed-runner.ts +351 -0
  289. package/src/ci/executor.ts +671 -0
  290. package/src/ci/git-info.ts +127 -0
  291. package/src/ci/index.ts +5 -0
  292. package/src/ci/measurement.ts +25 -0
  293. package/src/ci/replay.ts +127 -0
  294. package/src/ci/reporters/default.ts +50 -0
  295. package/src/ci/reporters/index.ts +21 -0
  296. package/src/ci/reporters/json.ts +18 -0
  297. package/src/ci/reporters/junit.ts +61 -0
  298. package/src/ci/runner.ts +208 -0
  299. package/src/ci/test-discovery.ts +16 -0
  300. package/src/ci/test-loader.ts +187 -0
  301. package/src/ci/test-registry.ts +62 -0
  302. package/src/ci/trace-schema.ts +96 -0
  303. package/src/ci/trace-writer.ts +107 -0
  304. package/src/ci/types.ts +115 -0
  305. package/src/ci/upload-client.ts +300 -0
  306. package/src/cli.ts +811 -0
  307. package/src/core/agent-state.ts +162 -0
  308. package/src/core/judge-utils.ts +232 -0
  309. package/src/core/registry.ts +92 -0
  310. package/src/dashboard-server.ts +2047 -0
  311. package/src/execution/tool-runner.ts +352 -0
  312. package/src/html/dashboard.html +2218 -0
  313. package/src/http.ts +13 -0
  314. package/src/index.ts +138 -0
  315. package/src/interceptors/ai-interceptor.ts +798 -0
  316. package/src/interceptors/db-auto.ts +243 -0
  317. package/src/interceptors/db.ts +156 -0
  318. package/src/interceptors/http.ts +393 -0
  319. package/src/interceptors/side-effects.ts +83 -0
  320. package/src/interceptors/telemetry-push.ts +537 -0
  321. package/src/interceptors/tool.ts +287 -0
  322. package/src/interceptors/workflow-ai.ts +419 -0
  323. package/src/internals/conditional-recorder.ts +63 -0
  324. package/src/internals/mock-resolver.ts +492 -0
  325. package/src/matchers/index.ts +824 -0
  326. package/src/observability.ts +501 -0
  327. package/src/portal-executor.ts +355 -0
  328. package/src/portal-server.ts +304 -0
  329. package/src/proxy/llm-capture.ts +301 -0
  330. package/src/reporter.ts +81 -0
  331. package/src/runWorkflowSubprocess.ts +74 -0
  332. package/src/runner.ts +178 -0
  333. package/src/socket-connector.ts +117 -0
  334. package/src/telemetry-batcher.ts +191 -0
  335. package/src/test-setup.ts +16 -0
  336. package/src/tool-registry.ts +94 -0
  337. package/src/tool-runner-worker.ts +244 -0
  338. package/src/trace-adapter/context.ts +156 -0
  339. package/src/tracing.ts +62 -0
  340. package/src/trigger-executor.ts +171 -0
  341. package/src/types/agent.d.ts +63 -0
  342. package/src/types/expect.d.ts +81 -0
  343. package/src/types/modules.d.ts +2 -0
  344. package/src/types/portal.ts +69 -0
  345. package/src/utils/debug.ts +8 -0
  346. package/src/utils/license-error.ts +43 -0
  347. package/src/utils/redact.ts +25 -0
  348. package/src/workflow-runner-worker.ts +386 -0
  349. package/src/workflow-runner.ts +58 -0
package/README.md ADDED
@@ -0,0 +1,775 @@
1
+ # ElasticDash SDK
2
+
3
+ An AI-native test runner for ElasticDash workflow testing. Built for async AI pipelines — not a general-purpose test runner.
4
+
5
+ ## Quick Links
6
+
7
+ ### Jump to Key Sections
8
+ - [Quick Start](#quick-start)
9
+ - [Documentation](#documentation)
10
+ - [Tool Recording](#tool-recording)
11
+ - [AI Call Recording](#ai-call-recording)
12
+ - [Workflow Tracing](#workflow-tracing)
13
+ - [HTTP Workflow Mode](#http-workflow-mode)
14
+ - [CI/CD Runner](#cicd-runner)
15
+ - [Configuration](#configuration)
16
+
17
+ ### Open Detailed Docs
18
+ - **[Quick Start Guide](docs/quickstart.md)** ← Start here to set up your first workflow
19
+ - [Test Writing Guidelines](docs/test-writing-guidelines.md)
20
+ - [Test Matchers](docs/matchers.md)
21
+ - [Tool Recording and Replay](docs/tools.md)
22
+ - [Workflows Dashboard](docs/dashboard.md)
23
+ - [Agent Mid-Trace Replay](docs/agents.md)
24
+ - [Deno Support](docs/deno.md)
25
+ - [Instrumentation Guide](docs/instrumentation.md) — how to write `ed_tools.ts`, `ed_workflows.ts`, `ed_agents.ts`
26
+ - [Langfuse Trace Structure](docs/langfuse-trace-structure.md) — span structure required for dashboard replay
27
+
28
+ ## Features
29
+
30
+ - 🎯 **Trace-first testing** — every test gets a `trace` context to record and assert on LLM calls and tool invocations
31
+ - 🔍 **Automatic AI interception** — captures OpenAI, Gemini, and Grok calls without code changes
32
+ - 🧪 **AI-specific matchers** — semantic output matching, LLM-judged evaluations, prompt assertions
33
+ - 🛠️ **Tool & LLM recording & replay** — automatically trace tool and AI calls with checkpoint-based replay and mock support
34
+ - 📊 **Interactive dashboard** — browse workflows, debug traces, validate fixes visually
35
+ - 🤖 **Agent mid-trace replay** — resume long-running agents from any task without re-execution
36
+ - 🌐 **HTTP workflow mode** — run workflows against your live dev server for framework-heavy apps (Next.js, Remix, etc.) with full AI and tool call observability
37
+ - 🚀 **CI/CD runner** — fetch test groups from your ElasticDash project, execute tests, submit results, and fail the build on regressions
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ npm install elasticdash-sdk
45
+ ```
46
+
47
+ **Requirements:** Node 20+. For Deno projects, see [Using elasticdash-sdk in Deno](docs/deno.md).
48
+
49
+ ### Setup with a Coding Agent
50
+
51
+ If you use a coding agent (Claude Code, Cursor, Copilot, Codex, Windsurf, etc.), tell your agent:
52
+
53
+ ```
54
+ Integrate elasticdash-sdk into this project.
55
+ Read node_modules/elasticdash-sdk/docs/agent-coding-instructions.md for how to proceed,
56
+ and node_modules/elasticdash-sdk/docs/agent-integration-guide.md for technical reference.
57
+ ```
58
+
59
+ Your agent will read both docs and handle the full setup — creating `ed_tools.ts`, `ed_workflows.ts`, updating source files, and validating the connection.
60
+
61
+ **Optional:** To copy the agent instructions into your project for easier access:
62
+
63
+ ```bash
64
+ npx elasticdash init-guide # creates AGENTS.md
65
+ npx elasticdash init-guide --target CLAUDE.md # for Claude Code
66
+ npx elasticdash init-guide --target .cursor/rules/elasticdash.md # for Cursor
67
+ npx elasticdash init-guide --target .github/copilot-instructions.md # for Copilot
68
+ ```
69
+
70
+ If the target file already exists, the guide is appended (not overwritten). Use `--force` to replace the file entirely.
71
+
72
+ ### Cloud Setup
73
+
74
+ Add these to your `.env` (or CI secrets):
75
+
76
+ ```bash
77
+ ELASTICDASH_API_URL=https://server.elasticdash.com
78
+ ELASTICDASH_API_KEY=ed_your_api_key_here
79
+ ```
80
+
81
+ - **`ELASTICDASH_API_URL`** — The ElasticDash cloud backend URL. For cloud users this is always `https://server.elasticdash.com`. For self-hosted instances, use your own backend URL.
82
+ - **`ELASTICDASH_API_KEY`** — Your project API key. Find it in the [ElasticDash dashboard](https://app.elasticdash.com) under project settings.
83
+
84
+ > **Note:** `ELASTICDASH_SERVER` is an alias for `ELASTICDASH_API_URL`. Both work — the SDK checks `ELASTICDASH_API_URL` first, then falls back to `ELASTICDASH_SERVER`.
85
+
86
+ **Git ignore:** ElasticDash writes temporary runtime artifacts under `.temp/`. Add this to your `.gitignore`:
87
+
88
+ ```gitignore
89
+ .temp/
90
+ ```
91
+
92
+ **Running CLI commands:** Use `npx` to run commands with your locally installed version (recommended to avoid version drift):
93
+
94
+ ```bash
95
+ npx elasticdash test
96
+ npx elasticdash dashboard
97
+ ```
98
+
99
+ Alternatively, install globally if you prefer shorter commands:
100
+
101
+ ```bash
102
+ npm install -g elasticdash-sdk
103
+ elasticdash test
104
+ elasticdash dashboard
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Quick Start
110
+
111
+ **1. Write a test file** (`my-flow.ai.test.ts`):
112
+
113
+ ```ts
114
+ import '../node_modules/elasticdash-sdk/dist/test-setup.js'
115
+ import { expect } from 'expect'
116
+
117
+ aiTest('checkout flow', async (ctx) => {
118
+ await runCheckout(ctx)
119
+
120
+ expect(ctx.trace).toHaveLLMStep({ model: 'gpt-4', contains: 'order confirmed' })
121
+ expect(ctx.trace).toCallTool('chargeCard')
122
+ })
123
+ ```
124
+
125
+ **2. Run it:**
126
+
127
+ ```bash
128
+ npx elasticdash test # discover all * *.ai.test.ts files
129
+ npx elasticdash test ./ai-tests # discover in a specific directory
130
+ npx elasticdash run my-flow.ai.test.ts # run a single file
131
+ ```
132
+
133
+ **3. Read the output:**
134
+
135
+ ```
136
+ ✓ checkout flow (1.2s)
137
+ ✗ refund flow (0.8s)
138
+ → Expected tool "chargeCard" to be called, but no tool calls were recorded
139
+
140
+ 2 passed
141
+ 1 failed
142
+ Total: 3
143
+ Duration: 3.4s
144
+ ```
145
+
146
+ **Workflow export requirements (subprocess mode):**
147
+
148
+ - Export plain callable functions from `ed_workflows.ts/js`.
149
+ - Use JSON-serializable inputs/outputs (object or array) so dashboard replay can pass args and read results.
150
+ - Do not export framework-bound handlers directly (for example Next.js `NextRequest`/`NextResponse` route handlers) — use [HTTP workflow mode](#http-workflow-mode) instead.
151
+
152
+ ---
153
+
154
+ ## Documentation
155
+
156
+ ### Core Concepts
157
+ - **[Test Writing Guidelines](docs/test-writing-guidelines.md)** — comprehensive guide to writing AI workflow tests
158
+ - **[Test Matchers](docs/matchers.md)** — all available matchers with examples
159
+ - **[Tool Recording & Replay](docs/tools.md)** — automatic tool tracing and checkpoint-based replay
160
+
161
+ ### Advanced Features
162
+ - **[Workflows Dashboard](docs/dashboard.md)** — interactive workflow browser, debugger, and fetching traces from Langfuse
163
+ - **[Agent Mid-Trace Replay](docs/agents.md)** — resume long-running agents from any task
164
+ - **[Deno Support](docs/deno.md)** — using ElasticDash SDK in Deno projects
165
+
166
+ ### Integration & Reference
167
+ - **[Instrumentation Guide](docs/instrumentation.md)** — how to write `ed_tools.ts`, `ed_workflows.ts`, and `ed_agents.ts` to connect your production code to ElasticDash
168
+ - **[Integration Guide](docs/agent-integration-guide.md)** — step-by-step SDK integration reference (templates, patterns, decision trees)
169
+ - **[Agent Coding Instructions](docs/agent-coding-instructions.md)** — behavioral instructions for AI coding agents performing the integration
170
+ - **[Langfuse Trace Structure](docs/langfuse-trace-structure.md)** — Langfuse span structure required for dashboard replay and tool-level diffing
171
+
172
+ ---
173
+
174
+ ## Quick Reference
175
+
176
+ ### Test Globals
177
+
178
+ | Global | Description |
179
+ |---|---|
180
+ | `aiTest(name, fn)` | Register a test |
181
+ | `beforeAll(fn)` | Run once before all tests in the file |
182
+ | `beforeEach(fn)` | Run before every test in the file |
183
+ | `afterEach(fn)` | Run after every test in the file (runs even if test fails) |
184
+ | `afterAll(fn)` | Run once after all tests in the file |
185
+
186
+ ### Recording Trace Data
187
+
188
+ **Automatic (recommended):** Workflow code making real API calls to OpenAI, Gemini, or Grok is automatically intercepted and recorded.
189
+
190
+ **Manual (for custom providers or mocks):**
191
+
192
+ ```ts
193
+ ctx.trace.recordLLMStep({
194
+ model: 'gpt-4',
195
+ prompt: 'What is the order status?',
196
+ completion: 'The order has been confirmed.',
197
+ })
198
+
199
+ ctx.trace.recordToolCall({
200
+ name: 'chargeCard',
201
+ args: { amount: 99.99 },
202
+ })
203
+
204
+ ctx.trace.recordCustomStep({
205
+ kind: 'rag',
206
+ name: 'pokemon-search',
207
+ payload: { query: 'pikachu' },
208
+ result: { ids: [25] },
209
+ })
210
+ ```
211
+
212
+ ### Common Matchers
213
+
214
+ ```ts
215
+ // Assert LLM calls
216
+ expect(ctx.trace).toHaveLLMStep({ model: 'gpt-4' })
217
+ expect(ctx.trace).toHaveLLMStep({ promptContains: 'order status' })
218
+
219
+ // Assert tool calls
220
+ expect(ctx.trace).toCallTool('chargeCard')
221
+
222
+ // Semantic output matching (LLM-judged)
223
+ expect(ctx.trace).toMatchSemanticOutput('order confirmed')
224
+
225
+ // Custom steps (RAG, code, fixed)
226
+ expect(ctx.trace).toHaveCustomStep({ kind: 'rag', name: 'pokemon-search' })
227
+ ```
228
+
229
+ **→ See [Test Matchers](docs/matchers.md) for complete documentation**
230
+
231
+ ---
232
+
233
+ ## Automatic AI & Tool Tracing
234
+
235
+ ### AI Interception
236
+
237
+ The runner automatically intercepts and records calls to:
238
+ - OpenAI (`api.openai.com`)
239
+ - Gemini (`generativelanguage.googleapis.com`)
240
+ - Grok/xAI (`api.x.ai`)
241
+
242
+ No code changes needed — just run your workflow and assertions work automatically.
243
+
244
+ ### Tool Recording
245
+
246
+ **Recommended: `wrapTool`** wraps a tool function and automatically records its name, input, output, duration, and any streaming output. Works in both subprocess mode and HTTP mode:
247
+
248
+ ```ts
249
+ import { wrapTool } from 'elasticdash-sdk'
250
+ import { runSelectQuery } from './services/dataService'
251
+
252
+ export const dataService = wrapTool('dataService', async (input: { query: string }) => {
253
+ return await runSelectQuery(input.query)
254
+ })
255
+ ```
256
+
257
+ **Manual pattern (legacy):** isolate tracing in the service `.then/.catch` path so tracing failures never block business logic:
258
+
259
+ ```ts
260
+ import { runSelectQuery } from './services/dataService'
261
+
262
+ export const dataService = async (input: any) => {
263
+ const { query } = input as { query: string }
264
+ return await runSelectQuery(query)
265
+ .then(async (res: any) => {
266
+ try {
267
+ const { recordToolCall } = await import('elasticdash-sdk')
268
+ recordToolCall('dataService', input, res)
269
+ } catch {
270
+ // tracing must never block the main service path
271
+ }
272
+ return res
273
+ })
274
+ .catch(async (err: any) => {
275
+ try {
276
+ const { recordToolCall } = await import('elasticdash-sdk')
277
+ recordToolCall('dataService', input, err)
278
+ } catch {
279
+ // tracing must never block the main service path
280
+ }
281
+ throw err
282
+ })
283
+ }
284
+ ```
285
+
286
+ In manual mode, always isolate tracing in a separate `try/catch` so trace logging errors cannot interrupt core service execution.
287
+
288
+ **→ See [Tool Recording & Replay](docs/tools.md) for checkpoint-based replay and freezing**
289
+
290
+ ### AI Call Recording
291
+
292
+ **`wrapAI`** wraps any AI call function and records its name, input, output, duration, and token usage (auto-detected for Anthropic, OpenAI, and Gemini SDK responses):
293
+
294
+ ```ts
295
+ import { wrapAI } from 'elasticdash-sdk'
296
+ import Anthropic from '@anthropic-ai/sdk'
297
+
298
+ const client = new Anthropic()
299
+
300
+ export const callClaude = wrapAI('claude-sonnet-4-5', async (messages: Anthropic.MessageParam[]) => {
301
+ return await client.messages.create({
302
+ model: 'claude-sonnet-4-5-20250929',
303
+ max_tokens: 1024,
304
+ messages,
305
+ })
306
+ })
307
+ ```
308
+
309
+ Use `wrapAI` when you have a custom AI wrapper or a provider not covered by automatic interception. For direct OpenAI/Anthropic/Gemini SDK calls inside a subprocess workflow, automatic interception via `installAIInterceptor` already handles recording without any code changes.
310
+
311
+ **AI mocking (subprocess / test runner mode):** `wrapAI` also checks `resolveAIMock` at call time, so the dashboard can mock LLM responses the same way it mocks tool calls — without modifying your server code. Configure an `AIMockConfig` in the dashboard UI or pass it programmatically via the `aiMockConfig` option when running a workflow.
312
+
313
+ ### HTTP Streaming Capture and Replay
314
+
315
+ ElasticDash also captures non-AI `fetch` responses that stream over HTTP (for example SSE and NDJSON endpoints) in the HTTP interceptor.
316
+
317
+ Currently detected as streaming when response `content-type` includes:
318
+ - `text/event-stream`
319
+ - `application/x-ndjson`
320
+ - `application/stream+json`
321
+ - `application/jsonl`
322
+
323
+ How it behaves today:
324
+ - During live execution, ElasticDash tees the response stream and returns a real stream to your app code.
325
+ - In parallel, ElasticDash buffers the recorder side of the stream as raw text for trace replay.
326
+ - During replay, ElasticDash reconstructs a stream from that captured raw payload and restores status, status text, and response headers.
327
+
328
+ Replay fidelity note:
329
+ - Replay preserves stream payload content, but not original chunk boundaries or timing cadence.
330
+
331
+ Minimal stream consumption example:
332
+
333
+ ```ts
334
+ const res = await fetch('https://example.com/events')
335
+ if (!res.body) throw new Error('Expected a streaming response body')
336
+
337
+ const reader = res.body.getReader()
338
+ const decoder = new TextDecoder()
339
+ let buffer = ''
340
+
341
+ for (;;) {
342
+ const { done, value } = await reader.read()
343
+ if (done) break
344
+ buffer += decoder.decode(value, { stream: true })
345
+ }
346
+
347
+ buffer += decoder.decode()
348
+ ```
349
+
350
+ **→ See [Quick Start Guide](docs/quickstart.md#capture-streaming-flows) for end-to-end setup guidance**
351
+
352
+ ---
353
+
354
+ ## Workflow Tracing
355
+
356
+ `wrapTool` and `wrapAI` record individual steps, but to group them into a single named workflow trace you need the **trace lifecycle**: call `edStartTrace` at the start of your handler and `edEndTrace` when it finishes. Every `wrapTool`/`wrapAI` call in between is automatically associated with that trace.
357
+
358
+ ### 1. Create `ed_workflows.ts`
359
+
360
+ This file exports the trace lifecycle functions. It holds a reference to the `elasticdash-sdk` module (set from `ed_tools.ts`) so it can call `startTrace`/`endTrace`:
361
+
362
+ ```ts
363
+ // ed_workflows.ts
364
+ let _ed: any = null;
365
+
366
+ /** Called from ed_tools.ts to share the SDK module instance. */
367
+ export function setElasticDashModule(mod: any): void {
368
+ _ed = mod;
369
+ }
370
+
371
+ /** Call at the start of your request handler to begin a named trace. */
372
+ export const edStartTrace = async (workflowName: string): Promise<void> => {
373
+ if (!_ed) return;
374
+ try {
375
+ await _ed.tryAutoInitHttpContext();
376
+ _ed.startTrace(workflowName);
377
+ } catch (err) {
378
+ console.error('[ed_workflows] edStartTrace error:', err);
379
+ }
380
+ };
381
+
382
+ /** Call in a finally block to end the trace and flush captured events. */
383
+ export const edEndTrace = (): void => {
384
+ if (!_ed) return;
385
+ try {
386
+ _ed.endTrace();
387
+ } catch (err) {
388
+ console.error('[ed_workflows] edEndTrace error:', err);
389
+ }
390
+ };
391
+ ```
392
+
393
+ ### 2. Create `ed_tools.ts`
394
+
395
+ This file loads the SDK, shares the module instance with `ed_workflows.ts`, and exports your wrapped tools:
396
+
397
+ ```ts
398
+ // ed_tools.ts
399
+ import { setElasticDashModule } from './ed_workflows';
400
+
401
+ let wrapTool: <T extends (...args: any[]) => any>(name: string, fn: T) => T = (_name, fn) => fn;
402
+
403
+ try {
404
+ // For Next.js / Turbopack: eval('require') bypasses static analysis.
405
+ // For plain Node.js projects you can use a normal require() or import.
406
+ const _edModule = (eval('require') as (id: string) => any)('elasticdash-sdk');
407
+ wrapTool = _edModule.wrapTool ?? wrapTool;
408
+ setElasticDashModule(_edModule);
409
+ } catch {
410
+ // elasticdash-sdk not available — tools run without tracing
411
+ }
412
+
413
+ export const myTool = wrapTool('myTool', async (input: { query: string }) => {
414
+ // ... your tool logic
415
+ });
416
+ ```
417
+
418
+ > **Why `setElasticDashModule`?** The SDK uses Node.js AsyncLocalStorage (ALS) to correlate events. Both `ed_tools.ts` and `ed_workflows.ts` must share the same CJS module instance so `wrapTool`/`wrapAI` calls write to the same ALS store that `startTrace`/`endTrace` reads from.
419
+
420
+ ### 3. Instrument Your Route Handler
421
+
422
+ Call `edStartTrace` at handler entry and `edEndTrace` in a `finally` block. All `wrapTool`/`wrapAI` calls in between are grouped under the trace:
423
+
424
+ ```ts
425
+ // app/api/chat/route.ts (Next.js example)
426
+ import { edStartTrace, edEndTrace } from './ed_workflows';
427
+ import { myTool } from './ed_tools';
428
+
429
+ export async function POST(req: Request) {
430
+ await edStartTrace('chatHandler');
431
+ try {
432
+ const body = await req.json();
433
+ const result = await myTool({ query: body.message });
434
+ return Response.json(result);
435
+ } finally {
436
+ edEndTrace();
437
+ }
438
+ }
439
+ ```
440
+
441
+ ### 4. Capture Traces to Disk
442
+
443
+ Set the `ELASTICDASH_CAPTURE_TRACE` environment variable to save each workflow trace as a JSON file under `.ed_traces/`:
444
+
445
+ ```bash
446
+ ELASTICDASH_CAPTURE_TRACE=1 npm run dev
447
+ ```
448
+
449
+ Captured traces can be used for offline replay, benchmark tests (`defineTest` in `ed_tests.ts`), and debugging in the dashboard.
450
+
451
+ ---
452
+
453
+ ## HTTP Workflow Mode
454
+
455
+ For apps where subprocess import fails (Next.js, Remix, SvelteKit, etc.), configure workflows to call your running dev server directly instead of importing the handler:
456
+
457
+ ```ts
458
+ // elasticdash.config.ts
459
+ export default {
460
+ testMatch: ['**/*.ai.test.ts'],
461
+ workflows: {
462
+ runChat: {
463
+ mode: 'http',
464
+ url: 'http://localhost:3001/api/chat',
465
+ method: 'POST',
466
+ headers: {
467
+ 'Content-Type': 'application/json',
468
+ 'x-user-id': '{{env.DEV_USER_ID}}',
469
+ },
470
+ bodyTemplate: {
471
+ messages: [{ role: 'user', content: '{{input.message}}' }],
472
+ selectedModel: 'claude-sonnet-4-5-20250929',
473
+ },
474
+ responseFormat: 'vercel-ai-stream',
475
+ },
476
+ },
477
+ }
478
+ ```
479
+
480
+ To enable full AI and tool call observability in HTTP mode, install `elasticdash-sdk` in your app:
481
+
482
+ ```ts
483
+ // app/api/chat/route.ts
484
+ import { initHttpRunContext, wrapTool, wrapAI } from 'elasticdash-sdk'
485
+
486
+ export async function POST(req: Request) {
487
+ const runId = req.headers.get('x-elasticdash-run-id')
488
+ const serverUrl = req.headers.get('x-elasticdash-server')
489
+ if (runId && serverUrl) {
490
+ await initHttpRunContext(runId, serverUrl)
491
+ }
492
+ // ... rest of handler
493
+ }
494
+ ```
495
+
496
+ The dashboard injects `x-elasticdash-run-id` and `x-elasticdash-server` headers automatically when triggering a run. `initHttpRunContext` fetches any frozen steps from the dashboard before execution begins — this is what enables step freezing (replaying historical results for specific steps). Every `wrapAI` and `wrapTool` call downstream pushes telemetry events back to the dashboard in real time.
497
+
498
+ > **Note:** Use `setHttpRunContext` (synchronous) if you only need observability and do not need step freezing. `initHttpRunContext` is required for the dashboard's breakpoint/replay functionality to work.
499
+
500
+ ### Dashboard Auto-Detection (env var mode)
501
+
502
+ As an alternative to calling `initHttpRunContext` in your request handler, you can set two environment variables before starting your server or script. Every `wrapTool` and `wrapAI` call will then connect to the dashboard automatically — no code changes needed:
503
+
504
+ ```bash
505
+ # Required: URL of the running ElasticDash dashboard
506
+ ELASTICDASH_SERVER=http://localhost:4573
507
+
508
+ # Optional: pre-registered run ID to fetch frozen steps for
509
+ ELASTICDASH_RUN_ID=<run-id-from-dashboard>
510
+ ```
511
+
512
+ - If only `ELASTICDASH_SERVER` is set, a fresh run ID is generated and all calls push live telemetry to the dashboard (observability only, no step freezing).
513
+ - If both variables are set, frozen steps are fetched from the dashboard at startup and replayed as configured.
514
+ - If the dashboard is unreachable the SDK falls through to live execution silently.
515
+ - The initialization runs **once per process** — subsequent `wrapTool`/`wrapAI` calls reuse the cached context.
516
+
517
+ This mode is intended for local development and testing scenarios. For production HTTP servers with concurrent requests, continue using `initHttpRunContext` inside your request handler.
518
+
519
+ **Subprocess vs HTTP mode comparison:**
520
+
521
+ | | Subprocess (default) | HTTP mode |
522
+ |---|---|---|
523
+ | Works with simple apps | Yes | Yes |
524
+ | Works with Next.js / Remix | No | Yes |
525
+ | Requires dev server running | No | Yes |
526
+ | App code changes needed | Extract handler to `ed_workflows.ts` | Add `initHttpRunContext` to request handler (or use env vars for auto-detect) |
527
+ | AI / tool call observability | Automatic via interceptors | Via `wrapAI` / `wrapTool` push |
528
+ | Step freezing / breakpoints | Yes | Yes (`initHttpRunContext`, or `ELASTICDASH_SERVER` + `ELASTICDASH_RUN_ID` env vars) |
529
+ | LLM response mocking | Yes (via `aiMockConfig`) | Yes (via frozen AI events) |
530
+
531
+ ---
532
+
533
+ ## CI/CD Runner
534
+
535
+ Run your ElasticDash test groups directly from CI pipelines. The `ci` command fetches active test groups from your project via API key, executes each test locally, submits results back to the backend, and exits with code 1 if any test fails.
536
+
537
+ ### How It Works
538
+
539
+ ```
540
+ ┌──────────────┐ GET /testgroups/by-project ┌──────────────────┐
541
+ │ CI Runner │ ──────────────────────────────────→ │ ElasticDash API │
542
+ │ (SDK side) │ ←────────────────────────────────── │ (your backend) │
543
+ │ │ test groups + tests + expectations │ │
544
+ │ │ │ │
545
+ │ execute │ POST /testgroups/:id/runs │ │
546
+ │ each test │ ──────────────────────────────────→ │ stores results │
547
+ │ locally │ │ │
548
+ │ │ POST /testgroups/batches │ │
549
+ │ │ ──────────────────────────────────→ │ groups the runs │
550
+ └──────────────┘ └──────────────────┘
551
+ ```
552
+
553
+ 1. **Fetch** — Calls `GET /testgroups/by-project` with the API key (scoped to project). Returns all active test groups with their tests and expectations.
554
+ 2. **Execute** — For each test, runs it locally using existing SDK infrastructure:
555
+ - **Single-step tests** — replays a specific tool or AI step with `mock_input` and `frozen_events`
556
+ - **Full-flow tests** — runs the entire workflow from `ed_workflows.ts` with `workflow_input`
557
+ 3. **Evaluate** — Checks all expectations (token-budget, latency-budget, output-contains, output-schema, tool-called, determinism, llm-judge). Respects `run_count` and `pass_threshold`.
558
+ 4. **Submit** — POSTs each result to `POST /testgroups/:id/runs` with single run data, expectation results, and git metadata.
559
+ 5. **Batch** — Creates a batch grouping all run IDs for dashboard viewing.
560
+
561
+ ### CLI Usage
562
+
563
+ ```bash
564
+ # Basic — uses env vars (set in .env or CI secrets)
565
+ npx elasticdash ci
566
+
567
+ # Explicit flags (if not using env vars)
568
+ npx elasticdash ci --server https://server.elasticdash.com --api-key ed_xxx
569
+
570
+ # Filter by workflow or tags
571
+ npx elasticdash ci --server $ELASTICDASH_API_URL --api-key $ELASTICDASH_API_KEY \
572
+ --workflow checkout --tags payment,critical
573
+
574
+ # Pass git metadata (auto-detected in GitHub Actions / GitLab CI)
575
+ npx elasticdash ci --server $ELASTICDASH_API_URL --api-key $ELASTICDASH_API_KEY \
576
+ --git-branch main --git-commit abc123
577
+ ```
578
+
579
+ **All flags:**
580
+
581
+ | Flag | Env Var | Description |
582
+ |------|---------|-------------|
583
+ | `--server <url>` | `ELASTICDASH_API_URL` | Backend API URL (required) |
584
+ | `--api-key <key>` | `ELASTICDASH_API_KEY` | Project API key (required) |
585
+ | `--workflow <name>` | — | Filter test groups by workflow name |
586
+ | `--tags <t1,t2>` | — | Filter test groups by tags (comma-separated) |
587
+ | `--triggered-by <src>` | — | Trigger source label (default: `ci`) |
588
+ | `--git-branch <branch>` | Auto-detected | Git branch name |
589
+ | `--git-commit <sha>` | Auto-detected | Git commit SHA |
590
+ | `--git-commit-message <msg>` | Auto-detected | Commit message |
591
+ | `--git-pr-number <n>` | Auto-detected | PR number |
592
+ | `--git-pr-url <url>` | Auto-detected | PR URL |
593
+
594
+ ### GitHub Actions Example
595
+
596
+ ```yaml
597
+ name: AI Tests
598
+ on: [push, pull_request]
599
+
600
+ jobs:
601
+ test:
602
+ runs-on: ubuntu-latest
603
+ steps:
604
+ - uses: actions/checkout@v4
605
+ - uses: actions/setup-node@v4
606
+ with:
607
+ node-version: 20
608
+
609
+ - run: npm ci
610
+
611
+ - name: Run ElasticDash CI tests
612
+ run: npx elasticdash ci
613
+ env:
614
+ ELASTICDASH_API_URL: ${{ secrets.ELASTICDASH_API_URL }}
615
+ ELASTICDASH_API_KEY: ${{ secrets.ELASTICDASH_API_KEY }}
616
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} # if tests use OpenAI
617
+ ```
618
+
619
+ Git branch, commit SHA, PR number, and PR URL are auto-detected from GitHub Actions environment variables — no extra flags needed.
620
+
621
+ ### GitLab CI Example
622
+
623
+ ```yaml
624
+ ai-tests:
625
+ stage: test
626
+ image: node:20
627
+ script:
628
+ - npm ci
629
+ - npx elasticdash ci
630
+ variables:
631
+ ELASTICDASH_API_URL: $ELASTICDASH_API_URL
632
+ ELASTICDASH_API_KEY: $ELASTICDASH_API_KEY
633
+ ```
634
+
635
+ ### Programmatic Usage
636
+
637
+ ```ts
638
+ import { runCI } from 'elasticdash-sdk'
639
+
640
+ const summary = await runCI({
641
+ serverUrl: 'https://your-api.com',
642
+ apiKey: 'ed_xxx',
643
+ workflowName: 'checkout', // optional filter
644
+ tags: ['payment', 'critical'], // optional filter
645
+ })
646
+
647
+ console.log(`${summary.passed}/${summary.total} passed`)
648
+ process.exit(summary.failed > 0 ? 1 : 0)
649
+ ```
650
+
651
+ ### Output
652
+
653
+ ```
654
+ [elasticdash ci] Fetching test groups...
655
+ [elasticdash ci] Found 2 test group(s), 5 test(s) total.
656
+
657
+ Checkout Flow (3 tests)
658
+ validate-input ... PASS (234ms)
659
+ charge-card ... PASS (1823ms)
660
+ send-confirmation ... FAIL (945ms)
661
+ [output-contains] Output text check failed.
662
+
663
+ Refund Flow (2 tests)
664
+ check-eligibility ... PASS (412ms)
665
+ process-refund ... PASS (1567ms)
666
+
667
+ ──────────────────────────────────────────────────
668
+ Summary
669
+ ──────────────────────────────────────────────────
670
+ Total: 5
671
+ Passed: 4
672
+ Failed: 1
673
+ Duration: 5.0s
674
+ Batch ID: 42
675
+ ──────────────────────────────────────────────────
676
+
677
+ [elasticdash ci] 1 test(s) failed.
678
+ ```
679
+
680
+ ### Prerequisites
681
+
682
+ - An ElasticDash project with an API key (create one in the dashboard under Settings → API Keys)
683
+ - Active test groups with tests and expectations configured in the dashboard
684
+ - `ed_tools.ts` and/or `ed_workflows.ts` in your project root (for the executor to discover tools and workflows)
685
+ - AI provider API keys in the environment if tests use LLM calls (e.g., `OPENAI_API_KEY`)
686
+
687
+ ---
688
+
689
+ ## Configuration
690
+
691
+ Optional `elasticdash.config.ts` at project root:
692
+
693
+ ```ts
694
+ export default {
695
+ testMatch: ['**/*.ai.test.ts'],
696
+ traceMode: 'local' as const,
697
+ }
698
+ ```
699
+
700
+ **Dashboard port:** defaults to `4573`. Override via CLI flag or `.env`:
701
+
702
+ ```bash
703
+ # .env
704
+ ELASTICDASH_PORT=5000
705
+ ```
706
+
707
+ ```bash
708
+ # or CLI flag
709
+ npx elasticdash dashboard --port 5000
710
+ ```
711
+
712
+ Optional project file: `ed_workers.ts` can be used by your app architecture (for example, exporting worker handlers), but it is not required or discovered by the ElasticDash CLI/dashboard.
713
+
714
+ ## TypeScript Setup
715
+
716
+ For typed globals and matchers, extend your test directory's `tsconfig.json`:
717
+
718
+ ```json
719
+ {
720
+ "extends": "../tsconfig.json",
721
+ "include": ["../src/**/*", "./**/*"]
722
+ }
723
+ ```
724
+
725
+ ---
726
+
727
+ ## Programmatic API
728
+
729
+ ```ts
730
+ import { runFiles, reportResults, registerMatchers, installAIInterceptor } from 'elasticdash-sdk'
731
+
732
+ registerMatchers()
733
+ installAIInterceptor()
734
+
735
+ const results = await runFiles(['./tests/flow.ai.test.ts'])
736
+ reportResults(results)
737
+ ```
738
+
739
+ **HTTP mode context (call inside your request handler):**
740
+
741
+ ```ts
742
+ import { initHttpRunContext, setHttpRunContext } from 'elasticdash-sdk'
743
+
744
+ // Async — fetches frozen steps from dashboard to enable step freezing/breakpoints
745
+ await initHttpRunContext(runId, dashboardUrl)
746
+
747
+ // Synchronous alternative — observability only, no step freezing
748
+ setHttpRunContext(runId, dashboardUrl)
749
+ ```
750
+
751
+ **Dashboard auto-detection (env var mode — no code changes needed):**
752
+
753
+ ```bash
754
+ # Set before starting your server or script
755
+ ELASTICDASH_API_URL=https://server.elasticdash.com # cloud (or http://localhost:4573 for local dashboard)
756
+ ELASTICDASH_API_KEY=ed_your_api_key_here # your project API key
757
+ ELASTICDASH_RUN_ID=<run-id-from-dashboard> # optional, enables step freezing
758
+ ```
759
+
760
+ `wrapTool` and `wrapAI` will auto-connect on their first call. See [Dashboard Auto-Detection](#dashboard-auto-detection-env-var-mode) for details.
761
+
762
+ **CI runner (execute test groups from your project):**
763
+
764
+ ```ts
765
+ import { runCI } from 'elasticdash-sdk'
766
+
767
+ const summary = await runCI({ serverUrl: 'https://server.elasticdash.com', apiKey: 'ed_xxx' })
768
+ // summary.total, summary.passed, summary.failed, summary.batchId, summary.results
769
+ ```
770
+
771
+ ---
772
+
773
+ ## License
774
+
775
+ MIT