@kairos-sdk/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs ADDED
@@ -0,0 +1,1508 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/client.ts
27
+ var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
28
+
29
+ // src/utils/uuid.ts
30
+ function generateUUID() {
31
+ return crypto.randomUUID();
32
+ }
33
+
34
+ // src/library/null-library.ts
35
+ var NullLibrary = class {
36
+ async initialize() {
37
+ }
38
+ async search(_description, _options) {
39
+ return [];
40
+ }
41
+ async save(_workflow, _metadata) {
42
+ return generateUUID();
43
+ }
44
+ async recordDeployment(_id) {
45
+ }
46
+ async get(_id) {
47
+ return null;
48
+ }
49
+ async list(_filters) {
50
+ return [];
51
+ }
52
+ };
53
+
54
+ // src/errors/base.ts
55
+ var KairosError = class extends Error {
56
+ constructor(message, cause) {
57
+ super(message);
58
+ this.cause = cause;
59
+ this.name = "KairosError";
60
+ }
61
+ cause;
62
+ };
63
+
64
+ // src/errors/api-error.ts
65
+ var ApiError = class extends KairosError {
66
+ constructor(message, statusCode, cause) {
67
+ super(message, cause);
68
+ this.statusCode = statusCode;
69
+ this.name = "ApiError";
70
+ }
71
+ statusCode;
72
+ };
73
+
74
+ // src/errors/provider-error.ts
75
+ var ProviderError = class extends KairosError {
76
+ constructor(message, cause) {
77
+ super(message, cause);
78
+ this.name = "ProviderError";
79
+ }
80
+ };
81
+
82
+ // src/providers/n8n/api-client.ts
83
+ var EXECUTION_LIMIT_CAP = 100;
84
+ var N8nApiClient = class {
85
+ constructor(baseUrl, apiKey, logger) {
86
+ this.baseUrl = baseUrl;
87
+ this.apiKey = apiKey;
88
+ this.logger = logger;
89
+ }
90
+ baseUrl;
91
+ apiKey;
92
+ logger;
93
+ async request(method, path, body) {
94
+ const url = `${this.baseUrl.replace(/\/$/, "")}/api/v1${path}`;
95
+ this.logger.debug(`n8n ${method} ${path}`);
96
+ let response;
97
+ try {
98
+ response = await fetch(url, {
99
+ method,
100
+ headers: {
101
+ "X-N8N-API-KEY": this.apiKey,
102
+ "Content-Type": "application/json",
103
+ Accept: "application/json"
104
+ },
105
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
106
+ });
107
+ } catch (err) {
108
+ throw new ProviderError(`Network error calling n8n API: ${path}`, err);
109
+ }
110
+ if (!response.ok) {
111
+ let errorBody;
112
+ try {
113
+ errorBody = await response.json();
114
+ } catch {
115
+ errorBody = await response.text().catch(() => "");
116
+ }
117
+ this.logger.error(`n8n API error ${response.status} on ${method} ${path}`, {
118
+ status: response.status,
119
+ body: String(errorBody)
120
+ });
121
+ throw new ApiError(
122
+ `n8n API returned ${response.status} for ${method} ${path}: ${JSON.stringify(errorBody)}`,
123
+ response.status,
124
+ errorBody
125
+ );
126
+ }
127
+ if (response.status === 204) return void 0;
128
+ return response.json();
129
+ }
130
+ async createWorkflow(workflow) {
131
+ return this.request("POST", "/workflows", workflow);
132
+ }
133
+ async updateWorkflow(id, workflow) {
134
+ return this.request("PUT", `/workflows/${id}`, workflow);
135
+ }
136
+ async getWorkflow(id) {
137
+ return this.request("GET", `/workflows/${id}`);
138
+ }
139
+ async listWorkflows() {
140
+ const response = await this.request("GET", "/workflows?limit=250");
141
+ return response.data.map((w) => ({
142
+ id: w.id,
143
+ name: w.name,
144
+ active: w.active,
145
+ createdAt: w.createdAt,
146
+ updatedAt: w.updatedAt,
147
+ ...w.tags !== void 0 ? { tags: w.tags } : {}
148
+ }));
149
+ }
150
+ async deleteWorkflow(id) {
151
+ await this.request("DELETE", `/workflows/${id}`);
152
+ }
153
+ async activateWorkflow(id) {
154
+ await this.request("POST", `/workflows/${id}/activate`);
155
+ }
156
+ async deactivateWorkflow(id) {
157
+ await this.request("POST", `/workflows/${id}/deactivate`);
158
+ }
159
+ async getExecutions(workflowId, filter) {
160
+ const params = new URLSearchParams();
161
+ if (workflowId) params.set("workflowId", workflowId);
162
+ if (filter?.status) params.set("status", filter.status);
163
+ const limit = Math.min(filter?.limit ?? 20, EXECUTION_LIMIT_CAP);
164
+ params.set("limit", String(limit));
165
+ if (filter?.cursor) params.set("cursor", filter.cursor);
166
+ const qs = params.toString();
167
+ const response = await this.request("GET", `/executions${qs ? `?${qs}` : ""}`);
168
+ return response.data.map(this.mapExecution);
169
+ }
170
+ async getExecution(id) {
171
+ const response = await this.request("GET", `/executions/${id}`);
172
+ return { ...this.mapExecution(response), data: response.data, workflowData: response.workflowData };
173
+ }
174
+ async listTags() {
175
+ const response = await this.request("GET", "/tags");
176
+ return response.data.map((t) => ({ id: t.id, name: t.name }));
177
+ }
178
+ async createTag(name) {
179
+ const response = await this.request("POST", "/tags", { name });
180
+ return { id: response.id, name: response.name };
181
+ }
182
+ async tagWorkflow(workflowId, tagIds) {
183
+ await this.request("PUT", `/workflows/${workflowId}/tags`, tagIds.map((id) => ({ id })));
184
+ }
185
+ async untagWorkflow(workflowId, tagIds) {
186
+ const current = await this.getWorkflow(workflowId);
187
+ const remaining = (current.tags ?? []).filter((t) => !tagIds.includes(t.id)).map((t) => ({ id: t.id }));
188
+ await this.request("PUT", `/workflows/${workflowId}/tags`, remaining);
189
+ }
190
+ mapExecution(e) {
191
+ return {
192
+ id: e.id,
193
+ workflowId: e.workflowId,
194
+ status: e.status,
195
+ startedAt: e.startedAt,
196
+ ...e.stoppedAt !== void 0 ? { stoppedAt: e.stoppedAt } : {},
197
+ mode: e.mode
198
+ };
199
+ }
200
+ };
201
+
202
+ // src/providers/n8n/types.ts
203
+ var FORBIDDEN_ON_CREATE = [
204
+ "id",
205
+ "createdAt",
206
+ "updatedAt",
207
+ "versionId",
208
+ "meta",
209
+ "isArchived",
210
+ "activeVersionId",
211
+ "activeVersion",
212
+ "active",
213
+ "pinData",
214
+ "triggerCount",
215
+ "shared"
216
+ ];
217
+ var FORBIDDEN_ON_UPDATE = FORBIDDEN_ON_CREATE.filter((f) => f !== "id");
218
+
219
+ // src/providers/n8n/stripper.ts
220
+ var N8nFieldStripper = class {
221
+ stripForCreate(workflow) {
222
+ return this.strip(workflow, FORBIDDEN_ON_CREATE);
223
+ }
224
+ stripForUpdate(workflow) {
225
+ return this.strip(workflow, FORBIDDEN_ON_UPDATE);
226
+ }
227
+ strip(workflow, forbidden) {
228
+ const result = { ...workflow };
229
+ for (const field of forbidden) {
230
+ delete result[field];
231
+ }
232
+ return result;
233
+ }
234
+ };
235
+
236
+ // src/errors/guard-error.ts
237
+ var GuardError = class extends KairosError {
238
+ constructor(message) {
239
+ super(message);
240
+ this.name = "GuardError";
241
+ }
242
+ };
243
+
244
+ // src/providers/n8n/provider.ts
245
+ var N8nProvider = class {
246
+ constructor(client, stripper) {
247
+ this.client = client;
248
+ this.stripper = stripper;
249
+ }
250
+ client;
251
+ stripper;
252
+ platform = "n8n";
253
+ async deploy(workflow) {
254
+ const stripped = this.stripper.stripForCreate(workflow);
255
+ const response = await this.client.createWorkflow(stripped);
256
+ return { workflowId: response.id, name: response.name };
257
+ }
258
+ async update(id, workflow) {
259
+ const stripped = this.stripper.stripForUpdate(workflow);
260
+ const response = await this.client.updateWorkflow(id, stripped);
261
+ return { workflowId: response.id, name: response.name };
262
+ }
263
+ async get(id) {
264
+ const response = await this.client.getWorkflow(id);
265
+ return {
266
+ name: response.name,
267
+ nodes: response.nodes,
268
+ connections: response.connections,
269
+ ...response.settings !== void 0 ? { settings: response.settings } : {},
270
+ ...response.tags !== void 0 ? { tags: response.tags } : {}
271
+ };
272
+ }
273
+ async list() {
274
+ return this.client.listWorkflows();
275
+ }
276
+ async activate(id) {
277
+ await this.client.activateWorkflow(id);
278
+ }
279
+ async deactivate(id) {
280
+ await this.client.deactivateWorkflow(id);
281
+ }
282
+ async delete(id, options) {
283
+ if (options.confirm !== true) {
284
+ throw new GuardError("delete() requires { confirm: true } to prevent accidental deletion");
285
+ }
286
+ await this.client.deleteWorkflow(id);
287
+ }
288
+ async executions(workflowId, filter) {
289
+ return this.client.getExecutions(workflowId, filter);
290
+ }
291
+ async execution(id) {
292
+ return this.client.getExecution(id);
293
+ }
294
+ async listTags() {
295
+ return this.client.listTags();
296
+ }
297
+ async createTag(name) {
298
+ return this.client.createTag(name);
299
+ }
300
+ async tag(workflowId, tagIds) {
301
+ await this.client.tagWorkflow(workflowId, tagIds);
302
+ }
303
+ async untag(workflowId, tagIds) {
304
+ await this.client.untagWorkflow(workflowId, tagIds);
305
+ }
306
+ };
307
+
308
+ // src/validation/registry.ts
309
+ var DEFAULT_REGISTRY = [
310
+ // Trigger nodes
311
+ { type: "n8n-nodes-base.manualTrigger", safeTypeVersions: [1], requiredParams: [], isTrigger: true },
312
+ { type: "n8n-nodes-base.scheduleTrigger", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [], isTrigger: true },
313
+ { type: "n8n-nodes-base.webhook", safeTypeVersions: [1, 1.1, 2], requiredParams: ["httpMethod", "path"], isTrigger: true },
314
+ { type: "n8n-nodes-base.formTrigger", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [], isTrigger: true },
315
+ { type: "n8n-nodes-base.emailReadImap", safeTypeVersions: [2], requiredParams: [], credentialType: "imap", isTrigger: true },
316
+ { type: "n8n-nodes-base.errorTrigger", safeTypeVersions: [1], requiredParams: [], isTrigger: true },
317
+ { type: "n8n-nodes-base.executeWorkflowTrigger", safeTypeVersions: [1, 1.1], requiredParams: [], isTrigger: true },
318
+ { type: "n8n-nodes-base.gmailTrigger", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [], credentialType: "gmailOAuth2", isTrigger: true },
319
+ { type: "n8n-nodes-base.googleDriveTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "googleDriveOAuth2Api", isTrigger: true },
320
+ { type: "n8n-nodes-base.googleSheetsTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "googleSheetsTriggerOAuth2Api", isTrigger: true },
321
+ { type: "n8n-nodes-base.slackTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "slackApi", isTrigger: true },
322
+ { type: "n8n-nodes-base.telegramTrigger", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [], credentialType: "telegramApi", isTrigger: true },
323
+ { type: "n8n-nodes-base.githubTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "githubApi", isTrigger: true },
324
+ { type: "n8n-nodes-base.stripeTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "stripeApi", isTrigger: true },
325
+ { type: "n8n-nodes-base.airtableTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "airtableTokenApi", isTrigger: true },
326
+ { type: "n8n-nodes-base.notionTrigger", safeTypeVersions: [1], requiredParams: [], credentialType: "notionApi", isTrigger: true },
327
+ { type: "@n8n/n8n-nodes-langchain.chatTrigger", safeTypeVersions: [1, 1.1], requiredParams: [], isTrigger: true },
328
+ // Core logic nodes
329
+ { type: "n8n-nodes-base.code", safeTypeVersions: [1, 2], requiredParams: [] },
330
+ { type: "n8n-nodes-base.httpRequest", safeTypeVersions: [1, 2, 3, 4, 4.1, 4.2], requiredParams: ["url"] },
331
+ { type: "n8n-nodes-base.set", safeTypeVersions: [1, 2, 3, 3.1, 3.2, 3.3, 3.4], requiredParams: [] },
332
+ { type: "n8n-nodes-base.if", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [] },
333
+ { type: "n8n-nodes-base.switch", safeTypeVersions: [1, 2, 3, 3.1, 3.2], requiredParams: [] },
334
+ { type: "n8n-nodes-base.filter", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [] },
335
+ { type: "n8n-nodes-base.merge", safeTypeVersions: [1, 2, 2.1, 3], requiredParams: [] },
336
+ { type: "n8n-nodes-base.splitInBatches", safeTypeVersions: [1, 2, 3], requiredParams: [] },
337
+ { type: "n8n-nodes-base.wait", safeTypeVersions: [1, 1.1], requiredParams: [] },
338
+ { type: "n8n-nodes-base.executeWorkflow", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [] },
339
+ { type: "n8n-nodes-base.respondToWebhook", safeTypeVersions: [1, 1.1], requiredParams: [] },
340
+ { type: "n8n-nodes-base.noOp", safeTypeVersions: [1], requiredParams: [] },
341
+ { type: "n8n-nodes-base.stopAndError", safeTypeVersions: [1], requiredParams: [] },
342
+ { type: "n8n-nodes-base.splitOut", safeTypeVersions: [1], requiredParams: [] },
343
+ { type: "n8n-nodes-base.aggregate", safeTypeVersions: [1], requiredParams: [] },
344
+ { type: "n8n-nodes-base.stickyNote", safeTypeVersions: [1], requiredParams: [] },
345
+ // Email / messaging
346
+ { type: "n8n-nodes-base.emailSend", safeTypeVersions: [1, 2, 2.1], requiredParams: [], credentialType: "smtp" },
347
+ { type: "n8n-nodes-base.slack", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [], credentialType: "slackOAuth2Api" },
348
+ { type: "n8n-nodes-base.telegram", safeTypeVersions: [1, 1.1, 1.2], requiredParams: [], credentialType: "telegramApi" },
349
+ { type: "n8n-nodes-base.discord", safeTypeVersions: [1, 2], requiredParams: [], credentialType: "discordWebhookApi" },
350
+ // Google
351
+ { type: "n8n-nodes-base.gmail", safeTypeVersions: [1, 2, 2.1], requiredParams: [], credentialType: "gmailOAuth2" },
352
+ { type: "n8n-nodes-base.googleSheets", safeTypeVersions: [1, 2, 3, 4, 4.1, 4.2, 4.3, 4.4, 4.5], requiredParams: [], credentialType: "googleSheetsOAuth2Api" },
353
+ { type: "n8n-nodes-base.googleDrive", safeTypeVersions: [1, 2, 3], requiredParams: [], credentialType: "googleDriveOAuth2Api" },
354
+ { type: "n8n-nodes-base.googleCalendar", safeTypeVersions: [1, 1.1, 1.2, 1.3], requiredParams: [], credentialType: "googleCalendarOAuth2Api" },
355
+ // Project management / CRM
356
+ { type: "n8n-nodes-base.notion", safeTypeVersions: [1, 2, 2.1, 2.2], requiredParams: [], credentialType: "notionApi" },
357
+ { type: "n8n-nodes-base.airtable", safeTypeVersions: [1, 2, 2.1], requiredParams: [], credentialType: "airtableTokenApi" },
358
+ { type: "n8n-nodes-base.github", safeTypeVersions: [1, 1.1], requiredParams: [], credentialType: "githubApi" },
359
+ { type: "n8n-nodes-base.jira", safeTypeVersions: [1], requiredParams: [], credentialType: "jiraSoftwareCloudApi" },
360
+ { type: "n8n-nodes-base.hubspot", safeTypeVersions: [1, 2, 2.1], requiredParams: [], credentialType: "hubspotOAuth2Api" },
361
+ // Databases
362
+ { type: "n8n-nodes-base.postgres", safeTypeVersions: [1, 2, 2.1, 2.2, 2.3, 2.4, 2.5], requiredParams: [], credentialType: "postgres" },
363
+ { type: "n8n-nodes-base.mySql", safeTypeVersions: [1, 2, 2.1, 2.2, 2.3, 2.4], requiredParams: [], credentialType: "mySql" },
364
+ { type: "n8n-nodes-base.redis", safeTypeVersions: [1], requiredParams: [], credentialType: "redis" },
365
+ { type: "n8n-nodes-base.supabase", safeTypeVersions: [1], requiredParams: [], credentialType: "supabaseApi" },
366
+ // Cloud
367
+ { type: "n8n-nodes-base.awsS3", safeTypeVersions: [1, 2], requiredParams: [], credentialType: "aws" },
368
+ // Payment / commerce
369
+ { type: "n8n-nodes-base.stripe", safeTypeVersions: [1], requiredParams: [], credentialType: "stripeApi" },
370
+ // AI / LangChain root nodes
371
+ { type: "@n8n/n8n-nodes-langchain.agent", safeTypeVersions: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], requiredParams: [] },
372
+ { type: "@n8n/n8n-nodes-langchain.chainLlm", safeTypeVersions: [1, 1.1, 1.2, 1.3, 1.4, 1.5], requiredParams: [] },
373
+ { type: "@n8n/n8n-nodes-langchain.chainRetrievalQa", safeTypeVersions: [1, 1.1, 1.2, 1.3, 1.4], requiredParams: [] },
374
+ { type: "@n8n/n8n-nodes-langchain.informationExtractor", safeTypeVersions: [1], requiredParams: [] },
375
+ { type: "@n8n/n8n-nodes-langchain.textClassifier", safeTypeVersions: [1], requiredParams: [] },
376
+ // AI / LangChain sub-nodes (models)
377
+ { type: "@n8n/n8n-nodes-langchain.lmChatOpenAi", safeTypeVersions: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7], requiredParams: [], credentialType: "openAiApi" },
378
+ { type: "@n8n/n8n-nodes-langchain.lmChatAnthropic", safeTypeVersions: [1, 1.1, 1.2, 1.3], requiredParams: [], credentialType: "anthropicApi" },
379
+ { type: "@n8n/n8n-nodes-langchain.lmChatGoogleGemini", safeTypeVersions: [1], requiredParams: [], credentialType: "googlePalmApi" },
380
+ // AI / LangChain sub-nodes (memory, tools, etc.)
381
+ { type: "@n8n/n8n-nodes-langchain.memoryBufferWindow", safeTypeVersions: [1, 1.1, 1.2, 1.3], requiredParams: [] },
382
+ { type: "@n8n/n8n-nodes-langchain.toolWorkflow", safeTypeVersions: [1, 1.1, 1.2, 1.3], requiredParams: [] },
383
+ { type: "@n8n/n8n-nodes-langchain.toolCode", safeTypeVersions: [1, 1.1], requiredParams: [] },
384
+ { type: "@n8n/n8n-nodes-langchain.toolHttpRequest", safeTypeVersions: [1, 1.1], requiredParams: [] }
385
+ ];
386
+ var NodeRegistry = class {
387
+ byType;
388
+ constructor(definitions = DEFAULT_REGISTRY) {
389
+ this.byType = new Map(definitions.map((d) => [d.type, d]));
390
+ }
391
+ get(type) {
392
+ return this.byType.get(type);
393
+ }
394
+ isTrigger(type) {
395
+ return this.byType.get(type)?.isTrigger === true;
396
+ }
397
+ isVersionSafe(type, version) {
398
+ const def = this.byType.get(type);
399
+ if (!def) return true;
400
+ return def.safeTypeVersions.includes(version);
401
+ }
402
+ };
403
+
404
+ // src/validation/validator.ts
405
+ var AI_CONNECTION_TYPES = [
406
+ "ai_languageModel",
407
+ "ai_memory",
408
+ "ai_tool",
409
+ "ai_outputParser",
410
+ "ai_embedding",
411
+ "ai_document",
412
+ "ai_textSplitter",
413
+ "ai_retriever",
414
+ "ai_vectorStore"
415
+ ];
416
+ var TRIGGER_TYPE_PATTERNS = [/trigger/i, /Trigger$/];
417
+ var NODE_TYPE_PATTERN = /^(@[a-z0-9-]+\/[a-z0-9-]+\.|n8n-nodes-[a-z0-9-]+\.)[a-zA-Z][a-zA-Z0-9]+$/;
418
+ var N8nValidator = class {
419
+ registry;
420
+ constructor(registry = new NodeRegistry(DEFAULT_REGISTRY)) {
421
+ this.registry = registry;
422
+ }
423
+ validate(workflow) {
424
+ const issues = [];
425
+ this.checkRule1(workflow, issues);
426
+ this.checkRule2(workflow, issues);
427
+ this.checkRule3(workflow, issues);
428
+ this.checkRule4(workflow, issues);
429
+ this.checkRule5(workflow, issues);
430
+ this.checkRule6(workflow, issues);
431
+ this.checkRule7(workflow, issues);
432
+ this.checkRule8(workflow, issues);
433
+ this.checkRule9(workflow, issues);
434
+ this.checkRule10(workflow, issues);
435
+ this.checkRule11(workflow, issues);
436
+ this.checkRule12(workflow, issues);
437
+ this.checkRule13(workflow, issues);
438
+ this.checkRule14(workflow, issues);
439
+ this.checkRule15(workflow, issues);
440
+ this.checkRule16(workflow, issues);
441
+ this.checkRule17(workflow, issues);
442
+ this.checkRule18(workflow, issues);
443
+ this.checkRule19(workflow, issues);
444
+ const errors = issues.filter((i) => i.severity === "error");
445
+ return { valid: errors.length === 0, issues };
446
+ }
447
+ err(issues, rule, message, nodeId) {
448
+ const issue = { rule, severity: "error", message };
449
+ if (nodeId !== void 0) issue.nodeId = nodeId;
450
+ issues.push(issue);
451
+ }
452
+ warn(issues, rule, message, nodeId) {
453
+ const issue = { rule, severity: "warn", message };
454
+ if (nodeId !== void 0) issue.nodeId = nodeId;
455
+ issues.push(issue);
456
+ }
457
+ isTriggerNode(node) {
458
+ if (this.registry.isTrigger(node.type)) return true;
459
+ return TRIGGER_TYPE_PATTERNS.some((p) => p.test(node.type));
460
+ }
461
+ // Rule 1: name is a non-empty string
462
+ checkRule1(w, issues) {
463
+ if (typeof w.name !== "string" || w.name.trim() === "") {
464
+ this.err(issues, 1, "Workflow name is required and must be a non-empty string");
465
+ }
466
+ }
467
+ // Rule 2: nodes is an array with at least one element
468
+ checkRule2(w, issues) {
469
+ if (!Array.isArray(w.nodes) || w.nodes.length === 0) {
470
+ this.err(issues, 2, "Workflow must have at least one node");
471
+ }
472
+ }
473
+ // Rule 3: every node has a non-empty id
474
+ checkRule3(w, issues) {
475
+ if (!Array.isArray(w.nodes)) return;
476
+ for (const node of w.nodes) {
477
+ if (typeof node.id !== "string" || node.id.trim() === "") {
478
+ this.err(issues, 3, `Node "${node.name ?? "unknown"}" is missing a valid id`, node.id);
479
+ }
480
+ }
481
+ }
482
+ // Rule 4: node ids are unique
483
+ checkRule4(w, issues) {
484
+ if (!Array.isArray(w.nodes)) return;
485
+ const seen = /* @__PURE__ */ new Set();
486
+ for (const node of w.nodes) {
487
+ if (!node.id) continue;
488
+ if (seen.has(node.id)) {
489
+ this.err(issues, 4, `Duplicate node id: "${node.id}"`, node.id);
490
+ }
491
+ seen.add(node.id);
492
+ }
493
+ }
494
+ // Rule 5: every node has a non-empty type string
495
+ checkRule5(w, issues) {
496
+ if (!Array.isArray(w.nodes)) return;
497
+ for (const node of w.nodes) {
498
+ if (typeof node.type !== "string" || node.type.trim() === "") {
499
+ this.err(issues, 5, `Node "${node.name ?? node.id}" is missing a type`, node.id);
500
+ }
501
+ }
502
+ }
503
+ // Rule 6: every node has a positive typeVersion number
504
+ checkRule6(w, issues) {
505
+ if (!Array.isArray(w.nodes)) return;
506
+ for (const node of w.nodes) {
507
+ if (typeof node.typeVersion !== "number" || node.typeVersion <= 0) {
508
+ this.err(issues, 6, `Node "${node.name}" has invalid typeVersion: ${String(node.typeVersion)}`, node.id);
509
+ }
510
+ }
511
+ }
512
+ // Rule 7: every node has a valid [x, y] position
513
+ checkRule7(w, issues) {
514
+ if (!Array.isArray(w.nodes)) return;
515
+ for (const node of w.nodes) {
516
+ const pos = node.position;
517
+ if (!Array.isArray(pos) || pos.length !== 2 || typeof pos[0] !== "number" || typeof pos[1] !== "number") {
518
+ this.err(issues, 7, `Node "${node.name}" has invalid position (must be [x, y])`, node.id);
519
+ }
520
+ }
521
+ }
522
+ // Rule 8: every node has a non-empty name
523
+ checkRule8(w, issues) {
524
+ if (!Array.isArray(w.nodes)) return;
525
+ for (const node of w.nodes) {
526
+ if (typeof node.name !== "string" || node.name.trim() === "") {
527
+ this.err(issues, 8, `Node with id "${node.id}" is missing a name`, node.id);
528
+ }
529
+ }
530
+ }
531
+ // Rule 9: connections is a plain object
532
+ checkRule9(w, issues) {
533
+ if (typeof w.connections !== "object" || w.connections === null || Array.isArray(w.connections)) {
534
+ this.err(issues, 9, "connections must be a plain object (use {} for single-node workflows)");
535
+ }
536
+ }
537
+ // Rule 10: every connection target node name exists in nodes
538
+ checkRule10(w, issues) {
539
+ if (!Array.isArray(w.nodes) || typeof w.connections !== "object" || w.connections === null) return;
540
+ const nodeNames = new Set(w.nodes.map((n) => n.name));
541
+ for (const [sourceName, outputs] of Object.entries(w.connections)) {
542
+ if (!nodeNames.has(sourceName)) {
543
+ this.err(issues, 10, `Connection source "${sourceName}" does not exist in nodes`);
544
+ continue;
545
+ }
546
+ if (typeof outputs !== "object" || outputs === null) continue;
547
+ for (const portGroup of Object.values(outputs)) {
548
+ if (!Array.isArray(portGroup)) continue;
549
+ for (const targets of portGroup) {
550
+ if (!Array.isArray(targets)) continue;
551
+ for (const target of targets) {
552
+ const t = target;
553
+ if (typeof t?.node === "string" && !nodeNames.has(t.node)) {
554
+ this.err(issues, 10, `Connection target "${t.node}" does not exist in nodes`);
555
+ }
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+ // Rule 11 (WARN): every non-trigger node has at least one incoming connection
562
+ checkRule11(w, issues) {
563
+ if (!Array.isArray(w.nodes) || typeof w.connections !== "object" || w.connections === null) return;
564
+ const reachable = /* @__PURE__ */ new Set();
565
+ for (const [, outputs] of Object.entries(w.connections)) {
566
+ if (typeof outputs !== "object" || outputs === null) continue;
567
+ for (const portGroup of Object.values(outputs)) {
568
+ if (!Array.isArray(portGroup)) continue;
569
+ for (const targets of portGroup) {
570
+ if (!Array.isArray(targets)) continue;
571
+ for (const target of targets) {
572
+ const t = target;
573
+ if (typeof t?.node === "string") reachable.add(t.node);
574
+ }
575
+ }
576
+ }
577
+ }
578
+ for (const node of w.nodes) {
579
+ if (!this.isTriggerNode(node) && !reachable.has(node.name)) {
580
+ this.warn(issues, 11, `Node "${node.name}" has no incoming connections and may never execute`, node.id);
581
+ }
582
+ }
583
+ }
584
+ // Rule 12: forbidden fields absent from workflow root
585
+ checkRule12(w, issues) {
586
+ const wObj = w;
587
+ for (const field of FORBIDDEN_ON_CREATE) {
588
+ if (field in wObj) {
589
+ this.err(issues, 12, `Forbidden field "${field}" present in workflow \u2014 remove it before deploying`);
590
+ }
591
+ }
592
+ }
593
+ // Rule 13: settings, if present, is a plain object
594
+ checkRule13(w, issues) {
595
+ if (w.settings !== void 0) {
596
+ if (typeof w.settings !== "object" || w.settings === null || Array.isArray(w.settings)) {
597
+ this.err(issues, 13, "workflow.settings must be a plain object");
598
+ }
599
+ }
600
+ }
601
+ // Rule 14: at least one trigger node is present
602
+ checkRule14(w, issues) {
603
+ if (!Array.isArray(w.nodes)) return;
604
+ const hasTrigger = w.nodes.some((n) => this.isTriggerNode(n));
605
+ if (!hasTrigger) {
606
+ this.err(issues, 14, "Workflow must contain at least one trigger node");
607
+ }
608
+ }
609
+ // Rule 15: node type string matches expected format
610
+ checkRule15(w, issues) {
611
+ if (!Array.isArray(w.nodes)) return;
612
+ for (const node of w.nodes) {
613
+ if (typeof node.type !== "string") continue;
614
+ if (!NODE_TYPE_PATTERN.test(node.type)) {
615
+ this.err(issues, 15, `Node "${node.name}" has malformed type string: "${node.type}"`, node.id);
616
+ }
617
+ }
618
+ }
619
+ // Rule 16: node names are unique within the workflow
620
+ checkRule16(w, issues) {
621
+ if (!Array.isArray(w.nodes)) return;
622
+ const seen = /* @__PURE__ */ new Set();
623
+ for (const node of w.nodes) {
624
+ if (!node.name) continue;
625
+ if (seen.has(node.name)) {
626
+ this.err(issues, 16, `Duplicate node name: "${node.name}"`, node.id);
627
+ }
628
+ seen.add(node.name);
629
+ }
630
+ }
631
+ // Rule 17: credentials shape — each entry has id and name
632
+ checkRule17(w, issues) {
633
+ if (!Array.isArray(w.nodes)) return;
634
+ for (const node of w.nodes) {
635
+ if (!node.credentials) continue;
636
+ for (const [credType, credRef] of Object.entries(node.credentials)) {
637
+ if (typeof credRef !== "object" || credRef === null) {
638
+ this.err(issues, 17, `Node "${node.name}" credential "${credType}" must be an object with id and name`, node.id);
639
+ continue;
640
+ }
641
+ const ref = credRef;
642
+ if (typeof ref["id"] !== "string" || ref["id"].trim() === "" || typeof ref["name"] !== "string" || ref["name"].trim() === "") {
643
+ this.err(issues, 17, `Node "${node.name}" credential "${credType}" must have non-empty string id and name fields`, node.id);
644
+ }
645
+ }
646
+ }
647
+ }
648
+ // Rule 18 (WARN): AI connections originate from sub-nodes, not the agent/chain root
649
+ checkRule18(w, issues) {
650
+ if (typeof w.connections !== "object" || w.connections === null) return;
651
+ const agentTypes = /* @__PURE__ */ new Set([
652
+ "@n8n/n8n-nodes-langchain.agent",
653
+ "@n8n/n8n-nodes-langchain.chainLlm",
654
+ "@n8n/n8n-nodes-langchain.chainRetrievalQa",
655
+ "@n8n/n8n-nodes-langchain.chainSummarization"
656
+ ]);
657
+ if (!Array.isArray(w.nodes)) return;
658
+ const nodesByName = new Map(w.nodes.map((n) => [n.name, n]));
659
+ for (const [sourceName, outputs] of Object.entries(w.connections)) {
660
+ const sourceNode = nodesByName.get(sourceName);
661
+ if (!sourceNode) continue;
662
+ if (!agentTypes.has(sourceNode.type)) continue;
663
+ if (typeof outputs !== "object" || outputs === null) continue;
664
+ for (const connType of AI_CONNECTION_TYPES) {
665
+ if (connType in outputs) {
666
+ this.warn(
667
+ issues,
668
+ 18,
669
+ `Node "${sourceName}" uses AI connection type "${connType}" as a SOURCE \u2014 AI sub-nodes should be the source, not the agent/chain root`,
670
+ sourceNode.id
671
+ );
672
+ }
673
+ }
674
+ }
675
+ }
676
+ // Rule 19 (WARN): typeVersion is within known safe range for registered node types
677
+ checkRule19(w, issues) {
678
+ if (!Array.isArray(w.nodes)) return;
679
+ for (const node of w.nodes) {
680
+ if (typeof node.type !== "string" || typeof node.typeVersion !== "number") continue;
681
+ if (!this.registry.isVersionSafe(node.type, node.typeVersion)) {
682
+ this.warn(
683
+ issues,
684
+ 19,
685
+ `Node "${node.name}" uses typeVersion ${node.typeVersion} for type "${node.type}" which is not in the known safe list`,
686
+ node.id
687
+ );
688
+ }
689
+ }
690
+ }
691
+ };
692
+
693
+ // src/errors/generation-error.ts
694
+ var GenerationError = class extends KairosError {
695
+ constructor(message, cause) {
696
+ super(message, cause);
697
+ this.name = "GenerationError";
698
+ }
699
+ };
700
+
701
+ // src/errors/response-parse-error.ts
702
+ var ResponseParseError = class extends KairosError {
703
+ constructor(message, cause) {
704
+ super(message, cause);
705
+ this.name = "ResponseParseError";
706
+ }
707
+ };
708
+
709
+ // src/errors/validation-error.ts
710
+ var ValidationError = class extends KairosError {
711
+ constructor(message, issues) {
712
+ super(message);
713
+ this.issues = issues;
714
+ this.name = "ValidationError";
715
+ }
716
+ issues;
717
+ };
718
+
719
+ // src/generation/prompts/v1.ts
720
+ var SYSTEM_PROMPT_V1 = `You are a workflow generation engine for n8n. Your only output is a generate_workflow tool call containing valid n8n workflow JSON. You never respond with prose, explanations, or markdown. If you cannot fulfill the request, set the error field in the tool call.
721
+
722
+ ## HARD RULES \u2014 violating any of these causes immediate deployment failure
723
+
724
+ ### Forbidden fields \u2014 NEVER include these in the workflow object:
725
+ id, active, createdAt, updatedAt, versionId, meta, isArchived, activeVersionId, activeVersion, pinData, triggerCount, shared, staticData
726
+
727
+ ### Required top-level structure:
728
+ {
729
+ "name": "<descriptive name>",
730
+ "nodes": [...],
731
+ "connections": {...},
732
+ "settings": {
733
+ "saveExecutionProgress": true,
734
+ "saveManualExecutions": true,
735
+ "saveDataErrorExecution": "all",
736
+ "saveDataSuccessExecution": "all",
737
+ "executionTimeout": 3600,
738
+ "timezone": "America/New_York",
739
+ "executionOrder": "v1"
740
+ }
741
+ }
742
+
743
+ ### Node IDs:
744
+ - Every node.id must be a valid UUID v4 (random hex, format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
745
+ - Never reuse IDs, never use sequential fake IDs like "node-1"
746
+
747
+ ### Credentials:
748
+ - Only reference credentials with exact type names (see catalog below)
749
+ - If credential ID is unknown, OMIT the credentials block entirely \u2014 never invent credential IDs
750
+ - Never put API keys or tokens in parameters when a credential type exists
751
+
752
+ ### Node names:
753
+ - All node names must be unique within the workflow
754
+ - Use descriptive names: "Fetch Open Invoices" not "HTTP Request 2"
755
+
756
+ ### Positioning:
757
+ - Trigger node: [250, 300]
758
+ - Each subsequent step: x + 220 minimum
759
+ - Parallel branches: offset y by \xB1150
760
+ - AI sub-nodes: place below their root node (y + 200)
761
+
762
+ ---
763
+
764
+ ## CONNECTION RULES \u2014 the most common source of errors
765
+
766
+ ### Standard connections (main data flow):
767
+ "NodeA": { "main": [ [ { "node": "NodeB", "type": "main", "index": 0 } ] ] }
768
+
769
+ ### AI connections \u2014 CRITICAL: the SUB-NODE is the SOURCE, NOT the agent/chain:
770
+ "OpenAI Chat Model": { "ai_languageModel": [ [ { "node": "AI Agent", "type": "ai_languageModel", "index": 0 } ] ] }
771
+ "Simple Memory": { "ai_memory": [ [ { "node": "AI Agent", "type": "ai_memory", "index": 0 } ] ] }
772
+ "Calculator Tool": { "ai_tool": [ [ { "node": "AI Agent", "type": "ai_tool", "index": 0 } ] ] }
773
+
774
+ The AI Agent node does NOT appear in connections as a source for ai_* types.
775
+ Every AI Agent must have at least one ai_languageModel sub-node connected.
776
+
777
+ ### IF node \u2014 two output ports (0 = true, 1 = false):
778
+ "IF Check": { "main": [ [{ "node": "True Path", "type": "main", "index": 0 }], [{ "node": "False Path", "type": "main", "index": 0 }] ] }
779
+
780
+ ### Triggers have no incoming connections.
781
+ ### Connection keys are NODE NAMES, never node IDs.
782
+
783
+ ---
784
+
785
+ ## NODE CATALOG \u2014 exact type strings and safe typeVersions
786
+
787
+ ### Triggers (always at least one required):
788
+ n8n-nodes-base.manualTrigger typeVersion: 1 \u2014 testing only
789
+ n8n-nodes-base.scheduleTrigger typeVersion: 1.2 \u2014 params: rule.interval[{field, ...}]
790
+ n8n-nodes-base.webhook typeVersion: 2 \u2014 params: httpMethod, path, responseMode
791
+ n8n-nodes-base.formTrigger typeVersion: 2.2
792
+ n8n-nodes-base.emailReadImap typeVersion: 2 \u2014 cred: imap
793
+ n8n-nodes-base.errorTrigger typeVersion: 1
794
+ n8n-nodes-base.executeWorkflowTrigger typeVersion: 1.1
795
+ n8n-nodes-base.gmailTrigger typeVersion: 1.2 \u2014 cred: gmailOAuth2
796
+ n8n-nodes-base.slackTrigger typeVersion: 1 \u2014 cred: slackApi
797
+ n8n-nodes-base.telegramTrigger typeVersion: 1.2 \u2014 cred: telegramApi
798
+ n8n-nodes-base.githubTrigger typeVersion: 1 \u2014 cred: githubApi
799
+ n8n-nodes-base.airtableTrigger typeVersion: 1 \u2014 cred: airtableTokenApi
800
+ n8n-nodes-base.notionTrigger typeVersion: 1 \u2014 cred: notionApi
801
+ @n8n/n8n-nodes-langchain.chatTrigger typeVersion: 1.1 \u2014 pairs with AI Agent
802
+
803
+ ### Core logic:
804
+ n8n-nodes-base.code typeVersion: 2 \u2014 params: mode, jsCode
805
+ n8n-nodes-base.httpRequest typeVersion: 4.2 \u2014 params: method, url, [sendBody, jsonBody, sendHeaders, headerParameters]
806
+ n8n-nodes-base.set typeVersion: 3.4 \u2014 params: assignments.assignments[{id, name, value, type}]
807
+ n8n-nodes-base.if typeVersion: 2.2 \u2014 params: conditions.conditions[{id, leftValue, rightValue, operator}], combinator
808
+ n8n-nodes-base.switch typeVersion: 3.2 \u2014 multi-branch routing
809
+ n8n-nodes-base.filter typeVersion: 2.2 \u2014 params: conditions (same as IF), 1 output
810
+ n8n-nodes-base.merge typeVersion: 3 \u2014 modes: append/combine/chooseBranch
811
+ n8n-nodes-base.splitInBatches typeVersion: 3 \u2014 output 0=done, output 1=loop body
812
+ n8n-nodes-base.wait typeVersion: 1.1
813
+ n8n-nodes-base.executeWorkflow typeVersion: 1.2
814
+ n8n-nodes-base.respondToWebhook typeVersion: 1.1 \u2014 required when webhook responseMode is "responseNode"
815
+ n8n-nodes-base.noOp typeVersion: 1
816
+ n8n-nodes-base.splitOut typeVersion: 1
817
+ n8n-nodes-base.aggregate typeVersion: 1
818
+ n8n-nodes-base.stickyNote typeVersion: 1 \u2014 never connected, canvas annotation only
819
+
820
+ ### Email / messaging:
821
+ n8n-nodes-base.emailSend typeVersion: 2.1 \u2014 cred: smtp
822
+ n8n-nodes-base.slack typeVersion: 2.2 \u2014 cred: slackOAuth2Api \u2014 params: resource, operation, select, channelId{__rl}, text
823
+ n8n-nodes-base.telegram typeVersion: 1.2 \u2014 cred: telegramApi
824
+ n8n-nodes-base.discord typeVersion: 2 \u2014 cred: discordWebhookApi
825
+
826
+ ### Google:
827
+ n8n-nodes-base.gmail typeVersion: 2.1 \u2014 cred: gmailOAuth2 \u2014 params: resource, operation
828
+ n8n-nodes-base.googleSheets typeVersion: 4.5 \u2014 cred: googleSheetsOAuth2Api \u2014 params: resource, operation, documentId{__rl}, sheetName{__rl}
829
+ n8n-nodes-base.googleDrive typeVersion: 3 \u2014 cred: googleDriveOAuth2Api
830
+ n8n-nodes-base.googleCalendar typeVersion: 1.3 \u2014 cred: googleCalendarOAuth2Api
831
+
832
+ ### Productivity:
833
+ n8n-nodes-base.notion typeVersion: 2.2 \u2014 cred: notionApi
834
+ n8n-nodes-base.airtable typeVersion: 2.1 \u2014 cred: airtableTokenApi
835
+ n8n-nodes-base.github typeVersion: 1.1 \u2014 cred: githubApi
836
+ n8n-nodes-base.jira typeVersion: 1 \u2014 cred: jiraSoftwareCloudApi
837
+ n8n-nodes-base.hubspot typeVersion: 2.1 \u2014 cred: hubspotOAuth2Api
838
+
839
+ ### Databases:
840
+ n8n-nodes-base.postgres typeVersion: 2.5 \u2014 cred: postgres
841
+ n8n-nodes-base.mySql typeVersion: 2.4 \u2014 cred: mySql
842
+ n8n-nodes-base.redis typeVersion: 1 \u2014 cred: redis
843
+ n8n-nodes-base.supabase typeVersion: 1 \u2014 cred: supabaseApi
844
+ n8n-nodes-base.awsS3 typeVersion: 2 \u2014 cred: aws
845
+
846
+ ### AI \u2014 Root nodes (sit on main data flow):
847
+ @n8n/n8n-nodes-langchain.agent typeVersion: 1.9 \u2014 params: promptType, text (if define), options.systemMessage
848
+ @n8n/n8n-nodes-langchain.chainLlm typeVersion: 1.5
849
+ @n8n/n8n-nodes-langchain.chainRetrievalQa typeVersion: 1.4
850
+ @n8n/n8n-nodes-langchain.openAi typeVersion: 1.8 \u2014 cred: openAiApi
851
+ @n8n/n8n-nodes-langchain.anthropic typeVersion: 1 \u2014 cred: anthropicApi
852
+
853
+ ### AI \u2014 Sub-nodes (sources of ai_* connections):
854
+ @n8n/n8n-nodes-langchain.lmChatOpenAi typeVersion: 1.7 \u2014 cred: openAiApi \u2014 ai_languageModel
855
+ @n8n/n8n-nodes-langchain.lmChatAnthropic typeVersion: 1.3 \u2014 cred: anthropicApi \u2014 ai_languageModel
856
+ @n8n/n8n-nodes-langchain.lmChatGoogleGemini typeVersion: 1 \u2014 cred: googlePalmApi \u2014 ai_languageModel
857
+ @n8n/n8n-nodes-langchain.memoryBufferWindow typeVersion: 1.3 \u2014 \u2014 ai_memory
858
+ @n8n/n8n-nodes-langchain.toolWorkflow typeVersion: 2 \u2014 \u2014 ai_tool
859
+ @n8n/n8n-nodes-langchain.toolCode typeVersion: 1.1 \u2014 \u2014 ai_tool
860
+ @n8n/n8n-nodes-langchain.toolHttpRequest typeVersion: 1.1 \u2014 \u2014 ai_tool
861
+ @n8n/n8n-nodes-langchain.toolCalculator typeVersion: 1 \u2014 \u2014 ai_tool
862
+
863
+ ### Resource locator (__rl) format (Google / Slack / Notion modern nodes):
864
+ { "__rl": true, "mode": "id", "value": "ACTUAL_ID" }
865
+ { "__rl": true, "mode": "name", "value": "#channel-name" }
866
+
867
+ ### App node parameter pattern:
868
+ { "resource": "message", "operation": "send", ...operation-specific fields }
869
+
870
+ ### Schedule Trigger \u2014 daily at 9am example:
871
+ { "rule": { "interval": [{ "field": "days", "daysInterval": 1, "triggerAtHour": 9, "triggerAtMinute": 0 }] } }
872
+ Cron: { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 9 * * 1-5" }] } }
873
+
874
+ ---
875
+
876
+ ## PRE-DELIVERY SELF-CHECK (do this before calling the tool):
877
+ 1. Every connection source/target name exists in nodes array
878
+ 2. No duplicate node names
879
+ 3. No duplicate node IDs
880
+ 4. No forbidden fields at the workflow root
881
+ 5. At least one trigger node present
882
+ 6. Every AI Agent has an ai_languageModel sub-node
883
+ 7. settings block is complete with executionOrder: "v1"
884
+
885
+ ---
886
+
887
+ Respond ONLY with a generate_workflow tool call. No prose. No markdown outside the tool call.
888
+ If the request is impossible or unclear, set the error field instead of generating a workflow.`;
889
+
890
+ // src/generation/prompt-builder.ts
891
+ var PromptBuilder = class {
892
+ build(request, matches) {
893
+ const mode = this.resolveMode(matches);
894
+ const system = this.buildSystem(matches, mode);
895
+ const userMessage = this.buildUserMessage(request, matches, mode);
896
+ return { system, userMessage, mode, matches };
897
+ }
898
+ buildCorrectionMessage(request, matches, allIssues, attempt) {
899
+ const base = this.buildUserMessage(request, matches, this.resolveMode(matches));
900
+ return `${base}
901
+
902
+ IMPORTANT: A previous generation attempt (attempt ${attempt}) failed validation with these issues:
903
+ ${allIssues.join("\n")}
904
+
905
+ Fix ALL of the above issues in your new response. Do not repeat any of these mistakes.`;
906
+ }
907
+ resolveMode(matches) {
908
+ if (matches.length === 0) return "scratch";
909
+ const top = matches[0];
910
+ if (!top) return "scratch";
911
+ if (top.score >= 0.92) return "direct";
912
+ if (top.score >= 0.72) return "reference";
913
+ return "scratch";
914
+ }
915
+ buildSystem(matches, mode) {
916
+ const blocks = [
917
+ {
918
+ type: "text",
919
+ text: SYSTEM_PROMPT_V1,
920
+ cache_control: { type: "ephemeral" }
921
+ }
922
+ ];
923
+ if (mode === "reference" && matches.length > 0) {
924
+ const refText = matches.slice(0, 3).map((m) => {
925
+ const nodes = m.workflow.workflow.nodes.map((n) => ` - ${n.name} (${n.type} v${n.typeVersion})`).join("\n");
926
+ return `Reference workflow: "${m.workflow.description}" (similarity: ${m.score.toFixed(2)})
927
+ Nodes:
928
+ ${nodes}`;
929
+ }).join("\n\n");
930
+ blocks.push({
931
+ type: "text",
932
+ text: `## Similar Workflows From Library (for reference only \u2014 adapt, do not copy verbatim)
933
+
934
+ ${refText}`
935
+ });
936
+ }
937
+ if (mode === "direct" && matches[0]) {
938
+ const match = matches[0];
939
+ blocks.push({
940
+ type: "text",
941
+ text: `## Closely Matched Workflow (score: ${match.score.toFixed(2)}) \u2014 adapt this structure:
942
+
943
+ ${JSON.stringify(match.workflow.workflow, null, 2)}`
944
+ });
945
+ }
946
+ return blocks;
947
+ }
948
+ buildUserMessage(request, _matches, _mode) {
949
+ const namePart = request.name ? `
950
+ Workflow name: "${request.name}"` : "";
951
+ return `Build a workflow that: ${request.description}${namePart}`;
952
+ }
953
+ };
954
+
955
+ // src/generation/designer.ts
956
+ var MAX_ATTEMPTS = 3;
957
+ var BASE_TEMPERATURE = 0.2;
958
+ var FINAL_TEMPERATURE = 0.1;
959
+ var GENERATE_WORKFLOW_TOOL = {
960
+ name: "generate_workflow",
961
+ description: "Generate a valid n8n workflow JSON object",
962
+ input_schema: {
963
+ type: "object",
964
+ properties: {
965
+ workflow: {
966
+ type: "object",
967
+ description: "The complete n8n workflow object",
968
+ properties: {
969
+ name: { type: "string" },
970
+ nodes: { type: "array" },
971
+ connections: { type: "object" },
972
+ settings: { type: "object" }
973
+ },
974
+ required: ["name", "nodes", "connections"]
975
+ },
976
+ credentialsNeeded: {
977
+ type: "array",
978
+ description: "List of credentials the user must configure before activating",
979
+ items: {
980
+ type: "object",
981
+ properties: {
982
+ service: { type: "string" },
983
+ credentialType: { type: "string" },
984
+ description: { type: "string" }
985
+ },
986
+ required: ["service", "credentialType", "description"]
987
+ }
988
+ },
989
+ error: {
990
+ type: "string",
991
+ description: "Set this if the request cannot be fulfilled \u2014 explain why"
992
+ }
993
+ },
994
+ required: ["workflow"]
995
+ }
996
+ };
997
+ var WorkflowDesigner = class {
998
+ constructor(anthropic, model, logger) {
999
+ this.anthropic = anthropic;
1000
+ this.model = model;
1001
+ this.logger = logger;
1002
+ this.validator = new N8nValidator();
1003
+ this.promptBuilder = new PromptBuilder();
1004
+ }
1005
+ anthropic;
1006
+ model;
1007
+ logger;
1008
+ validator;
1009
+ promptBuilder;
1010
+ async design(request, matches) {
1011
+ const allIssues = [];
1012
+ const attemptMetadata = [];
1013
+ let attempts = 0;
1014
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
1015
+ attempts = attempt;
1016
+ const temperature = attempt === MAX_ATTEMPTS ? FINAL_TEMPERATURE : BASE_TEMPERATURE;
1017
+ const built = this.promptBuilder.build(request, matches);
1018
+ let userMessage;
1019
+ if (attempt === 1) {
1020
+ userMessage = built.userMessage;
1021
+ this.logger.debug("WorkflowDesigner: attempt 1", { description: request.description });
1022
+ } else {
1023
+ const issueLines = allIssues.map(
1024
+ (i) => `- [Rule ${i.rule}] ${i.message}${i.nodeId ? ` (node: ${i.nodeId})` : ""}`
1025
+ );
1026
+ userMessage = this.promptBuilder.buildCorrectionMessage(request, matches, issueLines, attempt - 1);
1027
+ this.logger.debug(`WorkflowDesigner: correction attempt ${attempt}`, { issueCount: allIssues.length });
1028
+ }
1029
+ const start = Date.now();
1030
+ const message = await this.callClaude(built.system, userMessage, temperature);
1031
+ const durationMs = Date.now() - start;
1032
+ const parsed = this.extractToolUse(message);
1033
+ if (parsed.error) {
1034
+ throw new GenerationError(`Claude declined to generate workflow: ${parsed.error}`);
1035
+ }
1036
+ const validation = this.validator.validate(parsed.workflow);
1037
+ const errors = validation.issues.filter((i) => i.severity === "error");
1038
+ attemptMetadata.push({
1039
+ attempt,
1040
+ temperature,
1041
+ durationMs,
1042
+ tokensInput: message.usage.input_tokens,
1043
+ tokensOutput: message.usage.output_tokens,
1044
+ validationPassed: validation.valid,
1045
+ issues: errors
1046
+ });
1047
+ if (validation.valid) {
1048
+ return { workflow: parsed.workflow, credentialsNeeded: parsed.credentialsNeeded, attempts, attemptMetadata };
1049
+ }
1050
+ allIssues.push(...errors);
1051
+ this.logger.warn(`WorkflowDesigner: validation failed on attempt ${attempt}`, {
1052
+ newErrors: errors.length,
1053
+ totalErrors: allIssues.length
1054
+ });
1055
+ }
1056
+ throw new ValidationError(
1057
+ `Workflow failed validation after ${MAX_ATTEMPTS} attempts (${allIssues.length} total errors)`,
1058
+ allIssues
1059
+ );
1060
+ }
1061
+ async callClaude(system, userMessage, temperature) {
1062
+ try {
1063
+ return await this.anthropic.messages.create({
1064
+ model: this.model,
1065
+ max_tokens: 8192,
1066
+ temperature,
1067
+ system,
1068
+ messages: [{ role: "user", content: userMessage }],
1069
+ tools: [GENERATE_WORKFLOW_TOOL],
1070
+ tool_choice: { type: "tool", name: "generate_workflow" }
1071
+ });
1072
+ } catch (err) {
1073
+ throw new GenerationError("Anthropic API call failed", err);
1074
+ }
1075
+ }
1076
+ extractToolUse(message) {
1077
+ const toolUseBlock = message.content.find(
1078
+ (block) => block.type === "tool_use"
1079
+ );
1080
+ if (!toolUseBlock) {
1081
+ throw new ResponseParseError(
1082
+ "Claude response contained no tool_use block \u2014 forced tool_choice failed unexpectedly"
1083
+ );
1084
+ }
1085
+ const input = toolUseBlock.input;
1086
+ if (typeof input["error"] === "string") {
1087
+ return {
1088
+ workflow: { name: "", nodes: [], connections: {} },
1089
+ credentialsNeeded: [],
1090
+ error: input["error"]
1091
+ };
1092
+ }
1093
+ if (!input["workflow"] || typeof input["workflow"] !== "object") {
1094
+ throw new ResponseParseError("generate_workflow tool call missing workflow field");
1095
+ }
1096
+ const workflow = input["workflow"];
1097
+ const credentialsNeeded = input["credentialsNeeded"] ?? [];
1098
+ return { workflow, credentialsNeeded };
1099
+ }
1100
+ };
1101
+
1102
+ // src/telemetry/collector.ts
1103
+ var import_promises = require("fs/promises");
1104
+ var import_node_path = require("path");
1105
+ var import_node_os = require("os");
1106
+ var TelemetryCollector = class {
1107
+ dir;
1108
+ sessionId;
1109
+ constructor(dir) {
1110
+ this.dir = dir ?? (0, import_node_path.join)((0, import_node_os.homedir)(), ".kairos", "telemetry");
1111
+ this.sessionId = generateUUID();
1112
+ }
1113
+ async emit(eventType, data) {
1114
+ const event = {
1115
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1116
+ sessionId: this.sessionId,
1117
+ eventType,
1118
+ data
1119
+ };
1120
+ await (0, import_promises.mkdir)(this.dir, { recursive: true });
1121
+ const filename = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) + ".jsonl";
1122
+ const filepath = (0, import_node_path.join)(this.dir, filename);
1123
+ await (0, import_promises.appendFile)(filepath, JSON.stringify(event) + "\n", "utf-8");
1124
+ }
1125
+ };
1126
+
1127
+ // src/utils/logger.ts
1128
+ var nullLogger = {
1129
+ debug() {
1130
+ },
1131
+ info() {
1132
+ },
1133
+ warn() {
1134
+ },
1135
+ error() {
1136
+ }
1137
+ };
1138
+
1139
+ // src/client.ts
1140
+ var DEFAULT_MODEL = "claude-sonnet-4-6";
1141
+ var Kairos = class {
1142
+ provider;
1143
+ designer;
1144
+ validator;
1145
+ library;
1146
+ logger;
1147
+ telemetry;
1148
+ model;
1149
+ constructor(options) {
1150
+ const logger = options.logger ?? nullLogger;
1151
+ this.model = options.model ?? DEFAULT_MODEL;
1152
+ const anthropic = new import_sdk.default({ apiKey: options.anthropicApiKey });
1153
+ const apiClient = new N8nApiClient(options.n8nBaseUrl, options.n8nApiKey, logger);
1154
+ const stripper = new N8nFieldStripper();
1155
+ this.provider = new N8nProvider(apiClient, stripper);
1156
+ this.designer = new WorkflowDesigner(anthropic, this.model, logger);
1157
+ this.validator = new N8nValidator();
1158
+ this.library = options.library ?? new NullLibrary();
1159
+ this.logger = logger;
1160
+ if (options.telemetry === true) {
1161
+ this.telemetry = new TelemetryCollector();
1162
+ } else if (typeof options.telemetry === "string") {
1163
+ this.telemetry = new TelemetryCollector(options.telemetry);
1164
+ } else {
1165
+ this.telemetry = null;
1166
+ }
1167
+ }
1168
+ async build(description, options) {
1169
+ this.logger.info("Kairos.build", { description, dryRun: options?.dryRun });
1170
+ const buildStart = Date.now();
1171
+ await this.telemetry?.emit("build_start", {
1172
+ description,
1173
+ model: this.model,
1174
+ dryRun: options?.dryRun ?? false
1175
+ });
1176
+ await this.library.initialize();
1177
+ const matches = await this.library.search(description);
1178
+ const designResult = await this.designer.design(
1179
+ { description, ...options?.name ? { name: options.name } : {} },
1180
+ matches
1181
+ );
1182
+ for (const meta of designResult.attemptMetadata) {
1183
+ await this.telemetry?.emit("generation_attempt", {
1184
+ description,
1185
+ attempt: meta.attempt,
1186
+ temperature: meta.temperature,
1187
+ durationMs: meta.durationMs,
1188
+ tokensInput: meta.tokensInput,
1189
+ tokensOutput: meta.tokensOutput,
1190
+ validationPassed: meta.validationPassed,
1191
+ issueCount: meta.issues.length,
1192
+ issues: meta.issues.map((i) => ({ rule: i.rule, message: i.message }))
1193
+ });
1194
+ }
1195
+ const workflow = options?.name ? { ...designResult.workflow, name: options.name } : designResult.workflow;
1196
+ if (options?.dryRun) {
1197
+ const result2 = {
1198
+ workflowId: null,
1199
+ name: workflow.name,
1200
+ credentialsNeeded: designResult.credentialsNeeded,
1201
+ activationRequired: true,
1202
+ generationAttempts: designResult.attempts,
1203
+ dryRun: true
1204
+ };
1205
+ const totalTokensInput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
1206
+ const totalTokensOutput2 = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
1207
+ await this.telemetry?.emit("build_complete", {
1208
+ description,
1209
+ success: true,
1210
+ totalAttempts: designResult.attempts,
1211
+ totalDurationMs: Date.now() - buildStart,
1212
+ totalTokensInput: totalTokensInput2,
1213
+ totalTokensOutput: totalTokensOutput2,
1214
+ workflowName: workflow.name,
1215
+ workflowId: null,
1216
+ dryRun: true,
1217
+ credentialsNeeded: designResult.credentialsNeeded.length
1218
+ });
1219
+ return result2;
1220
+ }
1221
+ const deployed = await this.provider.deploy(workflow);
1222
+ this.library.save(workflow, { description }).catch((err) => {
1223
+ this.logger.warn("Failed to save workflow to library (non-fatal)", { err: String(err) });
1224
+ });
1225
+ if (options?.activate) {
1226
+ await this.provider.activate(deployed.workflowId);
1227
+ }
1228
+ const result = {
1229
+ workflowId: deployed.workflowId,
1230
+ name: deployed.name,
1231
+ credentialsNeeded: designResult.credentialsNeeded,
1232
+ activationRequired: !options?.activate,
1233
+ generationAttempts: designResult.attempts,
1234
+ dryRun: false
1235
+ };
1236
+ const totalTokensInput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensInput, 0);
1237
+ const totalTokensOutput = designResult.attemptMetadata.reduce((s, m) => s + m.tokensOutput, 0);
1238
+ await this.telemetry?.emit("build_complete", {
1239
+ description,
1240
+ success: true,
1241
+ totalAttempts: designResult.attempts,
1242
+ totalDurationMs: Date.now() - buildStart,
1243
+ totalTokensInput,
1244
+ totalTokensOutput,
1245
+ workflowName: deployed.name,
1246
+ workflowId: deployed.workflowId,
1247
+ dryRun: false,
1248
+ credentialsNeeded: designResult.credentialsNeeded.length
1249
+ });
1250
+ return result;
1251
+ }
1252
+ async update(id, description) {
1253
+ this.logger.info("Kairos.update", { id, description });
1254
+ const matches = await this.library.search(description);
1255
+ const designResult = await this.designer.design({ description }, matches);
1256
+ const deployed = await this.provider.update(id, designResult.workflow);
1257
+ return {
1258
+ workflowId: deployed.workflowId,
1259
+ name: deployed.name,
1260
+ credentialsNeeded: designResult.credentialsNeeded,
1261
+ activationRequired: true,
1262
+ generationAttempts: designResult.attempts,
1263
+ dryRun: false
1264
+ };
1265
+ }
1266
+ async get(id) {
1267
+ return this.provider.get(id);
1268
+ }
1269
+ async list() {
1270
+ return this.provider.list();
1271
+ }
1272
+ async activate(id) {
1273
+ await this.provider.activate(id);
1274
+ }
1275
+ async deactivate(id) {
1276
+ await this.provider.deactivate(id);
1277
+ }
1278
+ async delete(id, options) {
1279
+ await this.provider.delete(id, options);
1280
+ }
1281
+ async executions(workflowId, filter) {
1282
+ return this.provider.executions(workflowId, filter);
1283
+ }
1284
+ async execution(id) {
1285
+ return this.provider.execution(id);
1286
+ }
1287
+ async listTags() {
1288
+ return this.provider.listTags();
1289
+ }
1290
+ async createTag(name) {
1291
+ return this.provider.createTag(name);
1292
+ }
1293
+ async tag(workflowId, tagIds) {
1294
+ await this.provider.tag(workflowId, tagIds);
1295
+ }
1296
+ async untag(workflowId, tagIds) {
1297
+ await this.provider.untag(workflowId, tagIds);
1298
+ }
1299
+ };
1300
+
1301
+ // src/cli.ts
1302
+ var HELP = `
1303
+ Kairos SDK \u2014 LLM-powered n8n workflow generation
1304
+
1305
+ Usage:
1306
+ kairos build <description> [options]
1307
+ kairos list
1308
+ kairos get <id>
1309
+ kairos activate <id>
1310
+ kairos deactivate <id>
1311
+ kairos delete <id> --confirm
1312
+
1313
+ Build options:
1314
+ --dry-run Generate and validate without deploying
1315
+ --name <name> Override the generated workflow name
1316
+ --activate Activate the workflow after deployment
1317
+
1318
+ Environment variables:
1319
+ ANTHROPIC_API_KEY Anthropic API key (required)
1320
+ N8N_BASE_URL n8n instance URL (required)
1321
+ N8N_API_KEY n8n API key (required)
1322
+ KAIROS_MODEL Claude model override (default: claude-sonnet-4-6)
1323
+ KAIROS_TELEMETRY Set to "true" or a directory path to enable telemetry logging
1324
+ `;
1325
+ function getEnvOrExit(name) {
1326
+ const val = process.env[name];
1327
+ if (!val) {
1328
+ console.error(`Missing required environment variable: ${name}`);
1329
+ process.exit(1);
1330
+ }
1331
+ return val;
1332
+ }
1333
+ function parseArgs(argv) {
1334
+ const args = argv.slice(2);
1335
+ const command = args[0] ?? "";
1336
+ const positional = [];
1337
+ const flags = {};
1338
+ for (let i = 1; i < args.length; i++) {
1339
+ const arg = args[i];
1340
+ if (arg.startsWith("--")) {
1341
+ const key = arg.slice(2);
1342
+ const next = args[i + 1];
1343
+ if (next && !next.startsWith("--")) {
1344
+ flags[key] = next;
1345
+ i++;
1346
+ } else {
1347
+ flags[key] = true;
1348
+ }
1349
+ } else {
1350
+ positional.push(arg);
1351
+ }
1352
+ }
1353
+ return { command, positional, flags };
1354
+ }
1355
+ function createClient() {
1356
+ const telemetryEnv = process.env["KAIROS_TELEMETRY"];
1357
+ let telemetry;
1358
+ if (telemetryEnv === "true") {
1359
+ telemetry = true;
1360
+ } else if (telemetryEnv && telemetryEnv !== "false") {
1361
+ telemetry = telemetryEnv;
1362
+ }
1363
+ return new Kairos({
1364
+ anthropicApiKey: getEnvOrExit("ANTHROPIC_API_KEY"),
1365
+ n8nBaseUrl: getEnvOrExit("N8N_BASE_URL"),
1366
+ n8nApiKey: getEnvOrExit("N8N_API_KEY"),
1367
+ ...process.env["KAIROS_MODEL"] ? { model: process.env["KAIROS_MODEL"] } : {},
1368
+ ...telemetry !== void 0 ? { telemetry } : {},
1369
+ logger: {
1370
+ debug: () => {
1371
+ },
1372
+ info: () => {
1373
+ },
1374
+ warn: (msg) => console.error(`[warn] ${msg}`),
1375
+ error: (msg) => console.error(`[error] ${msg}`)
1376
+ }
1377
+ });
1378
+ }
1379
+ async function handleBuild(positional, flags) {
1380
+ const description = positional[0];
1381
+ if (!description) {
1382
+ console.error("Usage: kairos build <description> [--dry-run] [--name <name>] [--activate]");
1383
+ process.exit(1);
1384
+ }
1385
+ const kairos = createClient();
1386
+ const start = Date.now();
1387
+ console.error(`Generating workflow...`);
1388
+ const result = await kairos.build(description, {
1389
+ dryRun: flags["dry-run"] === true,
1390
+ ...typeof flags["name"] === "string" ? { name: flags["name"] } : {},
1391
+ activate: flags["activate"] === true
1392
+ });
1393
+ const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
1394
+ console.error(`Done in ${elapsed}s (${result.generationAttempts} attempt${result.generationAttempts > 1 ? "s" : ""})`);
1395
+ console.error("");
1396
+ console.log(JSON.stringify({
1397
+ workflowId: result.workflowId,
1398
+ name: result.name,
1399
+ generationAttempts: result.generationAttempts,
1400
+ activationRequired: result.activationRequired,
1401
+ dryRun: result.dryRun,
1402
+ credentialsNeeded: result.credentialsNeeded
1403
+ }, null, 2));
1404
+ }
1405
+ async function handleList() {
1406
+ const kairos = createClient();
1407
+ const workflows = await kairos.list();
1408
+ if (workflows.length === 0) {
1409
+ console.log("No workflows found.");
1410
+ return;
1411
+ }
1412
+ for (const w of workflows) {
1413
+ const status = w.active ? "active" : "inactive";
1414
+ console.log(` ${w.id} ${status.padEnd(8)} ${w.name}`);
1415
+ }
1416
+ console.log(`
1417
+ ${workflows.length} workflow(s)`);
1418
+ }
1419
+ async function handleGet(positional) {
1420
+ const id = positional[0];
1421
+ if (!id) {
1422
+ console.error("Usage: kairos get <workflow-id>");
1423
+ process.exit(1);
1424
+ }
1425
+ const kairos = createClient();
1426
+ const workflow = await kairos.get(id);
1427
+ console.log(JSON.stringify(workflow, null, 2));
1428
+ }
1429
+ async function handleActivate(positional) {
1430
+ const id = positional[0];
1431
+ if (!id) {
1432
+ console.error("Usage: kairos activate <workflow-id>");
1433
+ process.exit(1);
1434
+ }
1435
+ const kairos = createClient();
1436
+ await kairos.activate(id);
1437
+ console.log(`Activated workflow ${id}`);
1438
+ }
1439
+ async function handleDeactivate(positional) {
1440
+ const id = positional[0];
1441
+ if (!id) {
1442
+ console.error("Usage: kairos deactivate <workflow-id>");
1443
+ process.exit(1);
1444
+ }
1445
+ const kairos = createClient();
1446
+ await kairos.deactivate(id);
1447
+ console.log(`Deactivated workflow ${id}`);
1448
+ }
1449
+ async function handleDelete(positional, flags) {
1450
+ const id = positional[0];
1451
+ if (!id) {
1452
+ console.error("Usage: kairos delete <workflow-id> --confirm");
1453
+ process.exit(1);
1454
+ }
1455
+ if (flags["confirm"] !== true) {
1456
+ console.error("Refusing to delete without --confirm flag.");
1457
+ process.exit(1);
1458
+ }
1459
+ const kairos = createClient();
1460
+ await kairos.delete(id, { confirm: true });
1461
+ console.log(`Deleted workflow ${id}`);
1462
+ }
1463
+ async function main() {
1464
+ const { command, positional, flags } = parseArgs(process.argv);
1465
+ if (!command || command === "help" || flags["help"] === true) {
1466
+ console.log(HELP);
1467
+ return;
1468
+ }
1469
+ switch (command) {
1470
+ case "build":
1471
+ await handleBuild(positional, flags);
1472
+ break;
1473
+ case "list":
1474
+ await handleList();
1475
+ break;
1476
+ case "get":
1477
+ await handleGet(positional);
1478
+ break;
1479
+ case "activate":
1480
+ await handleActivate(positional);
1481
+ break;
1482
+ case "deactivate":
1483
+ await handleDeactivate(positional);
1484
+ break;
1485
+ case "delete":
1486
+ await handleDelete(positional, flags);
1487
+ break;
1488
+ default:
1489
+ console.error(`Unknown command: ${command}`);
1490
+ console.log(HELP);
1491
+ process.exit(1);
1492
+ }
1493
+ }
1494
+ main().catch((err) => {
1495
+ if (err instanceof Error) {
1496
+ console.error(`Error: ${err.message}`);
1497
+ if ("issues" in err) {
1498
+ const issues = err.issues;
1499
+ for (const issue of issues) {
1500
+ console.error(` [Rule ${issue.rule}] ${issue.message}`);
1501
+ }
1502
+ }
1503
+ } else {
1504
+ console.error(err);
1505
+ }
1506
+ process.exit(1);
1507
+ });
1508
+ //# sourceMappingURL=cli.cjs.map