@kairos-sdk/core 0.3.0 → 0.3.1

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/index.d.cts CHANGED
@@ -283,6 +283,17 @@ interface N8nWorkflowResponse {
283
283
  shared?: boolean;
284
284
  isArchived?: boolean;
285
285
  }
286
+ interface N8nNodeTypeInfo {
287
+ name: string;
288
+ displayName: string;
289
+ version: number | number[];
290
+ description?: string;
291
+ group?: string[];
292
+ credentials?: Array<{
293
+ name: string;
294
+ required?: boolean;
295
+ }>;
296
+ }
286
297
 
287
298
  declare class N8nApiClient {
288
299
  private readonly baseUrl;
@@ -304,6 +315,7 @@ declare class N8nApiClient {
304
315
  createTag(name: string): Promise<Tag>;
305
316
  tagWorkflow(workflowId: string, tagIds: string[]): Promise<void>;
306
317
  untagWorkflow(workflowId: string, tagIds: string[]): Promise<void>;
318
+ getNodeTypes(): Promise<N8nNodeTypeInfo[]>;
307
319
  private mapExecution;
308
320
  }
309
321
 
package/dist/index.d.ts CHANGED
@@ -283,6 +283,17 @@ interface N8nWorkflowResponse {
283
283
  shared?: boolean;
284
284
  isArchived?: boolean;
285
285
  }
286
+ interface N8nNodeTypeInfo {
287
+ name: string;
288
+ displayName: string;
289
+ version: number | number[];
290
+ description?: string;
291
+ group?: string[];
292
+ credentials?: Array<{
293
+ name: string;
294
+ required?: boolean;
295
+ }>;
296
+ }
286
297
 
287
298
  declare class N8nApiClient {
288
299
  private readonly baseUrl;
@@ -304,6 +315,7 @@ declare class N8nApiClient {
304
315
  createTag(name: string): Promise<Tag>;
305
316
  tagWorkflow(workflowId: string, tagIds: string[]): Promise<void>;
306
317
  untagWorkflow(workflowId: string, tagIds: string[]): Promise<void>;
318
+ getNodeTypes(): Promise<N8nNodeTypeInfo[]>;
307
319
  private mapExecution;
308
320
  }
309
321
 
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  TelemetryCollector,
9
9
  TemplateSyncer,
10
10
  ValidationError
11
- } from "./chunk-Q77XA7UC.js";
11
+ } from "./chunk-KQSNT3HZ.js";
12
12
  import {
13
13
  ApiError,
14
14
  DEFAULT_REGISTRY,
@@ -26,7 +26,7 @@ import {
26
26
  nullLogger,
27
27
  rerank,
28
28
  tokenize
29
- } from "./chunk-DDV7ZART.js";
29
+ } from "./chunk-RYGYNOR6.js";
30
30
  export {
31
31
  ApiError,
32
32
  DEFAULT_REGISTRY,
@@ -1144,6 +1144,14 @@ var N8nApiClient = class {
1144
1144
  const remaining = (current.tags ?? []).filter((t) => !tagIds.includes(t.id)).map((t) => ({ id: t.id }));
1145
1145
  await this.request("PUT", `/workflows/${workflowId}/tags`, remaining);
1146
1146
  }
1147
+ async getNodeTypes() {
1148
+ try {
1149
+ const response = await this.request("GET", "/node-types");
1150
+ return response.data ?? response;
1151
+ } catch {
1152
+ return [];
1153
+ }
1154
+ }
1147
1155
  mapExecution(e) {
1148
1156
  return {
1149
1157
  id: e.id,
@@ -1363,9 +1371,9 @@ var RULE_REMEDIES = {
1363
1371
  22: "Ensure all required parameters are set for each node type (e.g. webhook needs httpMethod and path)"
1364
1372
  };
1365
1373
  var PromptBuilder = class {
1366
- build(request, matches, globalFailureRates = []) {
1374
+ build(request, matches, globalFailureRates = [], dynamicCatalog) {
1367
1375
  const mode = this.resolveMode(matches);
1368
- const system = this.buildSystem(matches, mode, globalFailureRates);
1376
+ const system = this.buildSystem(matches, mode, globalFailureRates, dynamicCatalog);
1369
1377
  const userMessage = this.buildUserMessage(request, matches, mode);
1370
1378
  return { system, userMessage, mode, matches };
1371
1379
  }
@@ -1384,11 +1392,18 @@ Fix ALL of the above issues in your new response. Do not repeat any of these mis
1384
1392
  if (!top) return "scratch";
1385
1393
  return scoreToMode(top.score);
1386
1394
  }
1387
- buildSystem(matches, mode, globalFailureRates = []) {
1395
+ buildSystem(matches, mode, globalFailureRates = [], dynamicCatalog) {
1396
+ let basePrompt = SYSTEM_PROMPT_V1;
1397
+ if (dynamicCatalog) {
1398
+ basePrompt = basePrompt.replace(
1399
+ /## NODE CATALOG — exact type strings and safe typeVersions[\s\S]*?(?=## PRE-DELIVERY SELF-CHECK)/,
1400
+ dynamicCatalog + "\n\n"
1401
+ );
1402
+ }
1388
1403
  const blocks = [
1389
1404
  {
1390
1405
  type: "text",
1391
- text: SYSTEM_PROMPT_V1,
1406
+ text: basePrompt,
1392
1407
  cache_control: { type: "ephemeral" }
1393
1408
  }
1394
1409
  ];
@@ -1559,6 +1574,63 @@ var TelemetryReader = class {
1559
1574
  }
1560
1575
  };
1561
1576
 
1577
+ // src/validation/node-syncer.ts
1578
+ var TRIGGER_PATTERNS = [/trigger/i, /Trigger$/];
1579
+ var NodeSyncer = class {
1580
+ baseRegistry;
1581
+ constructor() {
1582
+ this.baseRegistry = new Map(DEFAULT_REGISTRY.map((d) => [d.type, d]));
1583
+ }
1584
+ sync(liveNodes) {
1585
+ const merged = new Map(this.baseRegistry);
1586
+ let newNodes = 0;
1587
+ for (const node of liveNodes) {
1588
+ const versions = Array.isArray(node.version) ? node.version : [node.version];
1589
+ const isTrigger = TRIGGER_PATTERNS.some((p) => p.test(node.name));
1590
+ const credentialType = node.credentials?.[0]?.name;
1591
+ const existing = merged.get(node.name);
1592
+ if (existing) {
1593
+ const allVersions = /* @__PURE__ */ new Set([...existing.safeTypeVersions, ...versions]);
1594
+ merged.set(node.name, {
1595
+ ...existing,
1596
+ safeTypeVersions: [...allVersions].sort((a, b) => a - b)
1597
+ });
1598
+ } else {
1599
+ newNodes++;
1600
+ merged.set(node.name, {
1601
+ type: node.name,
1602
+ safeTypeVersions: versions.sort((a, b) => a - b),
1603
+ requiredParams: [],
1604
+ ...credentialType ? { credentialType } : {},
1605
+ ...isTrigger ? { isTrigger: true } : {}
1606
+ });
1607
+ }
1608
+ }
1609
+ const definitions = [...merged.values()];
1610
+ const registry = new NodeRegistry(definitions);
1611
+ const catalogText = this.buildCatalog(definitions);
1612
+ return { registry, catalogText, nodeCount: definitions.length, newNodes };
1613
+ }
1614
+ buildCatalog(definitions) {
1615
+ const triggers = definitions.filter((d) => d.isTrigger);
1616
+ const regular = definitions.filter((d) => !d.isTrigger);
1617
+ const formatEntry = (d) => {
1618
+ const versions = d.safeTypeVersions.join(", ");
1619
+ const cred = d.credentialType ? ` \u2014 cred: ${d.credentialType}` : "";
1620
+ return `${d.type} typeVersion: ${versions}${cred}`;
1621
+ };
1622
+ const triggerLines = triggers.map(formatEntry).join("\n");
1623
+ const regularLines = regular.map(formatEntry).join("\n");
1624
+ return `## NODE CATALOG \u2014 synced from your n8n instance (${definitions.length} node types)
1625
+
1626
+ ### Triggers:
1627
+ ${triggerLines}
1628
+
1629
+ ### Regular nodes:
1630
+ ${regularLines}`;
1631
+ }
1632
+ };
1633
+
1562
1634
  // src/utils/logger.ts
1563
1635
  var nullLogger = {
1564
1636
  debug() {
@@ -1580,6 +1652,8 @@ var __dirname = (0, import_node_path3.dirname)((0, import_node_url.fileURLToPath
1580
1652
  var pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path3.join)(__dirname, "..", "package.json"), "utf-8"));
1581
1653
  var library = new FileLibrary();
1582
1654
  var validator = new N8nValidator();
1655
+ var nodeSyncer = new NodeSyncer();
1656
+ var lastSync = null;
1583
1657
  var stripper = new N8nFieldStripper();
1584
1658
  var promptBuilder = new PromptBuilder();
1585
1659
  function getTelemetryReader() {
@@ -1601,6 +1675,22 @@ function getApiClient() {
1601
1675
  }
1602
1676
  return new N8nApiClient(baseUrl, apiKey, nullLogger);
1603
1677
  }
1678
+ async function autoSync() {
1679
+ if (lastSync) return lastSync;
1680
+ const baseUrl = process.env["N8N_BASE_URL"];
1681
+ const apiKey = process.env["N8N_API_KEY"];
1682
+ if (!baseUrl || !apiKey) return null;
1683
+ try {
1684
+ const client = new N8nApiClient(baseUrl, apiKey, nullLogger);
1685
+ const nodeTypes = await client.getNodeTypes();
1686
+ if (nodeTypes.length === 0) return null;
1687
+ lastSync = nodeSyncer.sync(nodeTypes);
1688
+ validator = new N8nValidator(lastSync.registry);
1689
+ return lastSync;
1690
+ } catch {
1691
+ return null;
1692
+ }
1693
+ }
1604
1694
  var server = new import_mcp.McpServer({
1605
1695
  name: "kairos",
1606
1696
  version: pkg.version
@@ -1613,12 +1703,24 @@ server.tool(
1613
1703
  name: import_zod.z.string().optional().describe("Optional workflow name override")
1614
1704
  },
1615
1705
  async ({ description, name }) => {
1706
+ const baseUrl = process.env["N8N_BASE_URL"];
1707
+ const apiKey = process.env["N8N_API_KEY"];
1708
+ if (!baseUrl || !apiKey) {
1709
+ return {
1710
+ content: [{
1711
+ type: "text",
1712
+ text: JSON.stringify({ error: "N8N_BASE_URL and N8N_API_KEY are required. Kairos needs to sync your n8n instance's node types to generate accurate workflows." })
1713
+ }],
1714
+ isError: true
1715
+ };
1716
+ }
1616
1717
  await library.initialize();
1718
+ const syncResult = await autoSync();
1617
1719
  const matches = await library.search(description);
1618
1720
  const telemetryReader = getTelemetryReader();
1619
1721
  const failureRates = await telemetryReader?.getFailureRates() ?? [];
1620
1722
  const request = { description, ...name ? { name } : {} };
1621
- const built = promptBuilder.build(request, matches, failureRates);
1723
+ const built = promptBuilder.build(request, matches, failureRates, syncResult?.catalogText);
1622
1724
  const systemText = built.system.map((block) => block.text).join("\n\n---\n\n");
1623
1725
  return {
1624
1726
  content: [{
@@ -1627,6 +1729,8 @@ server.tool(
1627
1729
  mode: built.mode,
1628
1730
  matchCount: matches.length,
1629
1731
  topMatchScore: matches[0]?.score ?? null,
1732
+ nodeCatalog: syncResult ? "synced" : "static",
1733
+ nodeCount: syncResult?.nodeCount ?? null,
1630
1734
  systemPrompt: systemText,
1631
1735
  userMessage: built.userMessage,
1632
1736
  outputFormat: {
@@ -1806,6 +1910,46 @@ server.tool(
1806
1910
  };
1807
1911
  }
1808
1912
  );
1913
+ server.tool(
1914
+ "kairos_sync",
1915
+ "Sync the node catalog from your live n8n instance. Fetches all installed node types and versions so Kairos knows exactly what your n8n supports. Automatically called by kairos_prompt when n8n credentials are set, but you can call this manually to force a refresh.",
1916
+ {},
1917
+ async () => {
1918
+ const baseUrl = process.env["N8N_BASE_URL"];
1919
+ const apiKey = process.env["N8N_API_KEY"];
1920
+ if (!baseUrl || !apiKey) {
1921
+ return {
1922
+ content: [{
1923
+ type: "text",
1924
+ text: JSON.stringify({ error: "N8N_BASE_URL and N8N_API_KEY are required for sync." })
1925
+ }],
1926
+ isError: true
1927
+ };
1928
+ }
1929
+ lastSync = null;
1930
+ const result = await autoSync();
1931
+ if (!result) {
1932
+ return {
1933
+ content: [{
1934
+ type: "text",
1935
+ text: JSON.stringify({ error: "Failed to fetch node types from n8n. Check your credentials and that your instance is running." })
1936
+ }],
1937
+ isError: true
1938
+ };
1939
+ }
1940
+ return {
1941
+ content: [{
1942
+ type: "text",
1943
+ text: JSON.stringify({
1944
+ synced: true,
1945
+ nodeCount: result.nodeCount,
1946
+ newNodes: result.newNodes,
1947
+ message: `Synced ${result.nodeCount} node types from your n8n instance (${result.newNodes} not in default catalog).`
1948
+ }, null, 2)
1949
+ }]
1950
+ };
1951
+ }
1952
+ );
1809
1953
  server.tool(
1810
1954
  "kairos_list",
1811
1955
  "List all workflows deployed on the connected n8n instance.",