elasticdash-test 0.1.17 → 0.1.18-alpha-2

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 (100) hide show
  1. package/dist/capture/event.d.ts +5 -1
  2. package/dist/capture/event.d.ts.map +1 -1
  3. package/dist/cli.js +100 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/evaluators/llm-judge.js +17 -14
  6. package/dist/evaluators/types.d.ts +1 -0
  7. package/dist/execution/tool-runner.d.ts +26 -0
  8. package/dist/execution/tool-runner.d.ts.map +1 -0
  9. package/dist/execution/tool-runner.js +270 -0
  10. package/dist/execution/tool-runner.js.map +1 -0
  11. package/dist/http.d.ts +2 -0
  12. package/dist/http.d.ts.map +1 -1
  13. package/dist/http.js +2 -0
  14. package/dist/http.js.map +1 -1
  15. package/dist/index.cjs +4400 -2665
  16. package/dist/index.d.ts +12 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +9 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/interceptors/ai-interceptor.d.ts.map +1 -1
  21. package/dist/interceptors/ai-interceptor.js +97 -4
  22. package/dist/interceptors/ai-interceptor.js.map +1 -1
  23. package/dist/interceptors/db-auto.d.ts.map +1 -1
  24. package/dist/interceptors/db-auto.js +116 -24
  25. package/dist/interceptors/db-auto.js.map +1 -1
  26. package/dist/interceptors/db.d.ts +5 -0
  27. package/dist/interceptors/db.d.ts.map +1 -1
  28. package/dist/interceptors/db.js +93 -15
  29. package/dist/interceptors/db.js.map +1 -1
  30. package/dist/interceptors/http.d.ts.map +1 -1
  31. package/dist/interceptors/http.js +125 -93
  32. package/dist/interceptors/http.js.map +1 -1
  33. package/dist/interceptors/telemetry-push.d.ts +15 -0
  34. package/dist/interceptors/telemetry-push.d.ts.map +1 -1
  35. package/dist/interceptors/telemetry-push.js +96 -13
  36. package/dist/interceptors/telemetry-push.js.map +1 -1
  37. package/dist/interceptors/tool.d.ts.map +1 -1
  38. package/dist/interceptors/tool.js +42 -5
  39. package/dist/interceptors/tool.js.map +1 -1
  40. package/dist/interceptors/workflow-ai.d.ts.map +1 -1
  41. package/dist/interceptors/workflow-ai.js +46 -2
  42. package/dist/interceptors/workflow-ai.js.map +1 -1
  43. package/dist/observability.d.ts +69 -0
  44. package/dist/observability.d.ts.map +1 -0
  45. package/dist/observability.js +246 -0
  46. package/dist/observability.js.map +1 -0
  47. package/dist/portal-executor.d.ts +30 -0
  48. package/dist/portal-executor.d.ts.map +1 -0
  49. package/dist/portal-executor.js +304 -0
  50. package/dist/portal-executor.js.map +1 -0
  51. package/dist/portal-server.d.ts +3 -0
  52. package/dist/portal-server.d.ts.map +1 -0
  53. package/dist/portal-server.js +265 -0
  54. package/dist/portal-server.js.map +1 -0
  55. package/dist/socket-connector.d.ts +23 -0
  56. package/dist/socket-connector.d.ts.map +1 -0
  57. package/dist/socket-connector.js +102 -0
  58. package/dist/socket-connector.js.map +1 -0
  59. package/dist/telemetry-batcher.d.ts +43 -0
  60. package/dist/telemetry-batcher.d.ts.map +1 -0
  61. package/dist/telemetry-batcher.js +111 -0
  62. package/dist/telemetry-batcher.js.map +1 -0
  63. package/dist/trigger-executor.d.ts +12 -0
  64. package/dist/trigger-executor.d.ts.map +1 -0
  65. package/dist/trigger-executor.js +83 -0
  66. package/dist/trigger-executor.js.map +1 -0
  67. package/dist/types/portal.d.ts +64 -0
  68. package/dist/types/portal.d.ts.map +1 -0
  69. package/dist/types/portal.js +2 -0
  70. package/dist/types/portal.js.map +1 -0
  71. package/dist/utils/debug.d.ts +3 -0
  72. package/dist/utils/debug.d.ts.map +1 -0
  73. package/dist/utils/debug.js +8 -0
  74. package/dist/utils/debug.js.map +1 -0
  75. package/dist/utils/redact.d.ts +7 -0
  76. package/dist/utils/redact.d.ts.map +1 -0
  77. package/dist/utils/redact.js +26 -0
  78. package/dist/utils/redact.js.map +1 -0
  79. package/package.json +10 -1
  80. package/src/capture/event.ts +5 -1
  81. package/src/cli.ts +109 -0
  82. package/src/execution/tool-runner.ts +304 -0
  83. package/src/http.ts +2 -0
  84. package/src/index.ts +18 -0
  85. package/src/interceptors/ai-interceptor.ts +110 -4
  86. package/src/interceptors/db-auto.ts +121 -25
  87. package/src/interceptors/db.ts +92 -17
  88. package/src/interceptors/http.ts +145 -107
  89. package/src/interceptors/telemetry-push.ts +113 -13
  90. package/src/interceptors/tool.ts +42 -5
  91. package/src/interceptors/workflow-ai.ts +49 -2
  92. package/src/observability.ts +286 -0
  93. package/src/portal-executor.ts +335 -0
  94. package/src/portal-server.ts +290 -0
  95. package/src/socket-connector.ts +115 -0
  96. package/src/telemetry-batcher.ts +143 -0
  97. package/src/trigger-executor.ts +121 -0
  98. package/src/types/portal.ts +67 -0
  99. package/src/utils/debug.ts +8 -0
  100. package/src/utils/redact.ts +25 -0
