terse-cli 0.9.19 → 0.9.21

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 (126) hide show
  1. package/README.md +2 -2
  2. package/dist/cliUi.d.ts +3 -1
  3. package/dist/cliUi.d.ts.map +1 -1
  4. package/dist/cliUi.js +3 -2
  5. package/dist/cliUi.js.map +1 -1
  6. package/dist/commands/actor.d.ts.map +1 -1
  7. package/dist/commands/actor.js +32 -5
  8. package/dist/commands/actor.js.map +1 -1
  9. package/dist/commands/actorGenerate.d.ts.map +1 -1
  10. package/dist/commands/actorGenerate.js +22 -86
  11. package/dist/commands/actorGenerate.js.map +1 -1
  12. package/dist/commands/attach.d.ts +0 -7
  13. package/dist/commands/attach.d.ts.map +1 -1
  14. package/dist/commands/attach.js +220 -189
  15. package/dist/commands/attach.js.map +1 -1
  16. package/dist/commands/deploy.d.ts.map +1 -1
  17. package/dist/commands/deploy.js +15 -3
  18. package/dist/commands/deploy.js.map +1 -1
  19. package/dist/commands/generate.d.ts +0 -4
  20. package/dist/commands/generate.d.ts.map +1 -1
  21. package/dist/commands/generate.js +161 -127
  22. package/dist/commands/generate.js.map +1 -1
  23. package/dist/commands/history.d.ts +0 -15
  24. package/dist/commands/history.d.ts.map +1 -1
  25. package/dist/commands/history.js +139 -105
  26. package/dist/commands/history.js.map +1 -1
  27. package/dist/commands/install.d.ts.map +1 -1
  28. package/dist/commands/install.js +6 -4
  29. package/dist/commands/install.js.map +1 -1
  30. package/dist/commands/integrate.d.ts +0 -48
  31. package/dist/commands/integrate.d.ts.map +1 -1
  32. package/dist/commands/integrate.js +897 -699
  33. package/dist/commands/integrate.js.map +1 -1
  34. package/dist/commands/integrateUse.d.ts +0 -12
  35. package/dist/commands/integrateUse.d.ts.map +1 -1
  36. package/dist/commands/integrateUse.js +132 -98
  37. package/dist/commands/integrateUse.js.map +1 -1
  38. package/dist/commands/listen.d.ts +0 -7
  39. package/dist/commands/listen.d.ts.map +1 -1
  40. package/dist/commands/listen.js +98 -81
  41. package/dist/commands/listen.js.map +1 -1
  42. package/dist/commands/memory.d.ts +0 -21
  43. package/dist/commands/memory.d.ts.map +1 -1
  44. package/dist/commands/memory.js +147 -128
  45. package/dist/commands/memory.js.map +1 -1
  46. package/dist/commands/replay.d.ts +0 -2
  47. package/dist/commands/replay.d.ts.map +1 -1
  48. package/dist/commands/replay.js +37 -28
  49. package/dist/commands/replay.js.map +1 -1
  50. package/dist/commands/resume.d.ts +0 -4
  51. package/dist/commands/resume.d.ts.map +1 -1
  52. package/dist/commands/resume.js +34 -28
  53. package/dist/commands/resume.js.map +1 -1
  54. package/dist/commands/run.d.ts +0 -2
  55. package/dist/commands/run.d.ts.map +1 -1
  56. package/dist/commands/run.js +78 -70
  57. package/dist/commands/run.js.map +1 -1
  58. package/dist/commands/state.d.ts +0 -18
  59. package/dist/commands/state.d.ts.map +1 -1
  60. package/dist/commands/state.js +129 -108
  61. package/dist/commands/state.js.map +1 -1
  62. package/dist/commands/test.d.ts +0 -28
  63. package/dist/commands/test.d.ts.map +1 -1
  64. package/dist/commands/test.js +509 -403
  65. package/dist/commands/test.js.map +1 -1
  66. package/dist/commands/toolRun.d.ts +0 -6
  67. package/dist/commands/toolRun.d.ts.map +1 -1
  68. package/dist/commands/toolRun.js +149 -134
  69. package/dist/commands/toolRun.js.map +1 -1
  70. package/dist/index.js +1 -1
  71. package/dist/index.js.map +1 -1
  72. package/dist/integrationApi.d.ts +0 -15
  73. package/dist/integrationApi.d.ts.map +1 -1
  74. package/dist/integrationApi.js +75 -49
  75. package/dist/integrationApi.js.map +1 -1
  76. package/dist/loadJob.d.ts +0 -11
  77. package/dist/loadJob.d.ts.map +1 -1
  78. package/dist/loadJob.js +54 -46
  79. package/dist/loadJob.js.map +1 -1
  80. package/dist/providers/LanguageProvider.d.ts +0 -29
  81. package/dist/providers/LanguageProvider.d.ts.map +1 -1
  82. package/dist/providers/LanguageProvider.js +3 -0
  83. package/dist/providers/LanguageProvider.js.map +1 -1
  84. package/dist/providers/typescript/TypeScriptProvider.d.ts +0 -24
  85. package/dist/providers/typescript/TypeScriptProvider.d.ts.map +1 -1
  86. package/dist/providers/typescript/TypeScriptProvider.js +113 -113
  87. package/dist/providers/typescript/TypeScriptProvider.js.map +1 -1
  88. package/dist/providers/typescript/runtimes/JobRuntime.d.ts +0 -26
  89. package/dist/providers/typescript/runtimes/JobRuntime.d.ts.map +1 -1
  90. package/dist/providers/typescript/runtimes/JobRuntime.js +51 -18
  91. package/dist/providers/typescript/runtimes/JobRuntime.js.map +1 -1
  92. package/dist/providers/typescript/runtimes/directJobRuntime.d.ts +0 -2
  93. package/dist/providers/typescript/runtimes/directJobRuntime.d.ts.map +1 -1
  94. package/dist/providers/typescript/runtimes/directJobRuntime.js +60 -49
  95. package/dist/providers/typescript/runtimes/directJobRuntime.js.map +1 -1
  96. package/dist/providers/typescript/runtimes/durableJobRuntime.d.ts +0 -2
  97. package/dist/providers/typescript/runtimes/durableJobRuntime.d.ts.map +1 -1
  98. package/dist/providers/typescript/runtimes/durableJobRuntime.js +168 -146
  99. package/dist/providers/typescript/runtimes/durableJobRuntime.js.map +1 -1
  100. package/dist/providers/typescript/runtimes/index.d.ts +0 -3
  101. package/dist/providers/typescript/runtimes/index.d.ts.map +1 -1
  102. package/dist/providers/typescript/runtimes/index.js +5 -2
  103. package/dist/providers/typescript/runtimes/index.js.map +1 -1
  104. package/dist/providers/typescript/runtimes/session.d.ts +0 -4
  105. package/dist/providers/typescript/runtimes/session.d.ts.map +1 -1
  106. package/dist/providers/typescript/runtimes/session.js +78 -64
  107. package/dist/providers/typescript/runtimes/session.js.map +1 -1
  108. package/dist/providers/typescript/terseWorkflow.d.ts +0 -7
  109. package/dist/providers/typescript/terseWorkflow.d.ts.map +1 -1
  110. package/dist/providers/typescript/terseWorkflow.js +12 -7
  111. package/dist/providers/typescript/terseWorkflow.js.map +1 -1
  112. package/dist/runLocalTestJob.d.ts +0 -19
  113. package/dist/runLocalTestJob.d.ts.map +1 -1
  114. package/dist/runLocalTestJob.js +55 -29
  115. package/dist/runLocalTestJob.js.map +1 -1
  116. package/dist/setup.js +1 -1
  117. package/dist/setup.js.map +1 -1
  118. package/dist/sourceArchive.d.ts +1 -1
  119. package/dist/sourceArchive.d.ts.map +1 -1
  120. package/dist/sourceArchive.js +1 -0
  121. package/dist/sourceArchive.js.map +1 -1
  122. package/package.json +3 -4
  123. package/templates/typescript/init/package.json.hbs +1 -2
  124. package/templates/typescript/terse-transport.d.ts +25 -0
  125. package/templates/typescript/terse-transport.js +117 -0
  126. package/templates/typescript/tsconfig.json +13 -0
