@kairos-sdk/core 0.5.1 → 0.6.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/README.md +121 -15
- package/dist/{chunk-VPPWTMRJ.js → chunk-2ZHNO37N.js} +48 -4
- package/dist/chunk-2ZHNO37N.js.map +1 -0
- package/dist/chunk-GG4B4TYG.js +153 -0
- package/dist/chunk-GG4B4TYG.js.map +1 -0
- package/dist/{chunk-MYAGTDQ2.js → chunk-PCNW5ZUD.js} +2 -2
- package/dist/chunk-SC6CLQZB.js +144 -0
- package/dist/chunk-SC6CLQZB.js.map +1 -0
- package/dist/chunk-SQS4QHDH.js +44 -0
- package/dist/chunk-SQS4QHDH.js.map +1 -0
- package/dist/{chunk-V2IZBZGB.js → chunk-STG7Z2SS.js} +2 -2
- package/dist/{chunk-GVZKMS53.js → chunk-YOQTEVDB.js} +4 -4
- package/dist/cli.cjs +647 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +239 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +385 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -3
- package/dist/index.d.ts +86 -3
- package/dist/index.js +19 -5
- package/dist/mcp-server.cjs +47 -3
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/mcp-server.js +2 -2
- package/dist/pack-builder-RTQWXGIS.js +9 -0
- package/dist/pack-builder-RTQWXGIS.js.map +1 -0
- package/dist/pack-exporter-KFNLSP5V.js +7 -0
- package/dist/pack-exporter-KFNLSP5V.js.map +1 -0
- package/dist/pack-validator-HZPB2XJ3.js +7 -0
- package/dist/pack-validator-HZPB2XJ3.js.map +1 -0
- package/dist/{reader-B5mV20H6.d.ts → reader-CfWGpL4V.d.cts} +2 -1
- package/dist/{reader-B5mV20H6.d.cts → reader-CfWGpL4V.d.ts} +2 -1
- package/dist/standalone.cjs +43 -3
- package/dist/standalone.cjs.map +1 -1
- package/dist/standalone.d.cts +1 -1
- package/dist/standalone.d.ts +1 -1
- package/dist/standalone.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-VPPWTMRJ.js.map +0 -1
- /package/dist/{chunk-MYAGTDQ2.js.map → chunk-PCNW5ZUD.js.map} +0 -0
- /package/dist/{chunk-V2IZBZGB.js.map → chunk-STG7Z2SS.js.map} +0 -0
- /package/dist/{chunk-GVZKMS53.js.map → chunk-YOQTEVDB.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,6 +30,364 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
33
|
+
// src/pack/pack-builder.ts
|
|
34
|
+
var pack_builder_exports = {};
|
|
35
|
+
__export(pack_builder_exports, {
|
|
36
|
+
PackBuilder: () => PackBuilder,
|
|
37
|
+
derivePackStatus: () => derivePackStatus
|
|
38
|
+
});
|
|
39
|
+
function derivePackStatus(pack) {
|
|
40
|
+
const hasBlocking = pack.assumptions.some((a) => a.type === "blocking");
|
|
41
|
+
const hasFailures = pack.workflows.some((w) => w.error);
|
|
42
|
+
const allDeployed = pack.workflows.length > 0 && pack.workflows.every((w) => w.deployed);
|
|
43
|
+
const hasNeedsConfirmation = pack.assumptions.some((a) => a.type === "needs_confirmation");
|
|
44
|
+
if (pack.status === "active" && !hasBlocking && !hasFailures && allDeployed) return "active";
|
|
45
|
+
if (pack.workflows.length === 0 || !allDeployed && !hasFailures) return "draft";
|
|
46
|
+
if (hasBlocking) return "blocked";
|
|
47
|
+
if (hasFailures) return "needs_attention";
|
|
48
|
+
if (hasNeedsConfirmation) return "ready_for_test";
|
|
49
|
+
return "ready_for_activation";
|
|
50
|
+
}
|
|
51
|
+
function normalizeAssumptions(raw) {
|
|
52
|
+
const validTypes = /* @__PURE__ */ new Set(["safe", "needs_confirmation", "blocking"]);
|
|
53
|
+
return raw.map((a) => {
|
|
54
|
+
if (typeof a === "string") {
|
|
55
|
+
return { type: "needs_confirmation", text: a };
|
|
56
|
+
}
|
|
57
|
+
if (typeof a === "object" && a !== null) {
|
|
58
|
+
const obj = a;
|
|
59
|
+
const type = typeof obj["type"] === "string" && validTypes.has(obj["type"]) ? obj["type"] : "needs_confirmation";
|
|
60
|
+
const text = typeof obj["text"] === "string" ? obj["text"] : JSON.stringify(obj);
|
|
61
|
+
return { type, text };
|
|
62
|
+
}
|
|
63
|
+
return { type: "needs_confirmation", text: String(a) };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
var import_sdk2, PLAN_PROMPT, PackBuilder;
|
|
67
|
+
var init_pack_builder = __esm({
|
|
68
|
+
"src/pack/pack-builder.ts"() {
|
|
69
|
+
"use strict";
|
|
70
|
+
import_sdk2 = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
71
|
+
PLAN_PROMPT = `You are planning an n8n workflow automation pack for a business.
|
|
72
|
+
|
|
73
|
+
Business context: {CONTEXT}
|
|
74
|
+
|
|
75
|
+
Generate a list of 4-8 n8n workflows that would meaningfully automate this business's operations. Focus on workflows that save time on repetitive tasks, improve customer communication, prevent things falling through the cracks, and are realistic to implement with n8n nodes.
|
|
76
|
+
|
|
77
|
+
For each workflow, write a detailed build description (2-4 sentences) suitable for passing directly to an n8n workflow generator. Be specific: name the trigger type, data sources (Google Sheets columns if applicable), actions, and outputs.
|
|
78
|
+
|
|
79
|
+
For assumptions, classify each one:
|
|
80
|
+
- "safe": a clearly reasonable default the business likely expects (e.g. "Schedule runs Monday 9 AM")
|
|
81
|
+
- "needs_confirmation": should be confirmed before going live but won't break things immediately (e.g. "Assumed professional email tone \u2014 confirm brand voice")
|
|
82
|
+
- "blocking": MUST be resolved before activation or the workflow will fail, send duplicates, or surprise customers (e.g. "Google Sheet ID not provided", "emails auto-send without approval gate \u2014 add confirmation step")
|
|
83
|
+
|
|
84
|
+
Treat any open question that would block safe deployment as a blocking assumption.
|
|
85
|
+
|
|
86
|
+
Return ONLY valid JSON with no markdown or extra text:
|
|
87
|
+
{
|
|
88
|
+
"workflows": [
|
|
89
|
+
{
|
|
90
|
+
"name": "Short descriptive name",
|
|
91
|
+
"description": "Detailed generator-ready description specifying trigger, data sources, actions, outputs",
|
|
92
|
+
"purpose": "One sentence explaining the business value"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"assumptions": [
|
|
96
|
+
{ "type": "safe" | "needs_confirmation" | "blocking", "text": "Description of the assumption" }
|
|
97
|
+
],
|
|
98
|
+
"sheetsColumns": [
|
|
99
|
+
{ "sheet": "Sheet name", "columns": ["col1", "col2"] }
|
|
100
|
+
],
|
|
101
|
+
"testChecklist": [
|
|
102
|
+
{ "workflow": "Workflow name", "steps": ["How to manually test this workflow"] }
|
|
103
|
+
]
|
|
104
|
+
}`;
|
|
105
|
+
PackBuilder = class {
|
|
106
|
+
client;
|
|
107
|
+
kairos;
|
|
108
|
+
model;
|
|
109
|
+
constructor(options) {
|
|
110
|
+
this.client = new import_sdk2.default({ apiKey: options.anthropicApiKey });
|
|
111
|
+
this.kairos = options.kairos;
|
|
112
|
+
this.model = options.model ?? "claude-sonnet-4-6";
|
|
113
|
+
}
|
|
114
|
+
async plan(businessContext) {
|
|
115
|
+
const prompt = PLAN_PROMPT.replace("{CONTEXT}", businessContext);
|
|
116
|
+
const response = await this.client.messages.create({
|
|
117
|
+
model: this.model,
|
|
118
|
+
max_tokens: 4096,
|
|
119
|
+
messages: [{ role: "user", content: prompt }]
|
|
120
|
+
});
|
|
121
|
+
const text = response.content[0]?.type === "text" ? response.content[0].text.trim() : "";
|
|
122
|
+
const cleaned = text.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
123
|
+
const parsed = JSON.parse(cleaned);
|
|
124
|
+
const rawAssumptions = Array.isArray(parsed["assumptions"]) ? parsed["assumptions"] : [];
|
|
125
|
+
const rawOpenQuestions = Array.isArray(parsed["openQuestions"]) ? parsed["openQuestions"] : [];
|
|
126
|
+
const assumptions = normalizeAssumptions([...rawAssumptions, ...rawOpenQuestions.map(
|
|
127
|
+
(q) => typeof q === "string" ? { type: "needs_confirmation", text: q } : q
|
|
128
|
+
)]);
|
|
129
|
+
return {
|
|
130
|
+
businessContext,
|
|
131
|
+
workflows: Array.isArray(parsed["workflows"]) ? parsed["workflows"] : [],
|
|
132
|
+
assumptions,
|
|
133
|
+
sheetsColumns: Array.isArray(parsed["sheetsColumns"]) ? parsed["sheetsColumns"] : [],
|
|
134
|
+
testChecklist: Array.isArray(parsed["testChecklist"]) ? parsed["testChecklist"] : []
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async build(plan, options = {}) {
|
|
138
|
+
const hasBlockingAssumptions = plan.assumptions.some((a) => a.type === "blocking");
|
|
139
|
+
const effectiveActivate = hasBlockingAssumptions ? false : options.activate ?? false;
|
|
140
|
+
const results = [];
|
|
141
|
+
const credentialMap = /* @__PURE__ */ new Map();
|
|
142
|
+
for (let i = 0; i < plan.workflows.length; i++) {
|
|
143
|
+
const wf = plan.workflows[i];
|
|
144
|
+
options.onProgress?.(wf, i, plan.workflows.length);
|
|
145
|
+
try {
|
|
146
|
+
const result = await this.kairos.build(wf.description, {
|
|
147
|
+
name: wf.name,
|
|
148
|
+
dryRun: options.dryRun ?? false,
|
|
149
|
+
activate: effectiveActivate
|
|
150
|
+
});
|
|
151
|
+
for (const cred of result.credentialsNeeded) {
|
|
152
|
+
credentialMap.set(cred.service, { service: cred.service, credentialType: cred.credentialType });
|
|
153
|
+
}
|
|
154
|
+
results.push({
|
|
155
|
+
name: wf.name,
|
|
156
|
+
purpose: wf.purpose,
|
|
157
|
+
workflowId: result.workflowId,
|
|
158
|
+
deployed: !result.dryRun,
|
|
159
|
+
generationAttempts: result.generationAttempts,
|
|
160
|
+
credentialsNeeded: result.credentialsNeeded
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
results.push({
|
|
164
|
+
name: wf.name,
|
|
165
|
+
purpose: wf.purpose,
|
|
166
|
+
workflowId: null,
|
|
167
|
+
deployed: false,
|
|
168
|
+
generationAttempts: 0,
|
|
169
|
+
credentialsNeeded: [],
|
|
170
|
+
error: err instanceof Error ? err.message : String(err)
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const packName = plan.businessContext.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
175
|
+
const partial = {
|
|
176
|
+
businessContext: plan.businessContext,
|
|
177
|
+
packName,
|
|
178
|
+
status: "draft",
|
|
179
|
+
workflows: results,
|
|
180
|
+
allCredentials: Array.from(credentialMap.values()),
|
|
181
|
+
sheetsColumns: plan.sheetsColumns,
|
|
182
|
+
assumptions: plan.assumptions,
|
|
183
|
+
testChecklist: plan.testChecklist,
|
|
184
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
185
|
+
};
|
|
186
|
+
return { ...partial, status: derivePackStatus(partial) };
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// src/pack/pack-exporter.ts
|
|
193
|
+
var pack_exporter_exports = {};
|
|
194
|
+
__export(pack_exporter_exports, {
|
|
195
|
+
generateHandoff: () => generateHandoff
|
|
196
|
+
});
|
|
197
|
+
function generateHandoff(pack) {
|
|
198
|
+
const lines = [];
|
|
199
|
+
const line = () => lines.push("");
|
|
200
|
+
lines.push(`# ${pack.businessContext} \u2014 Workflow Pack`);
|
|
201
|
+
line();
|
|
202
|
+
lines.push(`**Status:** ${STATUS_LABELS[pack.status] ?? pack.status}`);
|
|
203
|
+
lines.push(`**Generated:** ${new Date(pack.builtAt).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}`);
|
|
204
|
+
lines.push(`**Workflows:** ${pack.workflows.length} (${pack.workflows.filter((w) => w.deployed).length} deployed)`);
|
|
205
|
+
line();
|
|
206
|
+
lines.push(`## Overview`);
|
|
207
|
+
line();
|
|
208
|
+
lines.push(
|
|
209
|
+
`This workflow pack automates operations for **${pack.businessContext}**. It was built with Kairos and requires credential setup before workflows can be activated in n8n.`
|
|
210
|
+
);
|
|
211
|
+
line();
|
|
212
|
+
const blocking = pack.assumptions.filter((a) => a.type === "blocking");
|
|
213
|
+
if (blocking.length > 0) {
|
|
214
|
+
lines.push(`## Blocking Issues`);
|
|
215
|
+
line();
|
|
216
|
+
lines.push(`> These must be resolved before any workflows are activated.`);
|
|
217
|
+
line();
|
|
218
|
+
for (const a of blocking) {
|
|
219
|
+
lines.push(`- [ ] ${a.text}`);
|
|
220
|
+
}
|
|
221
|
+
line();
|
|
222
|
+
}
|
|
223
|
+
lines.push(`## Workflows`);
|
|
224
|
+
line();
|
|
225
|
+
for (const wf of pack.workflows) {
|
|
226
|
+
const icon = wf.error ? "\u2717" : "\u2713";
|
|
227
|
+
lines.push(`### ${icon} ${wf.name}`);
|
|
228
|
+
line();
|
|
229
|
+
lines.push(`**Purpose:** ${wf.purpose}`);
|
|
230
|
+
if (wf.workflowId) lines.push(`**n8n ID:** \`${wf.workflowId}\``);
|
|
231
|
+
if (!wf.deployed && !wf.error) lines.push(`**Status:** Not deployed (dry run)`);
|
|
232
|
+
if (wf.error) lines.push(`**Error:** ${wf.error}`);
|
|
233
|
+
line();
|
|
234
|
+
}
|
|
235
|
+
if (pack.allCredentials.length > 0) {
|
|
236
|
+
lines.push(`## Required Credentials`);
|
|
237
|
+
line();
|
|
238
|
+
lines.push(`Connect these in n8n before activating workflows:`);
|
|
239
|
+
line();
|
|
240
|
+
for (const cred of pack.allCredentials) {
|
|
241
|
+
lines.push(`- [ ] **${cred.service}** (${cred.credentialType})`);
|
|
242
|
+
}
|
|
243
|
+
line();
|
|
244
|
+
}
|
|
245
|
+
if (pack.sheetsColumns.length > 0) {
|
|
246
|
+
lines.push(`## Required Google Sheets`);
|
|
247
|
+
line();
|
|
248
|
+
for (const sheet of pack.sheetsColumns) {
|
|
249
|
+
lines.push(`### ${sheet.sheet}`);
|
|
250
|
+
line();
|
|
251
|
+
lines.push(`Columns: ${sheet.columns.map((c) => `\`${c}\``).join(", ")}`);
|
|
252
|
+
line();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const needsConfirmation = pack.assumptions.filter((a) => a.type === "needs_confirmation");
|
|
256
|
+
if (needsConfirmation.length > 0) {
|
|
257
|
+
lines.push(`## Needs Confirmation`);
|
|
258
|
+
line();
|
|
259
|
+
lines.push(`Verify these with the client before going live:`);
|
|
260
|
+
line();
|
|
261
|
+
for (const a of needsConfirmation) {
|
|
262
|
+
lines.push(`- [ ] ${a.text}`);
|
|
263
|
+
}
|
|
264
|
+
line();
|
|
265
|
+
}
|
|
266
|
+
const safe = pack.assumptions.filter((a) => a.type === "safe");
|
|
267
|
+
if (safe.length > 0) {
|
|
268
|
+
lines.push(`## Safe Assumptions`);
|
|
269
|
+
line();
|
|
270
|
+
lines.push(`These defaults were used during generation \u2014 no action needed:`);
|
|
271
|
+
line();
|
|
272
|
+
for (const a of safe) {
|
|
273
|
+
lines.push(`- ${a.text}`);
|
|
274
|
+
}
|
|
275
|
+
line();
|
|
276
|
+
}
|
|
277
|
+
lines.push(`## Setup Checklist`);
|
|
278
|
+
line();
|
|
279
|
+
lines.push(`Complete before testing:`);
|
|
280
|
+
line();
|
|
281
|
+
for (const cred of pack.allCredentials) {
|
|
282
|
+
lines.push(`- [ ] Connect **${cred.service}** credential in n8n Settings \u2192 Credentials`);
|
|
283
|
+
}
|
|
284
|
+
for (const sheet of pack.sheetsColumns) {
|
|
285
|
+
lines.push(`- [ ] Create Google Sheet: "${sheet.sheet}" with columns: ${sheet.columns.join(", ")}`);
|
|
286
|
+
}
|
|
287
|
+
for (const a of blocking) {
|
|
288
|
+
lines.push(`- [ ] Resolve: ${a.text}`);
|
|
289
|
+
}
|
|
290
|
+
for (const a of needsConfirmation) {
|
|
291
|
+
lines.push(`- [ ] Confirm: ${a.text}`);
|
|
292
|
+
}
|
|
293
|
+
line();
|
|
294
|
+
if (pack.testChecklist.length > 0) {
|
|
295
|
+
lines.push(`## Testing Checklist`);
|
|
296
|
+
line();
|
|
297
|
+
for (const item of pack.testChecklist) {
|
|
298
|
+
lines.push(`### ${item.workflow}`);
|
|
299
|
+
line();
|
|
300
|
+
for (const step of item.steps) {
|
|
301
|
+
lines.push(`- [ ] ${step}`);
|
|
302
|
+
}
|
|
303
|
+
line();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const deployedWorkflows = pack.workflows.filter((w) => w.deployed && !w.error);
|
|
307
|
+
if (deployedWorkflows.length > 0) {
|
|
308
|
+
lines.push(`## Activation Checklist`);
|
|
309
|
+
line();
|
|
310
|
+
lines.push(`Activate in n8n after testing is complete:`);
|
|
311
|
+
line();
|
|
312
|
+
for (const wf of deployedWorkflows) {
|
|
313
|
+
const idSuffix = wf.workflowId ? ` (n8n ID: \`${wf.workflowId}\`)` : "";
|
|
314
|
+
lines.push(`- [ ] Activate: **${wf.name}**${idSuffix}`);
|
|
315
|
+
}
|
|
316
|
+
line();
|
|
317
|
+
}
|
|
318
|
+
lines.push(`## Maintenance Notes`);
|
|
319
|
+
line();
|
|
320
|
+
lines.push(`- Monitor n8n executions weekly \u2014 check the Executions tab for failures`);
|
|
321
|
+
lines.push(`- Re-run \`kairos build-pack\` to regenerate workflows if business needs change`);
|
|
322
|
+
lines.push(`- Update Google Sheets data as business information changes`);
|
|
323
|
+
lines.push(`- Rotate API credentials before expiration (n8n Settings \u2192 Credentials)`);
|
|
324
|
+
lines.push(`- Run \`kairos validate-pack <name>\` before activating after any changes`);
|
|
325
|
+
return lines.join("\n");
|
|
326
|
+
}
|
|
327
|
+
var STATUS_LABELS;
|
|
328
|
+
var init_pack_exporter = __esm({
|
|
329
|
+
"src/pack/pack-exporter.ts"() {
|
|
330
|
+
"use strict";
|
|
331
|
+
STATUS_LABELS = {
|
|
332
|
+
draft: "Draft",
|
|
333
|
+
blocked: "Blocked \u2014 resolve issues before activation",
|
|
334
|
+
ready_for_test: "Ready for Testing",
|
|
335
|
+
ready_for_activation: "Ready for Activation",
|
|
336
|
+
active: "Active",
|
|
337
|
+
needs_attention: "Needs Attention"
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// src/pack/pack-validator.ts
|
|
343
|
+
var pack_validator_exports = {};
|
|
344
|
+
__export(pack_validator_exports, {
|
|
345
|
+
validatePack: () => validatePack
|
|
346
|
+
});
|
|
347
|
+
function validatePack(pack) {
|
|
348
|
+
const issues = [];
|
|
349
|
+
const names = pack.workflows.map((w) => w.name);
|
|
350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
351
|
+
const duplicates = /* @__PURE__ */ new Set();
|
|
352
|
+
for (const name of names) {
|
|
353
|
+
if (seen.has(name)) duplicates.add(name);
|
|
354
|
+
seen.add(name);
|
|
355
|
+
}
|
|
356
|
+
if (duplicates.size > 0) {
|
|
357
|
+
issues.push({
|
|
358
|
+
type: "duplicate_name",
|
|
359
|
+
severity: "error",
|
|
360
|
+
message: `Duplicate workflow names: ${[...duplicates].join(", ")} \u2014 n8n may overwrite existing workflows on deploy`,
|
|
361
|
+
workflows: [...duplicates]
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
const blocking = pack.assumptions.filter((a) => a.type === "blocking");
|
|
365
|
+
if (blocking.length > 0) {
|
|
366
|
+
const plural = blocking.length === 1 ? "assumption" : "assumptions";
|
|
367
|
+
issues.push({
|
|
368
|
+
type: "blocking_assumption",
|
|
369
|
+
severity: "error",
|
|
370
|
+
message: `${blocking.length} blocking ${plural} must be resolved before activation:
|
|
371
|
+
${blocking.map((a) => `\u2022 ${a.text}`).join("\n ")}`
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
const failed = pack.workflows.filter((w) => w.error);
|
|
375
|
+
for (const wf of failed) {
|
|
376
|
+
issues.push({
|
|
377
|
+
type: "unsafe_activation",
|
|
378
|
+
severity: "error",
|
|
379
|
+
message: `Workflow "${wf.name}" failed to deploy: ${wf.error ?? "unknown error"}`,
|
|
380
|
+
workflows: [wf.name]
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
return issues;
|
|
384
|
+
}
|
|
385
|
+
var init_pack_validator = __esm({
|
|
386
|
+
"src/pack/pack-validator.ts"() {
|
|
387
|
+
"use strict";
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
26
391
|
// src/client.ts
|
|
27
392
|
var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
28
393
|
|
|
@@ -700,6 +1065,7 @@ var N8nValidator = class {
|
|
|
700
1065
|
this.checkRule32(workflow, issues);
|
|
701
1066
|
this.checkRule33(workflow, issues);
|
|
702
1067
|
this.checkRule34(workflow, issues);
|
|
1068
|
+
this.checkRule35(workflow, issues);
|
|
703
1069
|
if (Array.isArray(workflow.nodes)) {
|
|
704
1070
|
const nodeById = new Map(workflow.nodes.map((n) => [n.id, n.type]));
|
|
705
1071
|
for (const issue of issues) {
|
|
@@ -1272,6 +1638,43 @@ var N8nValidator = class {
|
|
|
1272
1638
|
}
|
|
1273
1639
|
}
|
|
1274
1640
|
}
|
|
1641
|
+
// Rule 35 (WARN): email-sending node with no duplicate-prevention signal
|
|
1642
|
+
checkRule35(w, issues) {
|
|
1643
|
+
if (!Array.isArray(w.nodes)) return;
|
|
1644
|
+
const sendNodes = w.nodes.filter((node) => {
|
|
1645
|
+
if (node.type === "n8n-nodes-base.gmail") {
|
|
1646
|
+
const op = node.parameters?.["operation"];
|
|
1647
|
+
return !op || op === "send" || op === "sendEmail" || op === "reply";
|
|
1648
|
+
}
|
|
1649
|
+
return node.type === "n8n-nodes-base.emailSend" || node.type === "n8n-nodes-base.sendEmail";
|
|
1650
|
+
});
|
|
1651
|
+
if (sendNodes.length === 0) return;
|
|
1652
|
+
const workflowText = JSON.stringify(w).toLowerCase();
|
|
1653
|
+
const IDEMPOTENCY_SIGNALS = [
|
|
1654
|
+
"sent_at",
|
|
1655
|
+
"last_sent",
|
|
1656
|
+
"last_reminder",
|
|
1657
|
+
"processed_at",
|
|
1658
|
+
"already_sent",
|
|
1659
|
+
"email_sent",
|
|
1660
|
+
"notified_at",
|
|
1661
|
+
"reminder_sent",
|
|
1662
|
+
"contacted_at",
|
|
1663
|
+
"dedupe",
|
|
1664
|
+
"idempotent"
|
|
1665
|
+
];
|
|
1666
|
+
const hasIdempotencySignal = IDEMPOTENCY_SIGNALS.some((s) => workflowText.includes(s));
|
|
1667
|
+
if (!hasIdempotencySignal) {
|
|
1668
|
+
for (const node of sendNodes) {
|
|
1669
|
+
this.warn(
|
|
1670
|
+
issues,
|
|
1671
|
+
35,
|
|
1672
|
+
`Node "${node.name}" sends email but no duplicate-prevention signal detected \u2014 add a sent_at timestamp field, a prior-send IF check, or a deduplication key to avoid repeat sends`,
|
|
1673
|
+
node.id
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1275
1678
|
// Rule 34 (WARN): webhook path contains spaces, starts with slash, or looks like a full URL
|
|
1276
1679
|
checkRule34(w, issues) {
|
|
1277
1680
|
if (!Array.isArray(w.nodes)) return;
|
|
@@ -1534,7 +1937,7 @@ function scoreToMode(score) {
|
|
|
1534
1937
|
}
|
|
1535
1938
|
|
|
1536
1939
|
// src/validation/rule-metadata.ts
|
|
1537
|
-
var VALIDATOR_RULE_IDS = Array.from({ length:
|
|
1940
|
+
var VALIDATOR_RULE_IDS = Array.from({ length: 35 }, (_, i) => i + 1);
|
|
1538
1941
|
var RULE_PIPELINE_STAGES = {
|
|
1539
1942
|
1: "node_generation",
|
|
1540
1943
|
2: "node_generation",
|
|
@@ -1569,7 +1972,8 @@ var RULE_PIPELINE_STAGES = {
|
|
|
1569
1972
|
31: "node_generation",
|
|
1570
1973
|
32: "node_generation",
|
|
1571
1974
|
33: "node_generation",
|
|
1572
|
-
34: "node_generation"
|
|
1975
|
+
34: "node_generation",
|
|
1976
|
+
35: "node_generation"
|
|
1573
1977
|
};
|
|
1574
1978
|
var RULE_EXAMPLES = {
|
|
1575
1979
|
17: {
|
|
@@ -1619,6 +2023,10 @@ var RULE_EXAMPLES = {
|
|
|
1619
2023
|
34: {
|
|
1620
2024
|
bad: '"path": "/my webhook"',
|
|
1621
2025
|
good: '"path": "my-webhook"'
|
|
2026
|
+
},
|
|
2027
|
+
35: {
|
|
2028
|
+
bad: '"type": "n8n-nodes-base.gmail", "parameters": { "operation": "send" } // no sent_at tracking',
|
|
2029
|
+
good: 'Add a Set node after send that writes "sent_at": "={{ $now }}" back to the sheet, or an IF node that checks sent_at before sending'
|
|
1622
2030
|
}
|
|
1623
2031
|
};
|
|
1624
2032
|
var RULE_MITIGATIONS = {
|
|
@@ -1655,7 +2063,8 @@ var RULE_MITIGATIONS = {
|
|
|
1655
2063
|
31: "Add at least one condition to the if node \u2014 conditions.conditions array must be non-empty",
|
|
1656
2064
|
32: "Add field assignments to the set node \u2014 assignments.assignments array must be non-empty for typeVersion 3.x",
|
|
1657
2065
|
33: "Add at least one schedule rule to scheduleTrigger \u2014 rule.interval array must have at least one entry",
|
|
1658
|
-
34: 'Webhook path must be a relative path without spaces, leading slashes, or protocol prefixes (e.g. "my-hook")'
|
|
2066
|
+
34: 'Webhook path must be a relative path without spaces, leading slashes, or protocol prefixes (e.g. "my-hook")',
|
|
2067
|
+
35: "Add duplicate-prevention to email-sending workflows: a sent_at timestamp field updated after each send, or an IF node that checks prior-send status before sending"
|
|
1659
2068
|
};
|
|
1660
2069
|
|
|
1661
2070
|
// src/generation/prompt-builder.ts
|
|
@@ -4108,6 +4517,9 @@ Kairos SDK \u2014 LLM-powered n8n workflow generation
|
|
|
4108
4517
|
Usage:
|
|
4109
4518
|
kairos init First-time setup wizard
|
|
4110
4519
|
kairos build <description> [options]
|
|
4520
|
+
kairos build-pack <business context> [options]
|
|
4521
|
+
kairos pack export <name> [--handoff]
|
|
4522
|
+
kairos validate-pack <name>
|
|
4111
4523
|
kairos replace <n8n-id> <description>
|
|
4112
4524
|
kairos patterns [options]
|
|
4113
4525
|
kairos sessions [options]
|
|
@@ -4124,6 +4536,16 @@ Build options:
|
|
|
4124
4536
|
--activate Activate the workflow after deployment
|
|
4125
4537
|
--smoke-test After deploy, trigger the workflow and verify it runs without error
|
|
4126
4538
|
|
|
4539
|
+
Build-pack options:
|
|
4540
|
+
--dry-run Plan and validate without deploying
|
|
4541
|
+
--activate Activate each workflow after deployment (blocked if blocking assumptions exist)
|
|
4542
|
+
--yes Skip confirmation prompt and build immediately
|
|
4543
|
+
|
|
4544
|
+
Pack options:
|
|
4545
|
+
pack export <name> Print the saved pack as JSON
|
|
4546
|
+
pack export <name> --handoff Generate a client-ready Markdown handoff document
|
|
4547
|
+
validate-pack <name> Cross-workflow safety check before activation
|
|
4548
|
+
|
|
4127
4549
|
Patterns options:
|
|
4128
4550
|
--days <days> Analysis window (default: 30)
|
|
4129
4551
|
--json Output raw JSON instead of summary
|
|
@@ -4455,6 +4877,210 @@ Recent Sessions (last ${sessions.length})`);
|
|
|
4455
4877
|
console.log(`${s.date} ${status}${nameStr}${attemptsStr}${typeTag}${rulesStr}`);
|
|
4456
4878
|
}
|
|
4457
4879
|
}
|
|
4880
|
+
function printPackResult(result) {
|
|
4881
|
+
const line = "\u2500".repeat(50);
|
|
4882
|
+
const deployed = result.workflows.filter((w) => w.deployed).length;
|
|
4883
|
+
const total = result.workflows.length;
|
|
4884
|
+
console.error(`
|
|
4885
|
+
${result.businessContext} \u2014 Workflow Pack`);
|
|
4886
|
+
console.error("\u2550".repeat(Math.min(result.businessContext.length + 18, 60)));
|
|
4887
|
+
console.error(`Status: ${result.status}`);
|
|
4888
|
+
const blocking = result.assumptions.filter((a) => a.type === "blocking");
|
|
4889
|
+
if (blocking.length > 0) {
|
|
4890
|
+
console.error(`
|
|
4891
|
+
\u26A0 Blocking Issues (${blocking.length}) \u2014 resolve before activating`);
|
|
4892
|
+
console.error(line);
|
|
4893
|
+
for (const a of blocking) {
|
|
4894
|
+
console.error(` \u2717 ${a.text}`);
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
console.error(`
|
|
4898
|
+
Workflows Built (${deployed}/${total})`);
|
|
4899
|
+
console.error(line);
|
|
4900
|
+
for (const wf of result.workflows) {
|
|
4901
|
+
const icon = wf.error ? "\u2717" : "\u2713";
|
|
4902
|
+
const idStr = wf.workflowId ? ` [${wf.workflowId}]` : "";
|
|
4903
|
+
const attStr = wf.generationAttempts > 1 ? ` ${wf.generationAttempts} attempts` : "";
|
|
4904
|
+
console.error(` ${icon} ${wf.name}${idStr}${attStr}`);
|
|
4905
|
+
console.error(` ${wf.purpose}`);
|
|
4906
|
+
if (wf.error) console.error(` Error: ${wf.error}`);
|
|
4907
|
+
}
|
|
4908
|
+
if (result.allCredentials.length > 0) {
|
|
4909
|
+
console.error(`
|
|
4910
|
+
Credentials Needed (connect once in n8n)`);
|
|
4911
|
+
console.error(line);
|
|
4912
|
+
for (const cred of result.allCredentials) {
|
|
4913
|
+
console.error(` \u25A1 ${cred.service}`);
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
if (result.sheetsColumns.length > 0) {
|
|
4917
|
+
console.error(`
|
|
4918
|
+
Google Sheets Required`);
|
|
4919
|
+
console.error(line);
|
|
4920
|
+
for (const sheet of result.sheetsColumns) {
|
|
4921
|
+
console.error(` \u25A1 ${sheet.sheet}: ${sheet.columns.join(", ")}`);
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
const needsConfirmation = result.assumptions.filter((a) => a.type === "needs_confirmation");
|
|
4925
|
+
if (needsConfirmation.length > 0) {
|
|
4926
|
+
console.error(`
|
|
4927
|
+
Needs Confirmation Before Going Live`);
|
|
4928
|
+
console.error(line);
|
|
4929
|
+
for (const a of needsConfirmation) {
|
|
4930
|
+
console.error(` ? ${a.text}`);
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
const safe = result.assumptions.filter((a) => a.type === "safe");
|
|
4934
|
+
if (safe.length > 0) {
|
|
4935
|
+
console.error(`
|
|
4936
|
+
Safe Assumptions`);
|
|
4937
|
+
console.error(line);
|
|
4938
|
+
for (const a of safe) {
|
|
4939
|
+
console.error(` - ${a.text}`);
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
if (result.testChecklist.length > 0) {
|
|
4943
|
+
console.error(`
|
|
4944
|
+
Test Checklist`);
|
|
4945
|
+
console.error(line);
|
|
4946
|
+
for (const item of result.testChecklist) {
|
|
4947
|
+
console.error(` ${item.workflow}`);
|
|
4948
|
+
for (const step of item.steps) {
|
|
4949
|
+
console.error(` \u25A1 ${step}`);
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
async function handleBuildPack(positional, flags) {
|
|
4955
|
+
const businessContext = positional.join(" ");
|
|
4956
|
+
if (!businessContext) {
|
|
4957
|
+
console.error("Usage: kairos build-pack <business context description> [--dry-run] [--activate] [--yes]");
|
|
4958
|
+
process.exit(1);
|
|
4959
|
+
}
|
|
4960
|
+
const anthropicKey = getEnvOrExit("ANTHROPIC_API_KEY");
|
|
4961
|
+
const { PackBuilder: PackBuilder2 } = await Promise.resolve().then(() => (init_pack_builder(), pack_builder_exports));
|
|
4962
|
+
const isDryRun = flags["dry-run"] === true;
|
|
4963
|
+
const kairos = isDryRun ? createDryRunClient() : createClient();
|
|
4964
|
+
const builder = new PackBuilder2({ anthropicApiKey: anthropicKey, kairos });
|
|
4965
|
+
console.error("\nPlanning workflow pack...");
|
|
4966
|
+
const plan = await builder.plan(businessContext);
|
|
4967
|
+
console.error(`
|
|
4968
|
+
${businessContext} \u2014 Planned Workflows (${plan.workflows.length})
|
|
4969
|
+
`);
|
|
4970
|
+
for (let i = 0; i < plan.workflows.length; i++) {
|
|
4971
|
+
const wf = plan.workflows[i];
|
|
4972
|
+
console.error(` ${i + 1}. ${wf.name}`);
|
|
4973
|
+
console.error(` ${wf.purpose}`);
|
|
4974
|
+
}
|
|
4975
|
+
const planBlocking = plan.assumptions.filter((a) => a.type === "blocking");
|
|
4976
|
+
const planNeedsConfirmation = plan.assumptions.filter((a) => a.type === "needs_confirmation");
|
|
4977
|
+
if (planBlocking.length > 0) {
|
|
4978
|
+
console.error(`
|
|
4979
|
+
Blocking Issues (resolve before activation)`);
|
|
4980
|
+
for (const a of planBlocking) console.error(` \u2717 ${a.text}`);
|
|
4981
|
+
}
|
|
4982
|
+
if (planNeedsConfirmation.length > 0) {
|
|
4983
|
+
console.error(`
|
|
4984
|
+
Needs Confirmation`);
|
|
4985
|
+
for (const a of planNeedsConfirmation) console.error(` ? ${a.text}`);
|
|
4986
|
+
}
|
|
4987
|
+
if (flags["yes"] !== true) {
|
|
4988
|
+
const readline = await import("readline");
|
|
4989
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
4990
|
+
const answer = await new Promise((resolve) => rl.question("\nBuild all of these? [y/N] ", resolve));
|
|
4991
|
+
rl.close();
|
|
4992
|
+
if (!answer.toLowerCase().startsWith("y")) {
|
|
4993
|
+
console.error("Aborted.");
|
|
4994
|
+
process.exit(0);
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
console.error("\nBuilding...\n");
|
|
4998
|
+
const result = await builder.build(plan, {
|
|
4999
|
+
dryRun: isDryRun,
|
|
5000
|
+
activate: flags["activate"] === true,
|
|
5001
|
+
onProgress: (wf, i, total) => {
|
|
5002
|
+
console.error(` [${i + 1}/${total}] ${wf.name}...`);
|
|
5003
|
+
}
|
|
5004
|
+
});
|
|
5005
|
+
printPackResult(result);
|
|
5006
|
+
const { writeFile: writeFile3, mkdir: mkdir4 } = await import("fs/promises");
|
|
5007
|
+
const { join: join8 } = await import("path");
|
|
5008
|
+
const { homedir: homedir7 } = await import("os");
|
|
5009
|
+
const packsDir = join8(homedir7(), ".kairos", "packs");
|
|
5010
|
+
await mkdir4(packsDir, { recursive: true });
|
|
5011
|
+
const packPath = join8(packsDir, `${result.packName}.json`);
|
|
5012
|
+
await writeFile3(packPath, JSON.stringify(result, null, 2), "utf-8");
|
|
5013
|
+
console.error(`
|
|
5014
|
+
Pack saved to: ${packPath}`);
|
|
5015
|
+
}
|
|
5016
|
+
async function handlePackExport(positional, flags) {
|
|
5017
|
+
const packName = positional[0];
|
|
5018
|
+
if (!packName) {
|
|
5019
|
+
console.error("Usage: kairos pack export <pack-name> [--handoff]");
|
|
5020
|
+
process.exit(1);
|
|
5021
|
+
}
|
|
5022
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
5023
|
+
const { join: join8 } = await import("path");
|
|
5024
|
+
const { homedir: homedir7 } = await import("os");
|
|
5025
|
+
const packPath = join8(homedir7(), ".kairos", "packs", `${packName}.json`);
|
|
5026
|
+
let pack;
|
|
5027
|
+
try {
|
|
5028
|
+
const content = await readFile2(packPath, "utf-8");
|
|
5029
|
+
pack = JSON.parse(content);
|
|
5030
|
+
} catch {
|
|
5031
|
+
console.error(`Pack not found: ${packPath}`);
|
|
5032
|
+
console.error('Run "kairos build-pack <context>" to create one.');
|
|
5033
|
+
process.exit(1);
|
|
5034
|
+
}
|
|
5035
|
+
if (flags["handoff"] === true) {
|
|
5036
|
+
const { generateHandoff: generateHandoff2 } = await Promise.resolve().then(() => (init_pack_exporter(), pack_exporter_exports));
|
|
5037
|
+
console.log(generateHandoff2(pack));
|
|
5038
|
+
} else {
|
|
5039
|
+
console.log(JSON.stringify(pack, null, 2));
|
|
5040
|
+
}
|
|
5041
|
+
}
|
|
5042
|
+
async function handleValidatePack(positional) {
|
|
5043
|
+
const packName = positional[0];
|
|
5044
|
+
if (!packName) {
|
|
5045
|
+
console.error("Usage: kairos validate-pack <pack-name>");
|
|
5046
|
+
process.exit(1);
|
|
5047
|
+
}
|
|
5048
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
5049
|
+
const { join: join8 } = await import("path");
|
|
5050
|
+
const { homedir: homedir7 } = await import("os");
|
|
5051
|
+
const packPath = join8(homedir7(), ".kairos", "packs", `${packName}.json`);
|
|
5052
|
+
let pack;
|
|
5053
|
+
try {
|
|
5054
|
+
const content = await readFile2(packPath, "utf-8");
|
|
5055
|
+
pack = JSON.parse(content);
|
|
5056
|
+
} catch {
|
|
5057
|
+
console.error(`Pack not found: ${packPath}`);
|
|
5058
|
+
console.error('Run "kairos build-pack <context>" to create one.');
|
|
5059
|
+
process.exit(1);
|
|
5060
|
+
}
|
|
5061
|
+
const { validatePack: validatePack2 } = await Promise.resolve().then(() => (init_pack_validator(), pack_validator_exports));
|
|
5062
|
+
const issues = validatePack2(pack);
|
|
5063
|
+
const packLabel = `"${packName}" (status: ${pack.status})`;
|
|
5064
|
+
if (issues.length === 0) {
|
|
5065
|
+
console.log(`\u2713 Pack ${packLabel} passed all cross-workflow checks`);
|
|
5066
|
+
return;
|
|
5067
|
+
}
|
|
5068
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
5069
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
5070
|
+
console.log(`
|
|
5071
|
+
${packName} \u2014 Pack Validation`);
|
|
5072
|
+
console.log("\u2500".repeat(50));
|
|
5073
|
+
console.log(`Status: ${pack.status}`);
|
|
5074
|
+
console.log(`Issues: ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
5075
|
+
console.log("");
|
|
5076
|
+
for (const issue of errors) {
|
|
5077
|
+
console.log(` \u2717 [error] ${issue.message}`);
|
|
5078
|
+
}
|
|
5079
|
+
for (const issue of warnings) {
|
|
5080
|
+
console.log(` \u26A0 [warning] ${issue.message}`);
|
|
5081
|
+
}
|
|
5082
|
+
if (errors.length > 0) process.exit(1);
|
|
5083
|
+
}
|
|
4458
5084
|
async function handleInit() {
|
|
4459
5085
|
const { writeFile: writeFile3, readFile: readFile2, mkdir: mkdir4 } = await import("fs/promises");
|
|
4460
5086
|
const { join: join8 } = await import("path");
|
|
@@ -4568,6 +5194,9 @@ async function main() {
|
|
|
4568
5194
|
case "build":
|
|
4569
5195
|
await handleBuild(positional, flags);
|
|
4570
5196
|
break;
|
|
5197
|
+
case "build-pack":
|
|
5198
|
+
await handleBuildPack(positional, flags);
|
|
5199
|
+
break;
|
|
4571
5200
|
case "replace":
|
|
4572
5201
|
await handleReplace(positional);
|
|
4573
5202
|
break;
|
|
@@ -4595,6 +5224,21 @@ async function main() {
|
|
|
4595
5224
|
case "sync-templates":
|
|
4596
5225
|
await handleSyncTemplates(flags);
|
|
4597
5226
|
break;
|
|
5227
|
+
case "pack": {
|
|
5228
|
+
const subcommand = positional[0];
|
|
5229
|
+
const subPositional = positional.slice(1);
|
|
5230
|
+
if (subcommand === "export") {
|
|
5231
|
+
await handlePackExport(subPositional, flags);
|
|
5232
|
+
} else {
|
|
5233
|
+
console.error(`Unknown pack subcommand: ${subcommand ?? "(none)"}`);
|
|
5234
|
+
console.error("Available: kairos pack export <name> [--handoff]");
|
|
5235
|
+
process.exit(1);
|
|
5236
|
+
}
|
|
5237
|
+
break;
|
|
5238
|
+
}
|
|
5239
|
+
case "validate-pack":
|
|
5240
|
+
await handleValidatePack(positional);
|
|
5241
|
+
break;
|
|
4598
5242
|
default:
|
|
4599
5243
|
console.error(`Unknown command: ${command}`);
|
|
4600
5244
|
console.log(HELP);
|