@@ -0,0 +1,83 @@
1
+ import { executePortalTask, checkToolAvailability, checkAIAvailability } from './portal-executor.js';
2
+ import { scanTools } from './execution/tool-runner.js';
3
+ import { debugLog } from './utils/debug.js';
4
+ /**
5
+ * Executes a trigger received from the backend's event batch response.
6
+ *
7
+ * For each step:
8
+ * 1. Pre-validates availability (tool exists? API key set?)
9
+ * 2. If unavailable: reports `available: false` with reason, skips execution
10
+ * 3. If available: re-executes `runCount` times, collects results
11
+ * 4. POSTs all results to `POST /api/observability/triggers/:triggerId/results`
12
+ */
13
+ export async function executeTrigger(serverUrl, apiKey, trigger) {
14
+ const cwd = process.cwd();
15
+ const tools = scanTools(cwd);
16
+ debugLog(`[elasticdash] Executing trigger ${trigger.triggerId}: ${trigger.steps.length} steps × ${trigger.runCount} runs`);
17
+ const stepResults = [];
18
+ for (const step of trigger.steps) {
19
+ // Pre-validate availability
20
+ const availability = step.eventType === 'ai'
21
+ ? checkAIAvailability(step.provider, step.model ?? step.eventName)
22
+ : checkToolAvailability(step.eventName, cwd, tools);
23
+ if (!availability.available) {
24
+ debugLog(`[elasticdash] Trigger ${trigger.triggerId} step=${step.eventName} unavailable: ${availability.reason}`);
25
+ stepResults.push({
26
+ originalEventDbId: step.originalEventDbId,
27
+ eventType: step.eventType,
28
+ eventName: step.eventName,
29
+ available: false,
30
+ unavailableReason: availability.reason,
31
+ runs: [],
32
+ });
33
+ continue;
34
+ }
35
+ // Execute runs
36
+ const runs = [];
37
+ for (let i = 0; i < trigger.runCount; i++) {
38
+ const result = await executePortalTask({
39
+ taskId: `trigger-${trigger.triggerId}-${step.eventName}-${i}`,
40
+ type: step.eventType === 'ai' ? 'ai' : 'tool',
41
+ name: step.eventName,
42
+ input: step.input,
43
+ model: step.eventType === 'ai' ? (step.model ?? step.eventName) : undefined,
44
+ provider: step.provider,
45
+ }, cwd, tools);
46
+ runs.push({
47
+ runIndex: i,
48
+ input: step.input,
49
+ output: result.output,
50
+ durationMs: result.durationMs,
51
+ error: result.error,
52
+ usageInputTokens: result.usage?.inputTokens,
53
+ usageOutputTokens: result.usage?.outputTokens,
54
+ usageTotalTokens: result.usage?.totalTokens,
55
+ });
56
+ debugLog(`[elasticdash] Trigger ${trigger.triggerId} step=${step.eventName} run=${i} ok=${result.ok}`);
57
+ }
58
+ stepResults.push({
59
+ originalEventDbId: step.originalEventDbId,
60
+ eventType: step.eventType,
61
+ eventName: step.eventName,
62
+ available: true,
63
+ runs,
64
+ });
65
+ }
66
+ // POST results to backend
67
+ const url = `${serverUrl.replace(/\/$/, '')}/api/observability/triggers/${trigger.triggerId}/results`;
68
+ const headers = { 'Content-Type': 'application/json' };
69
+ if (apiKey)
70
+ headers['Authorization'] = `Bearer ${apiKey}`;
71
+ try {
72
+ const res = await fetch(url, {
73
+ method: 'POST',
74
+ headers,
75
+ body: JSON.stringify({ steps: stepResults }),
76
+ });
77
+ debugLog(`[elasticdash] Trigger ${trigger.triggerId} results posted (status ${res.status})`);
78
+ }
79
+ catch (err) {
80
+ debugLog(`[elasticdash] Trigger ${trigger.triggerId} results POST failed: ${err instanceof Error ? err.message : String(err)}`);
81
+ }
82
+ }
83
+ //# sourceMappingURL=trigger-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-executor.js","sourceRoot":"","sources":["../src/trigger-executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ export interface PortalTask {
2
+ /** Unique task ID assigned by the backend */
3
+ taskId: string;
4
+ /** What to rerun */
5
+ type: 'tool' | 'ai';
6
+ /** Tool name or model name */
7
+ name: string;
8
+ /** Tool arguments or LLM prompt/messages */
9
+ input: unknown;
10
+ /** Model name (for AI tasks) */
11
+ model?: string;
12
+ /** LLM provider: openai, anthropic, gemini, grok, etc. */
13
+ provider?: string;
14
+ /** LLM generation parameters */
15
+ modelParameters?: {
16
+ temperature?: number;
17
+ max_tokens?: number;
18
+ };
19
+ /** Passthrough metadata (test group ID, expectation IDs, etc.) */
20
+ metadata?: Record<string, unknown>;
21
+ }
22
+ export interface PortalTaskResult {
23
+ taskId: string;
24
+ ok: boolean;
25
+ output: unknown;
26
+ error?: string;
27
+ durationMs: number;
28
+ usage?: {
29
+ inputTokens?: number;
30
+ outputTokens?: number;
31
+ totalTokens?: number;
32
+ };
33
+ /** Echoed from the original task */
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+ export interface PortalServerOptions {
37
+ /** Port to listen on (default 4574) */
38
+ port?: number;
39
+ /** Backend URL to POST results to */
40
+ backendUrl: string;
41
+ /** Auth token for incoming and outgoing requests */
42
+ apiKey?: string;
43
+ /** Project root directory (default process.cwd()) */
44
+ cwd?: string;
45
+ /**
46
+ * Allowed origin domains that may send requests to this portal.
47
+ * By default only the `backendUrl` domain and localhost are allowed.
48
+ * Provide additional origins (e.g. 'https://app.elasticdash.com') to extend.
49
+ */
50
+ allowedOrigins?: string[];
51
+ }
52
+ export interface PortalServerHandle {
53
+ port: number;
54
+ url: string;
55
+ close: () => Promise<void>;
56
+ }
57
+ export interface PortalStatus {
58
+ ok: boolean;
59
+ queueLength: number;
60
+ processing: string | null;
61
+ completed: number;
62
+ failed: number;
63
+ }
64
+ //# sourceMappingURL=portal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/types/portal.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,oBAAoB;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,KAAK,EAAE,OAAO,CAAA;IACd,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,eAAe,CAAC,EAAE;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE;QACN,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=portal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal.js","sourceRoot":"","sources":["../../src/types/portal.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ /** Log only when ELASTICDASH_DEBUG=1 is set. Drop-in replacement for console.log in interceptors. */
2
+ export declare function debugLog(...args: unknown[]): void;
3
+ //# sourceMappingURL=debug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAEA,qGAAqG;AACrG,wBAAgB,QAAQ,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAIjD"}
@@ -0,0 +1,8 @@
1
+ const DEBUG_KEY = 'ELASTICDASH_DEBUG';
2
+ /** Log only when ELASTICDASH_DEBUG=1 is set. Drop-in replacement for console.log in interceptors. */
3
+ export function debugLog(...args) {
4
+ if (typeof process !== 'undefined' && process.env?.[DEBUG_KEY] === '1') {
5
+ console.log(...args);
6
+ }
7
+ }
8
+ //# sourceMappingURL=debug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,mBAAmB,CAAA;AAErC,qGAAqG;AACrG,MAAM,UAAU,QAAQ,CAAC,GAAG,IAAe;IACzC,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,GAAG,EAAE,CAAC;QACvE,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Deep-clones a value and replaces any object property whose key matches
3
+ * one of `keys` (case-insensitive) with "[REDACTED]".
4
+ * Returns the original value when `keys` is empty.
5
+ */
6
+ export declare function redactPayload(value: unknown, keys: string[]): unknown;
7
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/utils/redact.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAIrE"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Deep-clones a value and replaces any object property whose key matches
3
+ * one of `keys` (case-insensitive) with "[REDACTED]".
4
+ * Returns the original value when `keys` is empty.
5
+ */
6
+ export function redactPayload(value, keys) {
7
+ if (keys.length === 0)
8
+ return value;
9
+ const lowerKeys = new Set(keys.map((k) => k.toLowerCase()));
10
+ return redact(value, lowerKeys);
11
+ }
12
+ function redact(value, keys) {
13
+ if (value === null || value === undefined)
14
+ return value;
15
+ if (typeof value !== 'object')
16
+ return value;
17
+ if (Array.isArray(value)) {
18
+ return value.map((item) => redact(item, keys));
19
+ }
20
+ const out = {};
21
+ for (const [k, v] of Object.entries(value)) {
22
+ out[k] = keys.has(k.toLowerCase()) ? '[REDACTED]' : redact(v, keys);
23
+ }
24
+ return out;
25
+ }
26
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/utils/redact.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc,EAAE,IAAc;IAC1D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACnC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IAC3D,OAAO,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,SAAS,MAAM,CAAC,KAAc,EAAE,IAAiB;IAC/C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE3C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,GAAG,GAA4B,EAAE,CAAA;IACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QACtE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IACrE,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elasticdash-test",
3
- "version": "0.1.17",
3
+ "version": "0.1.18-alpha-2",
4
4
  "description": "AI-native test runner for ElasticDash workflow testing",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,14 @@
17
17
  "./http": {
18
18
  "types": "./dist/http.d.ts",
19
19
  "default": "./dist/http.js"
20
+ },
21
+ "./observability": {
22
+ "types": "./dist/observability.d.ts",
23
+ "default": "./dist/observability.js"
24
+ },
25
+ "./portal": {
26
+ "types": "./dist/portal-server.d.ts",
27
+ "default": "./dist/portal-server.js"
20
28
  }
21
29
  },