@@ -1,404 +1,510 @@
1
- import { cancel, intro, isCancel, log, outro, select } from "@clack/prompts";
2
- import chalk from "chalk";
3
- import { DateTime } from "luxon";
4
- import fs from "node:fs";
5
- import { IntegrationType } from "terse-sdk";
6
- import { ApiRoutes, hydrateSerializedEvent, toEventFixture } from "terse-types";
7
- import { fetchWithAuth, readApiKeyOrBail } from "../api.js";
8
- import { assertProjectRoot } from "../assertProjectRoot.js";
9
- import { CliError } from "../cliError.js";
10
- import { isNonInteractive } from "../cliHelpers.js";
11
- import { createRunIndicator, createSpinner, interceptConsole } from "../cliUi.js";
12
- import { parseEventFixtureJson } from "../eventFixture.js";
13
- import { loadJob } from "../loadJob.js";
14
- import { readProjectConfig, readProjectConfigOrBail } from "../projectConfig.js";
15
- import { resolveProvider } from "../providers/resolveProvider.js";
16
- import { remoteDispatchNotice, runLocalTestJob } from "../runLocalTestJob.js";
17
- export async function test(jobName, verbose, provider = resolveProvider(), entryFile, freshState) {
18
- if (isNonInteractive()) {
19
- throw new CliError("test_requires_interactive", "`terse test` needs a terminal.", {
20
- detail: "In non-interactive contexts, use `terse test list` to enumerate sample events and `terse test run --id <id>` to execute one."
21
- });
22
- }
23
- intro("terse test");
24
- assertProjectRoot(provider);
25
- const { job } = await loadJob(provider, jobName, entryFile);
26
- const apiKey = readApiKeyOrBail({
27
- title: "Error: Not authenticated. Unable to fetch sample events.",
28
- detail: "Run `terse auth login` to authenticate, or set TERSE_API_KEY in your environment."
29
- });
30
- const spinner = createSpinner();
31
- spinner.start("Fetching sample events");
32
- let candidates = [];
33
- let webhookEndpoints = [];
34
- try {
35
- const result = await fetchSampleEventCandidatesForJob(job, apiKey);
36
- candidates = result.candidates;
37
- webhookEndpoints = result.webhookEndpoints;
38
- spinner.stop(`Fetched ${candidates.length} sample event${candidates.length === 1 ? "" : "s"}`);
39
- }
40
- catch (err) {
41
- spinner.stop(err instanceof Error ? err.message : "Failed to fetch sample events.");
42
- }
43
- if (candidates.length === 0) {
44
- if (webhookEndpoints.length > 0) {
45
- log.info(emptyWebhookCurlHint(job.name, webhookEndpoints));
46
- outro("Done");
47
- return;
48
- }
49
- throw new CliError("no_sample_events", "No sample events available.", {
50
- detail: "Make sure your triggers are configured and events have been received."
51
- });
52
- }
53
- log.info(`Testing job ${chalk.cyan(job.name)} on the ${chalk.cyan(provider.runtimeName(job))} runtime`);
54
- const event = await chooseSampleEvent(candidates, apiKey);
55
- const projectId = readProjectConfigOrBail().projectId;
56
- const runView = createRunIndicator(`Running ${formatEventLabel(event)}`);
57
- const restoreConsole = interceptConsole(line => runView.logLine(line));
58
- runView.start();
59
- try {
60
- const { runId, local } = await runLocalTestJob(provider, job, event, {
61
- projectId,
62
- apiKey,
63
- verbose: !!verbose,
64
- entryFile,
65
- freshState,
66
- pauseUiAround: async (fn) => {
67
- runView.pause("Awaiting input");
68
- try {
69
- return await fn();
70
- }
71
- finally {
72
- runView.start();
73
- }
74
- }
75
- });
76
- restoreConsole();
77
- if (local)
78
- runView.succeed("Run completed");
79
- else
80
- runView.succeed(remoteDispatchNotice(runId));
81
- outro("Done");
82
- }
83
- catch (error) {
84
- restoreConsole();
85
- runView.fail("Run failed");
86
- throw error;
87
- }
88
- }
89
- export async function testList(opts = {}) {
90
- const provider = opts.provider ?? resolveProvider();
91
- assertProjectRoot(provider);
92
- const { job } = await loadJob(provider, opts.jobName, opts.entryFile, { nonInteractive: true });
93
- const apiKey = readApiKeyOrBail();
94
- const { candidates, webhookEndpoints } = await fetchSampleEventCandidatesForJob(job, apiKey);
95
- if (opts.json) {
96
- const payload = {
97
- job: job.name,
98
- events: candidates.map(candidate => ({
99
- id: candidate.id,
100
- integrationType: candidate.integrationType,
101
- eventType: candidate.eventType,
102
- label: candidate.label,
103
- subtitle: candidate.subtitle,
104
- entityType: candidate.kind === "ref" ? candidate.entityType : null,
105
- entityId: candidate.kind === "ref" ? candidate.entityId : null
106
- })),
107
- webhookEndpoints
108
- };
109
- process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
110
- return;
111
- }
112
- if (candidates.length === 0) {
113
- if (webhookEndpoints.length > 0) {
114
- process.stdout.write(emptyWebhookCurlHint(job.name, webhookEndpoints) + "\n");
115
- }
116
- else {
117
- process.stdout.write("No sample events available.\n");
118
- }
119
- return;
120
- }
121
- process.stdout.write(`Sample events for job ${job.name}:\n\n`);
122
- for (const candidate of candidates) {
123
- const subtitle = candidate.subtitle ? ` — ${normalizeSingleLine(candidate.subtitle)}` : "";
124
- process.stdout.write(` ${chalk.cyan(candidate.id)} ${candidate.integrationType}/${candidate.eventType} ${candidate.label}${chalk.dim(subtitle)}\n`);
125
- }
126
- process.stdout.write("\n");
127
- process.stdout.write(chalk.dim(`Run one with: terse test run --id <id>\n`));
128
- }
129
- export async function testShow(opts) {
130
- const provider = opts.provider ?? resolveProvider();
131
- assertProjectRoot(provider);
132
- const event = await resolveEventById(provider, opts.id, opts.jobName, opts.entryFile);
133
- if (opts.json) {
134
- process.stdout.write(JSON.stringify({ id: opts.id, event: toEventFixture(event) }, null, 2) + "\n");
135
- return;
136
- }
137
- process.stdout.write(`${chalk.bold(formatEventLabel(event))}\n`);
138
- if (event.display?.subtitle)
139
- process.stdout.write(chalk.dim(event.display.subtitle) + "\n");
140
- process.stdout.write(`\n${event.formattedContent}\n`);
141
- }
142
- export async function testRun(opts) {
143
- const provider = opts.provider ?? resolveProvider();
144
- assertProjectRoot(provider);
145
- const modeCount = [opts.id, opts.eventJson, opts.eventFile].filter(Boolean).length;
146
- if (modeCount === 0) {
147
- throw new CliError("missing_event", "Provide one of --id, --event, or --event-file.", {
148
- detail: "Run `terse test list --json` to see available sample ids."
149
- });
150
- }
151
- if (modeCount > 1) {
152
- throw new CliError("conflicting_flags", "--id, --event, and --event-file are mutually exclusive.");
153
- }
154
- const { job } = await loadJob(provider, opts.jobName, opts.entryFile, { nonInteractive: true });
155
- let event;
156
- if (opts.id) {
157
- event = await resolveEventByIdForJob(job, opts.id);
158
- }
159
- else {
160
- const rawJson = opts.eventJson ?? readEventFile(opts.eventFile);
161
- event = parseEventFixtureJson(rawJson, "--event / --event-file");
162
- }
163
- const apiKey = readApiKeyOrBail();
164
- const projectId = readProjectConfigOrBail().projectId;
165
- const sideEffects = [];
166
- try {
167
- const { runId, local } = await runLocalTestJob(provider, job, event, {
168
- projectId,
169
- apiKey,
170
- verbose: !!opts.verbose,
171
- entryFile: opts.entryFile,
172
- freshState: opts.freshState,
173
- onSessionEvent: sessionEvent => {
174
- if (sessionEvent.type === "action" && isSideEffect(sessionEvent.action))
175
- sideEffects.push(sessionEvent.action);
176
- }
177
- });
178
- if (!local)
179
- console.log(chalk.cyan(` ${remoteDispatchNotice(runId)}`));
180
- if (isNonInteractive())
181
- printAgentTestReport(job.name, local ? "passed" : "dispatched", sideEffects);
182
- }
183
- catch (error) {
184
- if (isNonInteractive())
185
- printAgentTestReport(job.name, "failed", sideEffects, error);
186
- throw error;
187
- }
188
- }
189
- function isSideEffect(action) {
190
- return action.type === "create" || action.type === "update" || action.type === "delete" || action.type === "approve";
191
- }
192
- function printAgentTestReport(jobName, result, sideEffects, error) {
193
- const lines = ["--- TERSE TEST REPORT: surface this to the user ---", `Tested: ${jobName}`, `Result: ${result}`];
194
- if (sideEffects.length > 0) {
195
- lines.push("Observed side effects:");
196
- for (const effect of sideEffects)
197
- lines.push(`- ${effect.action} — ${effect.details}${effect.url ? ` (${effect.url})` : ""}`);
198
- }
199
- if (error instanceof CliError)
200
- lines.push(`Error: ${error.message}${error.opts.detail ? ` — ${normalizeSingleLine(error.opts.detail)}` : ""}`);
201
- else if (error instanceof Error)
202
- lines.push(`Error: ${error.message}`);
203
- lines.push('CODING AGENT: Your first sentence after this command must be either "The test ran as expected" or "The test did not run as expected." Then list the observed side effects exactly and explain any mismatch with the expectation you stated before the run.');
204
- process.stdout.write(`\n${lines.join("\n")}\n`);
205
- }
206
- async function resolveEventById(provider, id, jobNameHint, entryFile) {
207
- const { job } = await loadJob(provider, jobNameHint, entryFile, { nonInteractive: true });
208
- return resolveEventByIdForJob(job, id);
209
- }
210
- async function resolveEventByIdForJob(job, id) {
211
- const apiKey = readApiKeyOrBail();
212
- const { candidates } = await fetchSampleEventCandidatesForJob(job, apiKey);
213
- const match = candidates.find(candidate => candidate.id === id);
214
- if (match)
215
- return hydrateCandidateEvent(match, apiKey);
216
- throw new CliError("sample_not_found", `No sample event matches id ${id}.`, {
217
- detail: "Run `terse test list` to refresh — the event may have rotated out of the sample buffer."
218
- });
219
- }
220
- function readEventFile(filePath) {
221
- try {
222
- return fs.readFileSync(filePath, "utf-8");
223
- }
224
- catch (err) {
225
- throw new CliError("event_file_unreadable", `Could not read event file: ${filePath}`, {
226
- detail: err instanceof Error ? err.message : String(err)
227
- });
228
- }
229
- }
230
- async function fetchSampleEventCandidatesForJob(job, apiKey) {
231
- const timeTriggers = job.triggers.filter(t => t.integrationType === IntegrationType.CRON_JOB);
232
- const sampleEventTriggers = job.triggers.filter(t => t.integrationType !== IntegrationType.CRON_JOB);
233
- const candidates = [];
234
- const webhookEndpoints = [];
235
- if (sampleEventTriggers.length > 0) {
236
- const projectId = readProjectConfig()?.projectId;
237
- const result = await fetchWithAuth(ApiRoutes.SDK.SAMPLE_EVENTS, apiKey, {
238
- projectId,
239
- jobName: job.name,
240
- triggers: sampleEventTriggers.map(trigger => ({
241
- triggerId: undefined,
242
- integrationId: trigger.integrationId,
243
- integrationType: trigger.integrationType,
244
- config: trigger
245
- }))
246
- }, "POST");
247
- let storedIndex = 0;
248
- for (const event of result.events) {
249
- if (event.entity) {
250
- candidates.push({
251
- id: encodeRefId(event.entity.entityType, event.entity.entityId),
252
- kind: "ref",
253
- integrationType: event.serializedEvent.integrationType,
254
- eventType: event.serializedEvent.eventType,
255
- label: normalizeSingleLine(event.serializedEvent.display?.title || `${event.serializedEvent.integrationType} / ${event.serializedEvent.eventType}`),
256
- subtitle: event.serializedEvent.display?.subtitle ?? null,
257
- entityType: event.entity.entityType,
258
- entityId: event.entity.entityId
259
- });
260
- }
261
- else {
262
- const time = event.recordedAt ? (DateTime.fromISO(event.recordedAt).toRelative() ?? null) : null;
263
- const method = event.serializedEvent.display?.subtitle ?? null;
264
- const labelParts = [event.serializedEvent.integrationType === IntegrationType.WEBHOOK ? "Webhook" : event.serializedEvent.integrationType, method, time].filter(Boolean);
265
- candidates.push({
266
- id: `stored:${storedIndex++}`,
267
- kind: "stored",
268
- integrationType: event.serializedEvent.integrationType,
269
- eventType: event.serializedEvent.eventType,
270
- label: labelParts.join(" · "),
271
- subtitle: event.serializedEvent.display?.subtitle ?? null,
272
- recordedAt: event.recordedAt ?? null,
273
- event: event.serializedEvent
274
- });
275
- }
276
- }
277
- if (result.webhookEndpoints)
278
- webhookEndpoints.push(...result.webhookEndpoints);
279
- }
280
- for (const [index, trigger] of timeTriggers.entries()) {
281
- const event = hydrateSerializedEvent({
282
- integrationType: IntegrationType.CRON_JOB,
283
- eventType: "cron",
284
- inputId: trigger.integrationId,
285
- isManualTrigger: true,
286
- manualContext: `Manual trigger from terse test (schedule: ${trigger.cronExpression})`
287
- });
288
- candidates.push({
289
- id: `synthetic:cron:${index}`,
290
- kind: "synthetic",
291
- integrationType: event.integrationType,
292
- eventType: event.eventType,
293
- label: formatEventLabel(event),
294
- subtitle: event.display?.subtitle ?? null,
295
- event
296
- });
297
- }
298
- return { candidates, webhookEndpoints };
299
- }
300
- function formatEventLabel(event) {
301
- return normalizeSingleLine(event.display?.title || `${event.integrationType} / ${event.eventType}`);
302
- }
303
- function formatEventHint(candidate) {
304
- if (candidate.kind === "stored") {
305
- const preview = previewPayload(candidate.event);
306
- if (preview)
307
- return truncate(preview, 120);
308
- if (candidate.subtitle)
309
- return truncate(normalizeSingleLine(candidate.subtitle), 120);
310
- return truncate(`${candidate.integrationType}/${candidate.eventType}`, 120);
311
- }
312
- if (candidate.subtitle)
313
- return truncate(normalizeSingleLine(candidate.subtitle), 120);
314
- if (candidate.kind === "ref")
315
- return truncate(`${candidate.integrationType}/${candidate.eventType}`, 120);
316
- return "Synthetic sample event";
317
- }
318
- function previewPayload(event) {
319
- const data = event.data;
320
- const body = data && "body" in data ? data.body : data;
321
- if (body == null)
322
- return null;
323
- try {
324
- return normalizeSingleLine(JSON.stringify(body));
325
- }
326
- catch {
327
- return null;
328
- }
329
- }
330
- function truncate(value, maxLength) {
331
- if (value.length <= maxLength) {
332
- return value;
333
- }
334
- return `${value.slice(0, maxLength - 1)}…`;
335
- }
336
- function normalizeSingleLine(value) {
337
- return value
338
- .replace(/\s*\n+\s*/g, " ")
339
- .replace(/\s+/g, " ")
340
- .trim();
341
- }
342
- function abortIfCancelled(value) {
343
- if (isCancel(value)) {
344
- cancel("Operation cancelled.");
345
- process.exit(0);
346
- }
347
- return value;
348
- }
349
- async function chooseSampleEvent(candidates, apiKey) {
350
- while (true) {
351
- const choice = abortIfCancelled(await select({
352
- message: "Choose a sample event",
353
- options: candidates.map((candidate, index) => ({
354
- label: `${candidate.label} ${chalk.dim("›")}`,
355
- hint: formatEventHint(candidate),
356
- value: index
357
- }))
358
- }));
359
- const event = await hydrateCandidateEvent(candidates[choice], apiKey);
360
- const action = await inspectSampleEvent(event);
361
- if (action === "run") {
362
- return event;
363
- }
364
- }
365
- }
366
- async function hydrateCandidateEvent(candidate, apiKey) {
367
- if (candidate.kind === "synthetic" || candidate.kind === "stored") {
368
- return candidate.event;
369
- }
370
- const payload = await fetchWithAuth(ApiRoutes.SDK.HYDRATE_SAMPLE_EVENT, apiKey, { entityType: candidate.entityType, entityId: candidate.entityId }, "POST");
371
- return payload.event;
372
- }
373
- function encodeRefId(entityType, entityId) {
374
- const token = Buffer.from(JSON.stringify({ entityType, entityId }), "utf8").toString("base64url");
375
- return `ref:${token}`;
376
- }
377
- function emptyWebhookCurlHint(jobName, endpoints) {
378
- const lines = [`No past webhook events for ${chalk.cyan(jobName)} yet. Trigger one with:`, ""];
379
- for (const { webhookUrl } of endpoints) {
380
- lines.push(` curl -X POST ${chalk.cyan(webhookUrl)} \\`);
381
- lines.push(` -H 'Content-Type: application/json' \\`);
382
- lines.push(` -d '{ "your": "payload" }'`);
383
- lines.push("");
384
- }
385
- lines.push(chalk.dim("After the first event lands, `terse test` will pick it up automatically."));
386
- return lines.join("\n");
387
- }
388
- async function inspectSampleEvent(event) {
389
- log.info(chalk.bold(formatEventLabel(event)));
390
- if (event.display?.subtitle) {
391
- console.log(chalk.dim(event.display.subtitle));
392
- }
393
- console.log("");
394
- console.log(chalk.dim(truncate(normalizeSingleLine(event.formattedContent), 300)));
395
- console.log("");
396
- return abortIfCancelled(await select({
397
- message: "Run this event?",
398
- options: [
399
- { label: "Run this sample event", value: "run" },
400
- { label: "Choose another event", value: "back" }
401
- ]
402
- }));
403
- }
1
+ "use strict";
2
+ // LEGACY-JOBS: Commented out while Terse is actors-only. Uncomment to restore; see the note in terse-sdk/src/index.ts.
3
+ // import { cancel, intro, isCancel, log, outro, select } from "@clack/prompts"
4
+ // import chalk from "chalk"
5
+ // import { DateTime } from "luxon"
6
+ // import fs from "node:fs"
7
+ // import type { CreateJobParameters } from "terse-sdk"
8
+ // import { IntegrationType } from "terse-sdk"
9
+ // import { ApiRoutes, hydrateSerializedEvent, toEventFixture } from "terse-types"
10
+ // import type { RunHistoryAction, SdkSampleEventsResponse, SerializedEvent } from "terse-types"
11
+ //
12
+ // import { fetchWithAuth, readApiKeyOrBail } from "../api.js"
13
+ // import { assertProjectRoot } from "../assertProjectRoot.js"
14
+ // import { CliError } from "../cliError.js"
15
+ // import { isNonInteractive } from "../cliHelpers.js"
16
+ // import { createRunIndicator, createSpinner, interceptConsole } from "../cliUi.js"
17
+ // import { parseEventFixtureJson } from "../eventFixture.js"
18
+ // import { loadJob } from "../loadJob.js"
19
+ // import { readProjectConfig, readProjectConfigOrBail } from "../projectConfig.js"
20
+ // import type { LanguageProvider } from "../providers/LanguageProvider.js"
21
+ // import { resolveProvider } from "../providers/resolveProvider.js"
22
+ // import { remoteDispatchNotice, runLocalTestJob } from "../runLocalTestJob.js"
23
+ // import { parseSerializedEventJson } from "../serializedEvent.js"
24
+ //
25
+ // export async function test(jobName?: string, verbose?: boolean, provider: LanguageProvider = resolveProvider(), entryFile?: string, freshState?: boolean): Promise<void> {
26
+ // if (isNonInteractive()) {
27
+ // throw new CliError("test_requires_interactive", "`terse test` needs a terminal.", {
28
+ // detail: "In non-interactive contexts, use `terse test list` to enumerate sample events and `terse test run --id <id>` to execute one."
29
+ // })
30
+ // }
31
+ //
32
+ // intro("terse test")
33
+ // assertProjectRoot(provider)
34
+ //
35
+ // const { job } = await loadJob(provider, jobName, entryFile)
36
+ //
37
+ // const apiKey = readApiKeyOrBail({
38
+ // title: "Error: Not authenticated. Unable to fetch sample events.",
39
+ // detail: "Run `terse auth login` to authenticate, or set TERSE_API_KEY in your environment."
40
+ // })
41
+ //
42
+ // const spinner = createSpinner()
43
+ // spinner.start("Fetching sample events")
44
+ // let candidates: SampleEventCandidate[] = []
45
+ // let webhookEndpoints: SampleEventCandidatesResult["webhookEndpoints"] = []
46
+ // try {
47
+ // const result = await fetchSampleEventCandidatesForJob(job, apiKey)
48
+ // candidates = result.candidates
49
+ // webhookEndpoints = result.webhookEndpoints
50
+ // spinner.stop(`Fetched ${candidates.length} sample event${candidates.length === 1 ? "" : "s"}`)
51
+ // } catch (err) {
52
+ // spinner.stop(err instanceof Error ? err.message : "Failed to fetch sample events.")
53
+ // }
54
+ //
55
+ // if (candidates.length === 0) {
56
+ // if (webhookEndpoints.length > 0) {
57
+ // log.info(emptyWebhookCurlHint(job.name, webhookEndpoints))
58
+ // outro("Done")
59
+ // return
60
+ // }
61
+ // throw new CliError("no_sample_events", "No sample events available.", {
62
+ // detail: "Make sure your triggers are configured and events have been received."
63
+ // })
64
+ // }
65
+ //
66
+ // log.info(`Testing job ${chalk.cyan(job.name)} on the ${chalk.cyan(provider.runtimeName(job))} runtime`)
67
+ //
68
+ // const event = await chooseSampleEvent(candidates, apiKey)
69
+ //
70
+ // const projectId = readProjectConfigOrBail().projectId
71
+ // const runView = createRunIndicator(`Running ${formatEventLabel(event)}`)
72
+ // const restoreConsole = interceptConsole(line => runView.logLine(line))
73
+ // runView.start()
74
+ // try {
75
+ // const { runId, local } = await runLocalTestJob(provider, job, event, {
76
+ // projectId,
77
+ // apiKey,
78
+ // verbose: !!verbose,
79
+ // entryFile,
80
+ // freshState,
81
+ // pauseUiAround: async fn => {
82
+ // runView.pause("Awaiting input")
83
+ // try {
84
+ // return await fn()
85
+ // } finally {
86
+ // runView.start()
87
+ // }
88
+ // }
89
+ // })
90
+ // restoreConsole()
91
+ // if (local) runView.succeed("Run completed")
92
+ // else runView.succeed(remoteDispatchNotice(runId))
93
+ // outro("Done")
94
+ // } catch (error) {
95
+ // restoreConsole()
96
+ // runView.fail("Run failed")
97
+ // throw error
98
+ // }
99
+ // }
100
+ //
101
+ // export async function testList(opts: TestListOpts = {}): Promise<void> {
102
+ // const provider = opts.provider ?? resolveProvider()
103
+ // assertProjectRoot(provider)
104
+ //
105
+ // const { job } = await loadJob(provider, opts.jobName, opts.entryFile, { nonInteractive: true })
106
+ // const apiKey = readApiKeyOrBail()
107
+ //
108
+ // const { candidates, webhookEndpoints } = await fetchSampleEventCandidatesForJob(job, apiKey)
109
+ //
110
+ // if (opts.json) {
111
+ // const payload = {
112
+ // job: job.name,
113
+ // events: candidates.map(candidate => ({
114
+ // id: candidate.id,
115
+ // integrationType: candidate.integrationType,
116
+ // eventType: candidate.eventType,
117
+ // label: candidate.label,
118
+ // subtitle: candidate.subtitle,
119
+ // entityType: candidate.kind === "ref" ? candidate.entityType : null,
120
+ // entityId: candidate.kind === "ref" ? candidate.entityId : null
121
+ // })),
122
+ // webhookEndpoints
123
+ // }
124
+ // process.stdout.write(JSON.stringify(payload, null, 2) + "\n")
125
+ // return
126
+ // }
127
+ //
128
+ // if (candidates.length === 0) {
129
+ // if (webhookEndpoints.length > 0) {
130
+ // process.stdout.write(emptyWebhookCurlHint(job.name, webhookEndpoints) + "\n")
131
+ // } else {
132
+ // process.stdout.write("No sample events available.\n")
133
+ // }
134
+ // return
135
+ // }
136
+ //
137
+ // process.stdout.write(`Sample events for job ${job.name}:\n\n`)
138
+ // for (const candidate of candidates) {
139
+ // const subtitle = candidate.subtitle ? ` — ${normalizeSingleLine(candidate.subtitle)}` : ""
140
+ // process.stdout.write(` ${chalk.cyan(candidate.id)} ${candidate.integrationType}/${candidate.eventType} ${candidate.label}${chalk.dim(subtitle)}\n`)
141
+ // }
142
+ // process.stdout.write("\n")
143
+ // process.stdout.write(chalk.dim(`Run one with: terse test run --id <id>\n`))
144
+ // }
145
+ //
146
+ // export async function testShow(opts: TestShowOpts): Promise<void> {
147
+ // const provider = opts.provider ?? resolveProvider()
148
+ // assertProjectRoot(provider)
149
+ //
150
+ // const event = await resolveEventById(provider, opts.id, opts.jobName, opts.entryFile)
151
+ //
152
+ // if (opts.json) {
153
+ // process.stdout.write(JSON.stringify({ id: opts.id, event: toEventFixture(event) }, null, 2) + "\n")
154
+ // return
155
+ // }
156
+ //
157
+ // process.stdout.write(`${chalk.bold(formatEventLabel(event))}\n`)
158
+ // if (event.display?.subtitle) process.stdout.write(chalk.dim(event.display.subtitle) + "\n")
159
+ // process.stdout.write(`\n${event.formattedContent}\n`)
160
+ // }
161
+ //
162
+ // export async function testRun(opts: TestRunOpts): Promise<void> {
163
+ // const provider = opts.provider ?? resolveProvider()
164
+ // assertProjectRoot(provider)
165
+ //
166
+ // const modeCount = [opts.id, opts.eventJson, opts.eventFile].filter(Boolean).length
167
+ // if (modeCount === 0) {
168
+ // throw new CliError("missing_event", "Provide one of --id, --event, or --event-file.", {
169
+ // detail: "Run `terse test list --json` to see available sample ids."
170
+ // })
171
+ // }
172
+ // if (modeCount > 1) {
173
+ // throw new CliError("conflicting_flags", "--id, --event, and --event-file are mutually exclusive.")
174
+ // }
175
+ //
176
+ // const { job } = await loadJob(provider, opts.jobName, opts.entryFile, { nonInteractive: true })
177
+ //
178
+ // let event: SerializedEvent
179
+ // if (opts.id) {
180
+ // event = await resolveEventByIdForJob(job, opts.id)
181
+ // } else {
182
+ // const rawJson = opts.eventJson ?? readEventFile(opts.eventFile!)
183
+ // event = parseEventFixtureJson(rawJson, "--event / --event-file")
184
+ // }
185
+ //
186
+ // const apiKey = readApiKeyOrBail()
187
+ // const projectId = readProjectConfigOrBail().projectId
188
+ // const sideEffects: RunHistoryAction[] = []
189
+ // try {
190
+ // const { runId, local } = await runLocalTestJob(provider, job, event, {
191
+ // projectId,
192
+ // apiKey,
193
+ // verbose: !!opts.verbose,
194
+ // entryFile: opts.entryFile,
195
+ // freshState: opts.freshState,
196
+ // onSessionEvent: sessionEvent => {
197
+ // if (sessionEvent.type === "action" && isSideEffect(sessionEvent.action)) sideEffects.push(sessionEvent.action)
198
+ // }
199
+ // })
200
+ // if (!local) console.log(chalk.cyan(` ${remoteDispatchNotice(runId)}`))
201
+ // if (isNonInteractive()) printAgentTestReport(job.name, local ? "passed" : "dispatched", sideEffects)
202
+ // } catch (error) {
203
+ // if (isNonInteractive()) printAgentTestReport(job.name, "failed", sideEffects, error)
204
+ // throw error
205
+ // }
206
+ // }
207
+ //
208
+ // function isSideEffect(action: RunHistoryAction): boolean {
209
+ // return action.type === "create" || action.type === "update" || action.type === "delete" || action.type === "approve"
210
+ // }
211
+ //
212
+ // function printAgentTestReport(jobName: string, result: "passed" | "failed" | "dispatched", sideEffects: RunHistoryAction[], error?: unknown): void {
213
+ // const lines = ["--- TERSE TEST REPORT: surface this to the user ---", `Tested: ${jobName}`, `Result: ${result}`]
214
+ // if (sideEffects.length > 0) {
215
+ // lines.push("Observed side effects:")
216
+ // for (const effect of sideEffects) lines.push(`- ${effect.action} — ${effect.details}${effect.url ? ` (${effect.url})` : ""}`)
217
+ // }
218
+ // if (error instanceof CliError) lines.push(`Error: ${error.message}${error.opts.detail ? ` — ${normalizeSingleLine(error.opts.detail)}` : ""}`)
219
+ // else if (error instanceof Error) lines.push(`Error: ${error.message}`)
220
+ // lines.push(
221
+ // 'CODING AGENT: Your first sentence after this command must be either "The test ran as expected" or "The test did not run as expected." Then list the observed side effects exactly and explain any mismatch with the expectation you stated before the run.'
222
+ // )
223
+ // process.stdout.write(`\n${lines.join("\n")}\n`)
224
+ // }
225
+ //
226
+ // async function resolveEventById(provider: LanguageProvider, id: string, jobNameHint: string | undefined, entryFile: string | undefined): Promise<SerializedEvent> {
227
+ // const { job } = await loadJob(provider, jobNameHint, entryFile, { nonInteractive: true })
228
+ // return resolveEventByIdForJob(job, id)
229
+ // }
230
+ //
231
+ // async function resolveEventByIdForJob(job: CreateJobParameters, id: string): Promise<SerializedEvent> {
232
+ // const apiKey = readApiKeyOrBail()
233
+ // const { candidates } = await fetchSampleEventCandidatesForJob(job, apiKey)
234
+ // const match = candidates.find(candidate => candidate.id === id)
235
+ // if (match) return hydrateCandidateEvent(match, apiKey)
236
+ //
237
+ // throw new CliError("sample_not_found", `No sample event matches id ${id}.`, {
238
+ // detail: "Run `terse test list` to refresh — the event may have rotated out of the sample buffer."
239
+ // })
240
+ // }
241
+ //
242
+ // function readEventFile(filePath: string): string {
243
+ // try {
244
+ // return fs.readFileSync(filePath, "utf-8")
245
+ // } catch (err) {
246
+ // throw new CliError("event_file_unreadable", `Could not read event file: ${filePath}`, {
247
+ // detail: err instanceof Error ? err.message : String(err)
248
+ // })
249
+ // }
250
+ // }
251
+ //
252
+ // async function fetchSampleEventCandidatesForJob(job: CreateJobParameters, apiKey: string): Promise<SampleEventCandidatesResult> {
253
+ // const timeTriggers = job.triggers.filter(t => t.integrationType === IntegrationType.CRON_JOB)
254
+ // const sampleEventTriggers = job.triggers.filter(t => t.integrationType !== IntegrationType.CRON_JOB)
255
+ //
256
+ // const candidates: SampleEventCandidate[] = []
257
+ // const webhookEndpoints: NonNullable<SdkSampleEventsResponse["webhookEndpoints"]> = []
258
+ //
259
+ // if (sampleEventTriggers.length > 0) {
260
+ // const projectId = readProjectConfig()?.projectId
261
+ // const result = await fetchWithAuth<SdkSampleEventsResponse>(
262
+ // ApiRoutes.SDK.SAMPLE_EVENTS,
263
+ // apiKey,
264
+ // {
265
+ // projectId,
266
+ // jobName: job.name,
267
+ // triggers: sampleEventTriggers.map(trigger => ({
268
+ // triggerId: undefined,
269
+ // integrationId: trigger.integrationId,
270
+ // integrationType: trigger.integrationType,
271
+ // config: trigger
272
+ // }))
273
+ // },
274
+ // "POST"
275
+ // )
276
+ // let storedIndex = 0
277
+ // for (const event of result.events) {
278
+ // if (event.entity) {
279
+ // candidates.push({
280
+ // id: encodeRefId(event.entity.entityType, event.entity.entityId),
281
+ // kind: "ref",
282
+ // integrationType: event.serializedEvent.integrationType,
283
+ // eventType: event.serializedEvent.eventType,
284
+ // label: normalizeSingleLine(event.serializedEvent.display?.title || `${event.serializedEvent.integrationType} / ${event.serializedEvent.eventType}`),
285
+ // subtitle: event.serializedEvent.display?.subtitle ?? null,
286
+ // entityType: event.entity.entityType,
287
+ // entityId: event.entity.entityId
288
+ // })
289
+ // } else {
290
+ // const time = event.recordedAt ? (DateTime.fromISO(event.recordedAt).toRelative() ?? null) : null
291
+ // const method = event.serializedEvent.display?.subtitle ?? null
292
+ // const labelParts = [event.serializedEvent.integrationType === IntegrationType.WEBHOOK ? "Webhook" : event.serializedEvent.integrationType, method, time].filter(Boolean) as string[]
293
+ // candidates.push({
294
+ // id: `stored:${storedIndex++}`,
295
+ // kind: "stored",
296
+ // integrationType: event.serializedEvent.integrationType,
297
+ // eventType: event.serializedEvent.eventType,
298
+ // label: labelParts.join(" · "),
299
+ // subtitle: event.serializedEvent.display?.subtitle ?? null,
300
+ // recordedAt: event.recordedAt ?? null,
301
+ // event: event.serializedEvent
302
+ // })
303
+ // }
304
+ // }
305
+ // if (result.webhookEndpoints) webhookEndpoints.push(...result.webhookEndpoints)
306
+ // }
307
+ //
308
+ // for (const [index, trigger] of timeTriggers.entries()) {
309
+ // const event = hydrateSerializedEvent({
310
+ // integrationType: IntegrationType.CRON_JOB,
311
+ // eventType: "cron",
312
+ // inputId: trigger.integrationId,
313
+ // isManualTrigger: true,
314
+ // manualContext: `Manual trigger from terse test (schedule: ${trigger.cronExpression})`
315
+ // })
316
+ // candidates.push({
317
+ // id: `synthetic:cron:${index}`,
318
+ // kind: "synthetic",
319
+ // integrationType: event.integrationType,
320
+ // eventType: event.eventType,
321
+ // label: formatEventLabel(event),
322
+ // subtitle: event.display?.subtitle ?? null,
323
+ // event
324
+ // })
325
+ // }
326
+ //
327
+ // return { candidates, webhookEndpoints }
328
+ // }
329
+ //
330
+ // function formatEventLabel(event: SerializedEvent): string {
331
+ // return normalizeSingleLine(event.display?.title || `${event.integrationType} / ${event.eventType}`)
332
+ // }
333
+ //
334
+ // function formatEventHint(candidate: SampleEventCandidate): string {
335
+ // if (candidate.kind === "stored") {
336
+ // const preview = previewPayload(candidate.event)
337
+ // if (preview) return truncate(preview, 120)
338
+ // if (candidate.subtitle) return truncate(normalizeSingleLine(candidate.subtitle), 120)
339
+ // return truncate(`${candidate.integrationType}/${candidate.eventType}`, 120)
340
+ // }
341
+ // if (candidate.subtitle) return truncate(normalizeSingleLine(candidate.subtitle), 120)
342
+ // if (candidate.kind === "ref") return truncate(`${candidate.integrationType}/${candidate.eventType}`, 120)
343
+ // return "Synthetic sample event"
344
+ // }
345
+ //
346
+ // function previewPayload(event: SerializedEvent): string | null {
347
+ // const data = event.data as Record<string, unknown> | undefined
348
+ // const body = data && "body" in data ? data.body : data
349
+ // if (body == null) return null
350
+ // try {
351
+ // return normalizeSingleLine(JSON.stringify(body))
352
+ // } catch {
353
+ // return null
354
+ // }
355
+ // }
356
+ //
357
+ // function truncate(value: string, maxLength: number): string {
358
+ // if (value.length <= maxLength) {
359
+ // return value
360
+ // }
361
+ //
362
+ // return `${value.slice(0, maxLength - 1)}…`
363
+ // }
364
+ //
365
+ // function normalizeSingleLine(value: string): string {
366
+ // return value
367
+ // .replace(/\s*\n+\s*/g, " ")
368
+ // .replace(/\s+/g, " ")
369
+ // .trim()
370
+ // }
371
+ //
372
+ // function abortIfCancelled<T>(value: T | symbol): T {
373
+ // if (isCancel(value)) {
374
+ // cancel("Operation cancelled.")
375
+ // process.exit(0)
376
+ // }
377
+ //
378
+ // return value as T
379
+ // }
380
+ //
381
+ // async function chooseSampleEvent(candidates: SampleEventCandidate[], apiKey: string): Promise<SerializedEvent> {
382
+ // while (true) {
383
+ // const choice = abortIfCancelled(
384
+ // await select<number>({
385
+ // message: "Choose a sample event",
386
+ // options: candidates.map((candidate, index) => ({
387
+ // label: `${candidate.label} ${chalk.dim("›")}`,
388
+ // hint: formatEventHint(candidate),
389
+ // value: index
390
+ // }))
391
+ // })
392
+ // )
393
+ //
394
+ // const event = await hydrateCandidateEvent(candidates[choice], apiKey)
395
+ // const action = await inspectSampleEvent(event)
396
+ // if (action === "run") {
397
+ // return event
398
+ // }
399
+ // }
400
+ // }
401
+ //
402
+ // async function hydrateCandidateEvent(candidate: SampleEventCandidate, apiKey: string): Promise<SerializedEvent> {
403
+ // if (candidate.kind === "synthetic" || candidate.kind === "stored") {
404
+ // return candidate.event
405
+ // }
406
+ // const payload = await fetchWithAuth<{ event: SerializedEvent }>(ApiRoutes.SDK.HYDRATE_SAMPLE_EVENT, apiKey, { entityType: candidate.entityType, entityId: candidate.entityId }, "POST")
407
+ // return payload.event
408
+ // }
409
+ //
410
+ // function encodeRefId(entityType: string, entityId: string): string {
411
+ // const token = Buffer.from(JSON.stringify({ entityType, entityId }), "utf8").toString("base64url")
412
+ // return `ref:${token}`
413
+ // }
414
+ //
415
+ // function emptyWebhookCurlHint(jobName: string, endpoints: NonNullable<SdkSampleEventsResponse["webhookEndpoints"]>): string {
416
+ // const lines = [`No past webhook events for ${chalk.cyan(jobName)} yet. Trigger one with:`, ""]
417
+ // for (const { webhookUrl } of endpoints) {
418
+ // lines.push(` curl -X POST ${chalk.cyan(webhookUrl)} \\`)
419
+ // lines.push(` -H 'Content-Type: application/json' \\`)
420
+ // lines.push(` -d '{ "your": "payload" }'`)
421
+ // lines.push("")
422
+ // }
423
+ // lines.push(chalk.dim("After the first event lands, `terse test` will pick it up automatically."))
424
+ // return lines.join("\n")
425
+ // }
426
+ //
427
+ // async function inspectSampleEvent(event: SerializedEvent): Promise<"back" | "run"> {
428
+ // log.info(chalk.bold(formatEventLabel(event)))
429
+ // if (event.display?.subtitle) {
430
+ // console.log(chalk.dim(event.display.subtitle))
431
+ // }
432
+ // console.log("")
433
+ // console.log(chalk.dim(truncate(normalizeSingleLine(event.formattedContent), 300)))
434
+ // console.log("")
435
+ //
436
+ // return abortIfCancelled(
437
+ // await select<"back" | "run">({
438
+ // message: "Run this event?",
439
+ // options: [
440
+ // { label: "Run this sample event", value: "run" },
441
+ // { label: "Choose another event", value: "back" }
442
+ // ]
443
+ // })
444
+ // )
445
+ // }
446
+ //
447
+ // // Types
448
+ //
449
+ // export type TestListOpts = {
450
+ // jobName?: string
451
+ // json?: boolean
452
+ // entryFile?: string
453
+ // provider?: LanguageProvider
454
+ // }
455
+ //
456
+ // export type TestShowOpts = {
457
+ // id: string
458
+ // jobName?: string
459
+ // json?: boolean
460
+ // entryFile?: string
461
+ // provider?: LanguageProvider
462
+ // }
463
+ //
464
+ // export type TestRunOpts = {
465
+ // jobName?: string
466
+ // id?: string
467
+ // eventJson?: string
468
+ // eventFile?: string
469
+ // verbose?: boolean
470
+ // entryFile?: string
471
+ // provider?: LanguageProvider
472
+ // freshState?: boolean
473
+ // }
474
+ //
475
+ // type SampleEventCandidate =
476
+ // | {
477
+ // id: string
478
+ // kind: "ref"
479
+ // integrationType: string
480
+ // eventType: string
481
+ // label: string
482
+ // subtitle: string | null
483
+ // entityType: string
484
+ // entityId: string
485
+ // }
486
+ // | {
487
+ // id: string
488
+ // kind: "synthetic"
489
+ // integrationType: string
490
+ // eventType: string
491
+ // label: string
492
+ // subtitle: string | null
493
+ // event: SerializedEvent
494
+ // }
495
+ // | {
496
+ // id: string
497
+ // kind: "stored"
498
+ // integrationType: string
499
+ // eventType: string
500
+ // label: string
501
+ // subtitle: string | null
502
+ // recordedAt: string | null
503
+ // event: SerializedEvent
504
+ // }
505
+ //
506
+ // type SampleEventCandidatesResult = {
507
+ // candidates: SampleEventCandidate[]
508
+ // webhookEndpoints: NonNullable<SdkSampleEventsResponse["webhookEndpoints"]>
509
+ // }
404
510
  //# sourceMappingURL=test.js.map