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/dist/cli.js ADDED
@@ -0,0 +1,716 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { Command } from 'commander';
4
+ import fg from 'fast-glob';
5
+ import path from 'node:path';
6
+ import { pathToFileURL, fileURLToPath } from 'node:url';
7
+ import { existsSync, copyFileSync, mkdirSync, readFileSync, appendFileSync } from 'node:fs';
8
+ import { registerMatchers } from './matchers/index.js';
9
+ import { installAIInterceptor } from './interceptors/ai-interceptor.js';
10
+ import { runFiles } from './runner.js';
11
+ import { reportResults } from './reporter.js';
12
+ import { startBrowserUiServer } from './browser-ui.js';
13
+ import { startDashboardServer } from './dashboard-server.js';
14
+ import { initObservability, shutdownObservability } from './observability.js';
15
+ import { startPortalServer } from './portal-server.js';
16
+ import { resolveRuntimeModule, scanTools, buildToolArgs, runToolInSubprocess } from './execution/tool-runner.js';
17
+ /** Brief, single-line preview of any value for trace event tables. */
18
+ function previewify(value, maxLen = 200) {
19
+ if (value === null || value === undefined)
20
+ return '';
21
+ let str;
22
+ try {
23
+ str = typeof value === 'string' ? value : JSON.stringify(value);
24
+ }
25
+ catch {
26
+ str = String(value);
27
+ }
28
+ str = str.replace(/\n/g, ' ').trim();
29
+ return str.length > maxLen ? str.slice(0, maxLen) + '...' : str;
30
+ }
31
+ function stripAnsi(input) {
32
+ if (!input)
33
+ return input;
34
+ return input.replace(/\u001b\[[0-9;]*m/g, '');
35
+ }
36
+ async function loadConfig(cwd) {
37
+ const configPath = path.join(cwd, 'elasticdash.config.ts');
38
+ const configPathJs = path.join(cwd, 'elasticdash.config.js');
39
+ for (const p of [configPath, configPathJs]) {
40
+ if (existsSync(p)) {
41
+ try {
42
+ const mod = await import(pathToFileURL(p).href);
43
+ return (mod.default ?? {});
44
+ }
45
+ catch (error) {
46
+ // Skip this config file if it can't be imported (e.g., .ts when running from built dist)
47
+ continue;
48
+ }
49
+ }
50
+ }
51
+ return {};
52
+ }
53
+ // --- File discovery ---
54
+ async function discoverTestFiles(patterns, cwd) {
55
+ const files = await fg(patterns, { cwd, absolute: true });
56
+ return files.sort();
57
+ }
58
+ // --- Validate repository directory ---
59
+ function validateRepoDirectory(cwd) {
60
+ const validFiles = [
61
+ 'elasticdash.config.ts',
62
+ 'elasticdash.config.js',
63
+ 'ed_workflows.ts',
64
+ 'ed_workflows.js',
65
+ 'ed_tools.ts',
66
+ 'ed_tools.js',
67
+ ];
68
+ return validFiles.some(file => existsSync(path.join(cwd, file)));
69
+ }
70
+ // --- Bootstrap ---
71
+ async function bootstrap() {
72
+ const cwd = process.cwd();
73
+ const isInitGuide = process.argv.includes('init-guide');
74
+ // Skip matchers/interceptors for commands that don't need them
75
+ if (!isInitGuide) {
76
+ registerMatchers();
77
+ installAIInterceptor();
78
+ }
79
+ if (!isInitGuide && !validateRepoDirectory(cwd)) {
80
+ console.error(`[elasticdash] Error: elasticdash command must be run from the elasticdash-sdk SDK repository directory.\n` +
81
+ `Current directory: ${cwd}\n` +
82
+ `Expected: A directory containing one of: elasticdash.config.ts, elasticdash.config.js, ed_workflows.ts, ed_workflows.js`);
83
+ process.exit(1);
84
+ }
85
+ const config = await loadConfig(cwd);
86
+ const defaultPattern = config.testMatch ?? ['**/*.ai.test.ts', '**/*.ai.test.js'];
87
+ // Read version from package.json
88
+ // Use require for CJS compatibility, fallback to import if needed
89
+ // This path is relative to the compiled dist directory
90
+ let version = 'unknown';
91
+ try {
92
+ // @ts-ignore
93
+ version = (await import(pathToFileURL(path.join(cwd, 'package.json')).href, { with: { type: 'json' } })).default.version;
94
+ }
95
+ catch (e) {
96
+ try {
97
+ version = require(path.join(cwd, 'package.json')).version;
98
+ }
99
+ catch { }
100
+ }
101
+ const program = new Command();
102
+ program
103
+ .name('elasticdash')
104
+ .description('AI-native test runner for ElasticDash workflow testing')
105
+ .version(version);
106
+ // elasticdash test [dir]
107
+ program
108
+ .command('test [dir]')
109
+ .description('Discover and run all AI test files')
110
+ .option('--no-browser-ui', 'Disable browser progress UI')
111
+ .option('--browser-ui-port <port>', 'Port for browser UI', (v) => Number(v), undefined)
112
+ .action(async (dir, cmd) => {
113
+ const searchBase = dir ? path.resolve(cwd, dir) : cwd;
114
+ console.log('[elasticdash] Test discovery pattern:', defaultPattern);
115
+ console.log('[elasticdash] Test search base:', searchBase);
116
+ const files = await discoverTestFiles(defaultPattern, searchBase);
117
+ console.log('[elasticdash] Discovered test files:', files);
118
+ if (files.length === 0) {
119
+ console.error(`No test files found matching: ${defaultPattern.join(', ')}`);
120
+ process.exit(1);
121
+ }
122
+ const useBrowserUiEnv = process.env.ELASTICDASH_BROWSER_UI !== '0';
123
+ const useBrowserUiFlag = cmd?.browserUi !== false;
124
+ const enableBrowserUi = useBrowserUiEnv && useBrowserUiFlag;
125
+ const ui = enableBrowserUi
126
+ ? await startBrowserUiServer({ port: cmd?.browserUiPort, autoOpen: true })
127
+ : undefined;
128
+ if (ui) {
129
+ ui.send({ type: 'run-start', payload: { files } });
130
+ }
131
+ const startedAt = Date.now();
132
+ const results = await runFiles(files, {
133
+ hooks: {
134
+ onTestStart(name) {
135
+ ui?.send({ type: 'test-start', payload: { name } });
136
+ },
137
+ onTestFinish(name, passed, durationMs, error) {
138
+ ui?.send({
139
+ type: 'test-finish',
140
+ payload: { name, passed, durationMs, errorMessage: stripAnsi(error?.message) },
141
+ });
142
+ },
143
+ },
144
+ });
145
+ // Log registered tests
146
+ const { getRegistry } = await import('./core/registry.js');
147
+ const registry = getRegistry();
148
+ console.log('[elasticdash] Tests registered:', registry.tests.map(t => t.name));
149
+ reportResults(results);
150
+ const anyFailed = results.some((fr) => fr.results.some((r) => !r.passed));
151
+ let uiDelayMs = 0;
152
+ if (ui) {
153
+ const durationMs = Date.now() - startedAt;
154
+ const failures = [];
155
+ let totalTests = 0;
156
+ let passedCount = 0;
157
+ for (const fr of results) {
158
+ for (const r of fr.results) {
159
+ totalTests += 1;
160
+ if (r.passed)
161
+ passedCount += 1;
162
+ else
163
+ failures.push({ name: r.name, errorMessage: stripAnsi(r.error?.message) });
164
+ }
165
+ }
166
+ ui.send({
167
+ type: 'run-summary',
168
+ payload: {
169
+ passed: passedCount,
170
+ failed: failures.length,
171
+ total: totalTests,
172
+ durationMs,
173
+ failures,
174
+ },
175
+ });
176
+ uiDelayMs = 60000;
177
+ }
178
+ if (uiDelayMs > 0) {
179
+ await new Promise((resolve) => setTimeout(resolve, uiDelayMs));
180
+ ui?.close();
181
+ }
182
+ process.exit(anyFailed ? 1 : 0);
183
+ });
184
+ // elasticdash run <file>
185
+ program
186
+ .command('run <file>')
187
+ .description('Run a single AI test file')
188
+ .option('--no-browser-ui', 'Disable browser progress UI')
189
+ .option('--browser-ui-port <port>', 'Port for browser UI', (v) => Number(v), undefined)
190
+ .action(async (file, cmd) => {
191
+ const absFile = pathToFileURL(path.resolve(cwd, file)).href;
192
+ const useBrowserUiEnv = process.env.ELASTICDASH_BROWSER_UI !== '0';
193
+ const useBrowserUiFlag = cmd?.browserUi !== false;
194
+ const enableBrowserUi = useBrowserUiEnv && useBrowserUiFlag;
195
+ const ui = enableBrowserUi
196
+ ? await startBrowserUiServer({ port: cmd?.browserUiPort, autoOpen: true })
197
+ : undefined;
198
+ if (ui) {
199
+ ui.send({ type: 'run-start', payload: { files: [absFile] } });
200
+ }
201
+ const startedAt = Date.now();
202
+ const results = await runFiles([absFile], {
203
+ hooks: {
204
+ onTestStart(name) {
205
+ ui?.send({ type: 'test-start', payload: { name } });
206
+ },
207
+ onTestFinish(name, passed, durationMs, error) {
208
+ ui?.send({
209
+ type: 'test-finish',
210
+ payload: { name, passed, durationMs, errorMessage: stripAnsi(error?.message) },
211
+ });
212
+ },
213
+ },
214
+ });
215
+ reportResults(results);
216
+ const anyFailed = results.some((fr) => fr.results.some((r) => !r.passed));
217
+ let uiDelayMs = 0;
218
+ if (ui) {
219
+ const durationMs = Date.now() - startedAt;
220
+ const failures = [];
221
+ let totalTests = 0;
222
+ let passedCount = 0;
223
+ for (const fr of results) {
224
+ for (const r of fr.results) {
225
+ totalTests += 1;
226
+ if (r.passed)
227
+ passedCount += 1;
228
+ else
229
+ failures.push({ name: r.name, errorMessage: stripAnsi(r.error?.message) });
230
+ }
231
+ }
232
+ ui.send({
233
+ type: 'run-summary',
234
+ payload: {
235
+ passed: passedCount,
236
+ failed: failures.length,
237
+ total: totalTests,
238
+ durationMs,
239
+ failures,
240
+ },
241
+ });
242
+ uiDelayMs = 60000;
243
+ }
244
+ if (uiDelayMs > 0) {
245
+ await new Promise((resolve) => setTimeout(resolve, uiDelayMs));
246
+ ui?.close();
247
+ }
248
+ process.exit(anyFailed ? 1 : 0);
249
+ });
250
+ // elasticdash dashboard
251
+ program
252
+ .command('dashboard')
253
+ .description('Browse and search workflow functions')
254
+ .option('--port <port>', 'Dashboard server port', (v) => Number(v), process.env.ELASTICDASH_PORT ? Number(process.env.ELASTICDASH_PORT) : 4573)
255
+ .option('--no-open', 'Skip auto-opening browser')
256
+ .action(async (options) => {
257
+ console.log('[elasticdash] Starting dashboard server...');
258
+ const server = await startDashboardServer(cwd, {
259
+ port: options.port,
260
+ autoOpen: options.open,
261
+ });
262
+ console.log(`[elasticdash] Dashboard running at ${server.url}`);
263
+ console.log('[elasticdash] Press Ctrl+C to stop');
264
+ // Keep the process running with proper cleanup
265
+ let isShuttingDown = false;
266
+ const cleanup = async () => {
267
+ if (isShuttingDown) {
268
+ // Force exit on second Ctrl+C
269
+ console.log('\n[elasticdash] Force exiting...');
270
+ process.exit(1);
271
+ }
272
+ isShuttingDown = true;
273
+ console.log('\n[elasticdash] Shutting down dashboard server...');
274
+ try {
275
+ await Promise.race([
276
+ server.close(),
277
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000))
278
+ ]);
279
+ process.exit(0);
280
+ }
281
+ catch (error) {
282
+ console.error('[elasticdash] Error during shutdown:', error);
283
+ process.exit(1);
284
+ }
285
+ };
286
+ process.once('SIGINT', cleanup);
287
+ process.once('SIGTERM', cleanup);
288
+ });
289
+ // elasticdash observe
290
+ program
291
+ .command('observe')
292
+ .description('Start observability mode — stream trace events to ElasticDash backend')
293
+ .option('--server <url>', 'ElasticDash backend API URL', process.env.ELASTICDASH_API_URL)
294
+ .option('--api-key <key>', 'Project API key', process.env.ELASTICDASH_API_KEY)
295
+ .action(async (options) => {
296
+ const serverUrl = options.server;
297
+ if (!serverUrl) {
298
+ console.error('[elasticdash] Error: --server or ELASTICDASH_API_URL is required');
299
+ process.exit(1);
300
+ }
301
+ const handle = initObservability({
302
+ serverUrl,
303
+ apiKey: options.apiKey,
304
+ });
305
+ console.log(`[elasticdash] Observability active`);
306
+ console.log(` Session ID : ${handle.sessionId}`);
307
+ console.log(` Server : ${serverUrl}`);
308
+ console.log(`[elasticdash] Press Ctrl+C to stop`);
309
+ let isShuttingDown = false;
310
+ const cleanup = async () => {
311
+ if (isShuttingDown) {
312
+ process.exit(1);
313
+ }
314
+ isShuttingDown = true;
315
+ console.log('\n[elasticdash] Shutting down observability...');
316
+ await shutdownObservability();
317
+ process.exit(0);
318
+ };
319
+ process.once('SIGINT', cleanup);
320
+ process.once('SIGTERM', cleanup);
321
+ });
322
+ // elasticdash ed-test — Phase 3 fixture-based CI testing
323
+ program
324
+ .command('ed-test')
325
+ .description('Run ed_tests benchmarks against recorded fixtures')
326
+ .option('--cwd <path>', 'Root for test discovery')
327
+ .option('--no-upload', 'Skip uploading results to backend')
328
+ .option('--filter <pattern>', 'Only run tests matching glob pattern')
329
+ .option('--reporter <name>', 'Output format: default, json, junit', 'default')
330
+ .option('--fail-fast', 'Stop after first failing test')
331
+ .option('--runs <count>', 'Number of times to run each test (passes if any run succeeds)', (v) => Number(v), 1)
332
+ .action(async (options) => {
333
+ // Auto-register tsx loader so .ts test files can be imported.
334
+ // tsx exposes a register() API for programmatic use.
335
+ try {
336
+ const { register } = await import('node:module');
337
+ register('tsx/esm', import.meta.url);
338
+ }
339
+ catch {
340
+ // tsx or module.register not available — .ts files will fail with a clear error
341
+ }
342
+ const { runEdTests } = await import('./ci/ed-runner.js');
343
+ const { createReporter } = await import('./ci/reporters/index.js');
344
+ const { buildUploadPayload, uploadResults, persistFailedUpload } = await import('./ci/upload-client.js');
345
+ const reporter = createReporter(options.reporter || 'default');
346
+ const testCwd = options.cwd ? path.resolve(cwd, options.cwd) : cwd;
347
+ let runResult;
348
+ try {
349
+ runResult = await runEdTests({
350
+ cwd: testCwd,
351
+ filter: options.filter,
352
+ failFast: options.failFast,
353
+ noUpload: options.upload === false,
354
+ reporter: options.reporter || 'default',
355
+ runs: options.runs,
356
+ });
357
+ }
358
+ catch (err) {
359
+ console.error(`[elasticdash] Configuration error: ${err instanceof Error ? err.message : String(err)}`);
360
+ process.exit(3);
361
+ }
362
+ if (runResult.results.length === 0) {
363
+ console.error('[elasticdash] No tests found.');
364
+ process.exit(3);
365
+ }
366
+ // Report results
367
+ for (const r of runResult.results) {
368
+ reporter.onTestStart(r.testName);
369
+ reporter.onTestResult(r);
370
+ }
371
+ // Upload if enabled
372
+ let uploadUrl;
373
+ const shouldUpload = options.upload !== false;
374
+ const apiKey = process.env.ELASTICDASH_API_KEY;
375
+ const serverUrl = process.env.ELASTICDASH_API_URL;
376
+ if (shouldUpload && apiKey && serverUrl) {
377
+ const payload = buildUploadPayload(runResult);
378
+ try {
379
+ const response = await uploadResults(payload, { serverUrl, apiKey });
380
+ uploadUrl = `${serverUrl.replace(/\/+$/, '')}/runs/${response.runId}`;
381
+ }
382
+ catch (err) {
383
+ const errorMsg = err instanceof Error ? err.message : String(err);
384
+ console.warn(`[elasticdash] Upload failed: ${errorMsg}`);
385
+ await persistFailedUpload(buildUploadPayload(runResult), errorMsg, testCwd);
386
+ // Exit 4 only if tests themselves passed
387
+ const hasFailed = runResult.results.some(r => r.status === 'fail');
388
+ if (!hasFailed) {
389
+ reporter.onRunComplete(runResult);
390
+ process.exit(4);
391
+ }
392
+ }
393
+ }
394
+ else if (shouldUpload && !apiKey) {
395
+ // In CI, warn about missing API key
396
+ const isCI = !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.BUILDKITE);
397
+ if (isCI) {
398
+ console.warn('[elasticdash] Warning: ELASTICDASH_API_KEY not set — skipping upload');
399
+ }
400
+ }
401
+ reporter.onRunComplete(runResult, uploadUrl);
402
+ const hasFailed = runResult.results.some(r => r.status === 'fail');
403
+ process.exit(hasFailed ? 1 : 0);
404
+ });
405
+ // elasticdash ci
406
+ program
407
+ .command('ci')
408
+ .description('Run CI/CD tests — fetch test groups from backend, execute tests, submit results')
409
+ .option('--server <url>', 'ElasticDash backend API URL', process.env.ELASTICDASH_API_URL)
410
+ .option('--api-key <key>', 'Project API key', process.env.ELASTICDASH_API_KEY)
411
+ .option('--workflow <name>', 'Filter test groups by workflow name')
412
+ .option('--tags <tags>', 'Filter test groups by tags (comma-separated)')
413
+ .option('--triggered-by <source>', 'Trigger source', 'ci')
414
+ .option('--git-branch <branch>', 'Git branch name')
415
+ .option('--git-commit <sha>', 'Git commit SHA')
416
+ .option('--git-commit-message <msg>', 'Git commit message')
417
+ .option('--git-pr-number <number>', 'Pull request number', (v) => Number(v))
418
+ .option('--git-pr-url <url>', 'Pull request URL')
419
+ .action(async (options) => {
420
+ const serverUrl = options.server;
421
+ if (!serverUrl) {
422
+ console.error('[elasticdash] Error: --server or ELASTICDASH_API_URL is required');
423
+ process.exit(1);
424
+ }
425
+ const apiKey = options.apiKey;
426
+ if (!apiKey) {
427
+ console.error('[elasticdash] Error: --api-key or ELASTICDASH_API_KEY is required');
428
+ process.exit(1);
429
+ }
430
+ const { runCI } = await import('./ci/runner.js');
431
+ const summary = await runCI({
432
+ serverUrl,
433
+ apiKey,
434
+ workflowName: options.workflow,
435
+ tags: options.tags ? options.tags.split(',').map(s => s.trim()).filter(Boolean) : undefined,
436
+ triggeredBy: options.triggeredBy || 'ci',
437
+ gitBranch: options.gitBranch,
438
+ gitCommit: options.gitCommit,
439
+ gitCommitMessage: options.gitCommitMessage,
440
+ gitPrNumber: options.gitPrNumber,
441
+ gitPrUrl: options.gitPrUrl,
442
+ });
443
+ process.exit(summary.failed > 0 ? 1 : 0);
444
+ });
445
+ // elasticdash portal
446
+ program
447
+ .command('portal')
448
+ .description('Start a portal server to receive and execute rerun tasks from ElasticDash backend')
449
+ .option('--server <url>', 'ElasticDash backend API URL to POST results to', process.env.ELASTICDASH_API_URL)
450
+ .option('--api-key <key>', 'Project API key', process.env.ELASTICDASH_API_KEY)
451
+ .option('--port <port>', 'Portal server port', (v) => Number(v), process.env.ELASTICDASH_PORTAL_PORT ? Number(process.env.ELASTICDASH_PORTAL_PORT) : 4574)
452
+ .option('--allowed-origins <origins>', 'Comma-separated list of additional allowed origin domains', process.env.ELASTICDASH_ALLOWED_ORIGINS)
453
+ .action(async (options) => {
454
+ const backendUrl = options.server;
455
+ if (!backendUrl) {
456
+ console.error('[elasticdash] Error: --server or ELASTICDASH_API_URL is required');
457
+ process.exit(1);
458
+ }
459
+ const allowedOrigins = options.allowedOrigins
460
+ ? options.allowedOrigins.split(',').map(s => s.trim()).filter(Boolean)
461
+ : undefined;
462
+ const handle = await startPortalServer({
463
+ port: options.port,
464
+ backendUrl,
465
+ apiKey: options.apiKey,
466
+ cwd,
467
+ allowedOrigins,
468
+ });
469
+ console.log(`[elasticdash] Portal server running`);
470
+ console.log(` URL : ${handle.url}`);
471
+ console.log(` Backend : ${backendUrl}`);
472
+ console.log(` Port : ${handle.port}`);
473
+ console.log(`[elasticdash] Waiting for tasks from backend... Press Ctrl+C to stop`);
474
+ // Register with backend
475
+ try {
476
+ const headers = { 'Content-Type': 'application/json' };
477
+ if (options.apiKey)
478
+ headers['Authorization'] = `Bearer ${options.apiKey}`;
479
+ const res = await fetch(`${backendUrl}/api/portal/register`, {
480
+ method: 'POST',
481
+ headers,
482
+ body: JSON.stringify({ portalUrl: handle.url }),
483
+ });
484
+ if (res.ok) {
485
+ console.log(`[elasticdash] Registered with backend`);
486
+ }
487
+ else {
488
+ console.warn(`[elasticdash] Backend registration returned ${res.status} — portal will still accept tasks directly`);
489
+ }
490
+ }
491
+ catch {
492
+ console.warn(`[elasticdash] Could not register with backend — portal will still accept tasks directly`);
493
+ }
494
+ let isShuttingDown = false;
495
+ const cleanup = async () => {
496
+ if (isShuttingDown) {
497
+ process.exit(1);
498
+ }
499
+ isShuttingDown = true;
500
+ console.log('\n[elasticdash] Shutting down portal...');
501
+ await handle.close();
502
+ process.exit(0);
503
+ };
504
+ process.once('SIGINT', cleanup);
505
+ process.once('SIGTERM', cleanup);
506
+ });
507
+ // elasticdash run-tool <name>
508
+ program
509
+ .command('run-tool <name>')
510
+ .description('Run a single tool by name with given input. Used for rerun validation (e.g. ElasticDash MCP).')
511
+ .option('--input <json>', 'JSON input to pass to the tool')
512
+ .option('--input-file <path>', 'Path to JSON file with input')
513
+ .action(async (name, options) => {
514
+ const toolsModulePath = resolveRuntimeModule(cwd, 'ed_tools');
515
+ if (!toolsModulePath) {
516
+ console.error(`[elasticdash] Error: Could not find ed_tools.ts or ed_tools.js in ${cwd}`);
517
+ process.exit(1);
518
+ }
519
+ let toolInput = undefined;
520
+ try {
521
+ if (options.inputFile) {
522
+ toolInput = JSON.parse(readFileSync(path.resolve(cwd, options.inputFile), 'utf-8'));
523
+ }
524
+ else if (options.input) {
525
+ toolInput = JSON.parse(options.input);
526
+ }
527
+ }
528
+ catch (err) {
529
+ console.error(`[elasticdash] Error: Failed to parse input JSON: ${err instanceof Error ? err.message : String(err)}`);
530
+ process.exit(1);
531
+ }
532
+ // File-scan is used to compute a positional-arg signature when present,
533
+ // but a missing scan hit is not fatal: tools defined via edTool() live
534
+ // in the runtime registry and only resolve once the worker imports the
535
+ // module. The worker handles "not found" with a clear error.
536
+ const tools = scanTools(cwd);
537
+ const tool = tools.find(t => t.name === name);
538
+ const args = buildToolArgs(toolInput, tool);
539
+ const result = await runToolInSubprocess(toolsModulePath, name, args);
540
+ if (!result.ok) {
541
+ console.error(`[elasticdash] ${result.error ?? 'Tool execution failed'}`);
542
+ process.exit(1);
543
+ }
544
+ const payload = {
545
+ tool: name,
546
+ output: result.currentOutput,
547
+ duration_ms: result.currentDurationMs,
548
+ };
549
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
550
+ // Sentinel line so machine consumers (e.g. ElasticDash MCP) can extract the
551
+ // result envelope even when project code prints to stdout before us.
552
+ process.stdout.write('__ED_RUN_TOOL_RESULT__:' + JSON.stringify(payload) + '\n');
553
+ process.exit(0);
554
+ });
555
+ // elasticdash run-workflow <name>
556
+ program
557
+ .command('run-workflow <name>')
558
+ .description('Run a single workflow by name with given input. Used for trace-less rerun (e.g. ElasticDash MCP).')
559
+ .option('--input <json>', 'JSON input to pass to the workflow')
560
+ .option('--input-file <path>', 'Path to JSON file with input')
561
+ .option('--timeout-seconds <n>', 'Hard cap on a single run in seconds', (v) => Number(v), 300)
562
+ .action(async (name, options) => {
563
+ const workflowsModulePath = resolveRuntimeModule(cwd, 'ed_workflows');
564
+ if (!workflowsModulePath) {
565
+ console.error(`[elasticdash] Error: Could not find ed_workflows.ts or ed_workflows.js in ${cwd}`);
566
+ process.exit(1);
567
+ }
568
+ // tsx's in-process `register('tsx/esm', ...)` produces a data:text/javascript URL
569
+ // that newer Node versions reject as unresolvable. To load TS reliably, re-spawn
570
+ // ourselves once with `--import tsx` in NODE_OPTIONS so tsx is installed at
571
+ // process startup (the same approach `runToolInSubprocess` uses for ed_tools.ts).
572
+ const isTs = workflowsModulePath.endsWith('.ts') || workflowsModulePath.endsWith('.tsx');
573
+ const nodeOptions = process.env.NODE_OPTIONS ?? '';
574
+ const tsxAlreadyLoaded = nodeOptions.includes('tsx') || nodeOptions.includes('--import');
575
+ if (isTs && !tsxAlreadyLoaded) {
576
+ const { spawn } = await import('node:child_process');
577
+ const child = spawn(process.execPath, process.argv.slice(1), {
578
+ env: { ...process.env, NODE_OPTIONS: `${nodeOptions} --import tsx`.trim() },
579
+ cwd,
580
+ stdio: 'inherit',
581
+ });
582
+ const exitCode = await new Promise(resolve => {
583
+ child.on('exit', code => resolve(code ?? 0));
584
+ child.on('error', () => resolve(1));
585
+ });
586
+ process.exit(exitCode);
587
+ }
588
+ let workflowInput = undefined;
589
+ try {
590
+ if (options.inputFile) {
591
+ workflowInput = JSON.parse(readFileSync(path.resolve(cwd, options.inputFile), 'utf-8'));
592
+ }
593
+ else if (options.input) {
594
+ workflowInput = JSON.parse(options.input);
595
+ }
596
+ }
597
+ catch (err) {
598
+ console.error(`[elasticdash] Error: Failed to parse input JSON: ${err instanceof Error ? err.message : String(err)}`);
599
+ process.exit(1);
600
+ }
601
+ let mod;
602
+ try {
603
+ mod = await import(pathToFileURL(workflowsModulePath).href);
604
+ }
605
+ catch (err) {
606
+ console.error(`[elasticdash] Error: Failed to import ${workflowsModulePath}: ${err instanceof Error ? err.message : String(err)}`);
607
+ process.exit(1);
608
+ }
609
+ const workflowFn = mod[name];
610
+ if (typeof workflowFn !== 'function') {
611
+ console.error(`[elasticdash] Error: Workflow '${name}' not found or not a function in ${workflowsModulePath}`);
612
+ process.exit(1);
613
+ }
614
+ const { runWorkflow } = await import('./workflow-runner.js');
615
+ const startedAt = Date.now();
616
+ let runError;
617
+ let timedOut = false;
618
+ let traceId = null;
619
+ let output = null;
620
+ let events = [];
621
+ try {
622
+ const runPromise = runWorkflow(async () => workflowFn(workflowInput));
623
+ const timeoutPromise = new Promise((_, reject) => {
624
+ setTimeout(() => {
625
+ timedOut = true;
626
+ reject(new Error(`workflow '${name}' timed out after ${options.timeoutSeconds}s`));
627
+ }, options.timeoutSeconds * 1000);
628
+ });
629
+ const runResult = await Promise.race([runPromise, timeoutPromise]);
630
+ output = runResult.result;
631
+ traceId = runResult.trace?.traceId ?? null;
632
+ events = (runResult.trace?.events ?? []).map((e) => {
633
+ const out = e.output;
634
+ const hasError = out !== null && typeof out === 'object' && out !== null && 'error' in out;
635
+ return {
636
+ id: e.id,
637
+ type: e.type,
638
+ name: e.name,
639
+ duration_ms: e.durationMs,
640
+ has_error: hasError,
641
+ input_preview: previewify(e.input),
642
+ output_preview: previewify(e.output),
643
+ };
644
+ });
645
+ }
646
+ catch (err) {
647
+ runError = err instanceof Error ? err.message : String(err);
648
+ }
649
+ const endedAt = Date.now();
650
+ const envelope = {
651
+ workflow: name,
652
+ trace_id: traceId,
653
+ output,
654
+ duration_ms: endedAt - startedAt,
655
+ started_at: startedAt,
656
+ ended_at: endedAt,
657
+ events,
658
+ status: runError ? (timedOut ? 'timed_out' : 'failed') : 'completed',
659
+ ...(runError ? { error: runError } : {}),
660
+ };
661
+ process.stdout.write(JSON.stringify(envelope, null, 2) + '\n');
662
+ process.stdout.write('__ED_RUN_WORKFLOW_RESULT__:' + JSON.stringify(envelope) + '\n');
663
+ process.exit(runError ? 1 : 0);
664
+ });
665
+ // elasticdash init-guide
666
+ program
667
+ .command('init-guide')
668
+ .description('Copy the ElasticDash agent coding instructions into your project')
669
+ .option('--target <path>', 'Destination file path', 'AGENTS.md')
670
+ .option('--force', 'Overwrite the file instead of appending')
671
+ .action(async (cmd) => {
672
+ const targetPath = path.resolve(cwd, cmd.target);
673
+ // Resolve docs from the SDK's own docs/ directory
674
+ const thisFile = fileURLToPath(import.meta.url);
675
+ const docsDir = path.resolve(path.dirname(thisFile), '..', 'docs');
676
+ const instructionsSrc = path.resolve(docsDir, 'agent-coding-instructions.md');
677
+ if (!existsSync(instructionsSrc)) {
678
+ console.error(`[elasticdash] Could not find agent coding instructions at ${instructionsSrc}`);
679
+ process.exit(1);
680
+ }
681
+ // Ensure target directory exists
682
+ const targetDir = path.dirname(targetPath);
683
+ if (!existsSync(targetDir)) {
684
+ mkdirSync(targetDir, { recursive: true });
685
+ }
686
+ const guideContent = readFileSync(instructionsSrc, 'utf-8');
687
+ if (existsSync(targetPath) && !cmd.force) {
688
+ // Check if instructions are already appended
689
+ const existing = readFileSync(targetPath, 'utf-8');
690
+ if (existing.includes('ElasticDash SDK — AI Coding Agent Instructions')) {
691
+ console.log(`[elasticdash] ${cmd.target} already contains the ElasticDash agent instructions. Use --force to replace the file.`);
692
+ process.exit(0);
693
+ }
694
+ // Append to existing file
695
+ appendFileSync(targetPath, '\n\n---\n\n' + guideContent);
696
+ console.log(`[elasticdash] ElasticDash agent instructions appended to ${cmd.target}`);
697
+ }
698
+ else {
699
+ // Create new file or overwrite with --force
700
+ copyFileSync(instructionsSrc, targetPath);
701
+ console.log(`[elasticdash] Agent coding instructions written to ${cmd.target}`);
702
+ }
703
+ console.log();
704
+ console.log(` Tell your coding agent:`);
705
+ console.log();
706
+ console.log(` Read ${cmd.target} and follow it to integrate elasticdash-sdk into this project.`);
707
+ console.log();
708
+ process.exit(0);
709
+ });
710
+ await program.parseAsync(process.argv);
711
+ }
712
+ bootstrap().catch((err) => {
713
+ console.error(err);
714
+ process.exit(1);
715
+ });
716
+ //# sourceMappingURL=cli.js.map