22
30
  "files": [
@@ -43,6 +51,7 @@
43
51
  "expect": "^29.7.0",
44
52
  "express": "^5.2.1",
45
53
  "fast-glob": "^3.3.2",
54
+ "socket.io-client": "^4.8.3",
46
55
  "tsx": "^4.15.6"
47
56
  },
48
57
  "devDependencies": {
@@ -1,4 +1,4 @@
1
- export type WorkflowEventType = 'ai' | 'tool' | 'http' | 'db' | 'side_effect'
1
+ export type WorkflowEventType = 'ai' | 'tool' | 'http' | 'db' | 'side_effect' | 'workflow'
2
2
 
3
3
  export interface WorkflowEvent {
4
4
  id: number
@@ -18,6 +18,10 @@ export interface WorkflowEvent {
18
18
  streamed?: boolean
19
19
  /** Raw buffered text of a streamed response (used for replay) */
20
20
  streamRaw?: string
21
+ /** Schema version for forward compatibility (default 1) */
22
+ schemaVersion?: number
23
+ /** Optional request-level trace ID for grouping events within a session */
24
+ traceId?: string
21
25
  }
22
26
 
23
27
  export interface WorkflowTrace {
package/src/cli.ts CHANGED
@@ -12,6 +12,8 @@ import { runFiles } from './runner.js'
12
12
  import { reportResults } from './reporter.js'
13
13
  import { startBrowserUiServer, type UiEvent } from './browser-ui.js'
14
14
  import { startDashboardServer } from './dashboard-server.js'
15
+ import { initObservability, shutdownObservability } from './observability.js'
16
+ import { startPortalServer } from './portal-server.js'
15
17
 
16
18
  function stripAnsi(input?: string): string | undefined {
17
19
  if (!input) return input
@@ -306,6 +308,113 @@ async function bootstrap(): Promise<void> {
306
308
  process.once('SIGTERM', cleanup)
307
309
  })
308
310
 
311
+ // elasticdash observe
312
+ program
313
+ .command('observe')
314
+ .description('Start observability mode — stream trace events to ElasticDash backend')
315
+ .option('--server <url>', 'ElasticDash backend API URL', process.env.ELASTICDASH_API_URL)
316
+ .option('--api-key <key>', 'Project API key', process.env.ELASTICDASH_API_KEY)
317
+ .option('--service-id <id>', 'Service identifier', process.env.ELASTICDASH_SERVICE_ID)
318
+ .action(async (options: { server?: string; apiKey?: string; serviceId?: string }) => {
319
+ const serverUrl = options.server
320
+ if (!serverUrl) {
321
+ console.error('[elasticdash] Error: --server or ELASTICDASH_API_URL is required')
322
+ process.exit(1)
323
+ }
324
+
325
+ const handle = initObservability({
326
+ serverUrl,
327
+ apiKey: options.apiKey,
328
+ serviceId: options.serviceId,
329
+ })
330
+
331
+ console.log(`[elasticdash] Observability active`)
332
+ console.log(` Session ID : ${handle.sessionId}`)
333
+ console.log(` Server : ${serverUrl}`)
334
+ console.log(` Service : ${options.serviceId ?? process.env.ELASTICDASH_SERVICE_ID ?? 'unknown-service'}`)
335
+ console.log(`[elasticdash] Press Ctrl+C to stop`)
336
+
337
+ let isShuttingDown = false
338
+ const cleanup = async () => {
339
+ if (isShuttingDown) {
340
+ process.exit(1)
341
+ }
342
+ isShuttingDown = true
343
+ console.log('\n[elasticdash] Shutting down observability...')
344
+ await shutdownObservability()
345
+ process.exit(0)
346
+ }
347
+
348
+ process.once('SIGINT', cleanup)
349
+ process.once('SIGTERM', cleanup)
350
+ })
351
+
352
+ // elasticdash portal
353
+ program
354
+ .command('portal')
355
+ .description('Start a portal server to receive and execute rerun tasks from ElasticDash backend')
356
+ .option('--server <url>', 'ElasticDash backend API URL to POST results to', process.env.ELASTICDASH_API_URL)
357
+ .option('--api-key <key>', 'Project API key', process.env.ELASTICDASH_API_KEY)
358
+ .option('--port <port>', 'Portal server port', (v) => Number(v), process.env.ELASTICDASH_PORTAL_PORT ? Number(process.env.ELASTICDASH_PORTAL_PORT) : 4574)
359
+ .option('--allowed-origins <origins>', 'Comma-separated list of additional allowed origin domains', process.env.ELASTICDASH_ALLOWED_ORIGINS)
360
+ .action(async (options: { server?: string; apiKey?: string; port: number; allowedOrigins?: string }) => {
361
+ const backendUrl = options.server
362
+ if (!backendUrl) {
363
+ console.error('[elasticdash] Error: --server or ELASTICDASH_API_URL is required')
364
+ process.exit(1)
365
+ }
366
+
367
+ const allowedOrigins = options.allowedOrigins
368
+ ? options.allowedOrigins.split(',').map(s => s.trim()).filter(Boolean)
369
+ : undefined
370
+
371
+ const handle = await startPortalServer({
372
+ port: options.port,
373
+ backendUrl,
374
+ apiKey: options.apiKey,
375
+ cwd,
376
+ allowedOrigins,
377
+ })
378
+
379
+ console.log(`[elasticdash] Portal server running`)
380
+ console.log(` URL : ${handle.url}`)
381
+ console.log(` Backend : ${backendUrl}`)
382
+ console.log(` Port : ${handle.port}`)
383
+ console.log(`[elasticdash] Waiting for tasks from backend... Press Ctrl+C to stop`)
384
+
385
+ // Register with backend
386
+ try {
387
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' }
388
+ if (options.apiKey) headers['Authorization'] = `Bearer ${options.apiKey}`
389
+ const res = await fetch(`${backendUrl}/api/portal/register`, {
390
+ method: 'POST',
391
+ headers,
392
+ body: JSON.stringify({ portalUrl: handle.url }),
393
+ })
394
+ if (res.ok) {
395
+ console.log(`[elasticdash] Registered with backend`)
396
+ } else {
397
+ console.warn(`[elasticdash] Backend registration returned ${res.status} — portal will still accept tasks directly`)
398
+ }
399
+ } catch {
400
+ console.warn(`[elasticdash] Could not register with backend — portal will still accept tasks directly`)
401
+ }
402
+
403
+ let isShuttingDown = false
404
+ const cleanup = async () => {
405
+ if (isShuttingDown) {
406
+ process.exit(1)
407
+ }
408
+ isShuttingDown = true
409
+ console.log('\n[elasticdash] Shutting down portal...')
410
+ await handle.close()
411
+ process.exit(0)
412
+ }
413
+
414
+ process.once('SIGINT', cleanup)
415
+ process.once('SIGTERM', cleanup)
416
+ })
417
+
309
418
  await program.parseAsync(process.argv)
310
419
  }
311
420