blokctl 0.2.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.
Files changed (169) hide show
  1. package/dist/commands/build/index.d.ts +2 -0
  2. package/dist/commands/build/index.js +210 -0
  3. package/dist/commands/config/index.d.ts +1 -0
  4. package/dist/commands/config/index.js +46 -0
  5. package/dist/commands/cost/index.d.ts +1 -0
  6. package/dist/commands/cost/index.js +74 -0
  7. package/dist/commands/create/node.d.ts +2 -0
  8. package/dist/commands/create/node.js +541 -0
  9. package/dist/commands/create/project.d.ts +2 -0
  10. package/dist/commands/create/project.js +941 -0
  11. package/dist/commands/create/utils/Examples.d.ts +39 -0
  12. package/dist/commands/create/utils/Examples.js +983 -0
  13. package/dist/commands/create/workflow.d.ts +2 -0
  14. package/dist/commands/create/workflow.js +109 -0
  15. package/dist/commands/deploy/index.d.ts +2 -0
  16. package/dist/commands/deploy/index.js +176 -0
  17. package/dist/commands/dev/index.d.ts +2 -0
  18. package/dist/commands/dev/index.js +190 -0
  19. package/dist/commands/generate/GenerationAnalytics.d.ts +61 -0
  20. package/dist/commands/generate/GenerationAnalytics.js +162 -0
  21. package/dist/commands/generate/GenerationAnalytics.test.d.ts +1 -0
  22. package/dist/commands/generate/GenerationAnalytics.test.js +407 -0
  23. package/dist/commands/generate/NodeFileWriter.d.ts +5 -0
  24. package/dist/commands/generate/NodeFileWriter.js +240 -0
  25. package/dist/commands/generate/NodeGenerator.d.ts +20 -0
  26. package/dist/commands/generate/NodeGenerator.js +181 -0
  27. package/dist/commands/generate/NodeGenerator.test.d.ts +1 -0
  28. package/dist/commands/generate/NodeGenerator.test.js +101 -0
  29. package/dist/commands/generate/PromptVersioning.d.ts +25 -0
  30. package/dist/commands/generate/PromptVersioning.js +71 -0
  31. package/dist/commands/generate/PromptVersioning.test.d.ts +1 -0
  32. package/dist/commands/generate/PromptVersioning.test.js +120 -0
  33. package/dist/commands/generate/RegisterNode.d.ts +3 -0
  34. package/dist/commands/generate/RegisterNode.js +37 -0
  35. package/dist/commands/generate/RuntimeGenerator.d.ts +40 -0
  36. package/dist/commands/generate/RuntimeGenerator.js +369 -0
  37. package/dist/commands/generate/RuntimeGenerator.test.d.ts +1 -0
  38. package/dist/commands/generate/RuntimeGenerator.test.js +553 -0
  39. package/dist/commands/generate/TriggerGenerator.d.ts +22 -0
  40. package/dist/commands/generate/TriggerGenerator.js +220 -0
  41. package/dist/commands/generate/TriggerGenerator.test.d.ts +1 -0
  42. package/dist/commands/generate/TriggerGenerator.test.js +209 -0
  43. package/dist/commands/generate/WorkflowGenerator.d.ts +20 -0
  44. package/dist/commands/generate/WorkflowGenerator.js +131 -0
  45. package/dist/commands/generate/WorkflowGenerator.test.d.ts +1 -0
  46. package/dist/commands/generate/WorkflowGenerator.test.js +77 -0
  47. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.d.ts +1 -0
  48. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +216 -0
  49. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.d.ts +1 -0
  50. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +759 -0
  51. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.d.ts +1 -0
  52. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +295 -0
  53. package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.d.ts +1 -0
  54. package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.js +353 -0
  55. package/dist/commands/generate/index.d.ts +1 -0
  56. package/dist/commands/generate/index.js +418 -0
  57. package/dist/commands/generate/prompts/create-fn-node.system.d.ts +5 -0
  58. package/dist/commands/generate/prompts/create-fn-node.system.js +256 -0
  59. package/dist/commands/generate/prompts/create-node-manifest.system.d.ts +4 -0
  60. package/dist/commands/generate/prompts/create-node-manifest.system.js +41 -0
  61. package/dist/commands/generate/prompts/create-node.system.d.ts +5 -0
  62. package/dist/commands/generate/prompts/create-node.system.js +114 -0
  63. package/dist/commands/generate/prompts/create-readme.system.d.ts +4 -0
  64. package/dist/commands/generate/prompts/create-readme.system.js +83 -0
  65. package/dist/commands/generate/prompts/create-runtime.system.d.ts +5 -0
  66. package/dist/commands/generate/prompts/create-runtime.system.js +284 -0
  67. package/dist/commands/generate/prompts/create-trigger.system.d.ts +5 -0
  68. package/dist/commands/generate/prompts/create-trigger.system.js +293 -0
  69. package/dist/commands/generate/prompts/create-workflow.system.d.ts +5 -0
  70. package/dist/commands/generate/prompts/create-workflow.system.js +476 -0
  71. package/dist/commands/generate/prompts/register-node.system.d.ts +4 -0
  72. package/dist/commands/generate/prompts/register-node.system.js +26 -0
  73. package/dist/commands/generate/validators/CompilationValidator.d.ts +9 -0
  74. package/dist/commands/generate/validators/CompilationValidator.js +86 -0
  75. package/dist/commands/generate/validators/CompilationValidator.test.d.ts +1 -0
  76. package/dist/commands/generate/validators/CompilationValidator.test.js +161 -0
  77. package/dist/commands/generate/validators/NodeValidator.d.ts +18 -0
  78. package/dist/commands/generate/validators/NodeValidator.js +217 -0
  79. package/dist/commands/generate/validators/NodeValidator.test.d.ts +1 -0
  80. package/dist/commands/generate/validators/NodeValidator.test.js +281 -0
  81. package/dist/commands/generate/validators/WorkflowValidator.d.ts +6 -0
  82. package/dist/commands/generate/validators/WorkflowValidator.js +301 -0
  83. package/dist/commands/generate/validators/WorkflowValidator.test.d.ts +1 -0
  84. package/dist/commands/generate/validators/WorkflowValidator.test.js +647 -0
  85. package/dist/commands/generate/validators/index.d.ts +4 -0
  86. package/dist/commands/generate/validators/index.js +2 -0
  87. package/dist/commands/graph/index.d.ts +1 -0
  88. package/dist/commands/graph/index.js +69 -0
  89. package/dist/commands/install/index.d.ts +1 -0
  90. package/dist/commands/install/index.js +4 -0
  91. package/dist/commands/install/node.d.ts +4 -0
  92. package/dist/commands/install/node.js +136 -0
  93. package/dist/commands/install/workflow.d.ts +4 -0
  94. package/dist/commands/install/workflow.js +62 -0
  95. package/dist/commands/login/index.d.ts +2 -0
  96. package/dist/commands/login/index.js +77 -0
  97. package/dist/commands/logout/index.d.ts +2 -0
  98. package/dist/commands/logout/index.js +20 -0
  99. package/dist/commands/marketplace/runtime.d.ts +54 -0
  100. package/dist/commands/marketplace/runtime.js +350 -0
  101. package/dist/commands/migrate/index.d.ts +1 -0
  102. package/dist/commands/migrate/index.js +14 -0
  103. package/dist/commands/migrate/node.d.ts +2 -0
  104. package/dist/commands/migrate/node.js +110 -0
  105. package/dist/commands/monitor/index.d.ts +1 -0
  106. package/dist/commands/monitor/index.js +28 -0
  107. package/dist/commands/monitor/monitor-component.d.ts +1 -0
  108. package/dist/commands/monitor/monitor-component.js +271 -0
  109. package/dist/commands/monitor/static/index.html +2124 -0
  110. package/dist/commands/monitor/static-web-server.d.ts +1 -0
  111. package/dist/commands/monitor/static-web-server.js +89 -0
  112. package/dist/commands/profile/index.d.ts +1 -0
  113. package/dist/commands/profile/index.js +112 -0
  114. package/dist/commands/publish/index.d.ts +1 -0
  115. package/dist/commands/publish/index.js +4 -0
  116. package/dist/commands/publish/node.d.ts +4 -0
  117. package/dist/commands/publish/node.js +231 -0
  118. package/dist/commands/publish/workflow.d.ts +4 -0
  119. package/dist/commands/publish/workflow.js +165 -0
  120. package/dist/commands/search/docs.d.ts +17 -0
  121. package/dist/commands/search/docs.js +179 -0
  122. package/dist/commands/search/index.d.ts +1 -0
  123. package/dist/commands/search/index.js +5 -0
  124. package/dist/commands/search/indexer.d.ts +10 -0
  125. package/dist/commands/search/indexer.js +265 -0
  126. package/dist/commands/search/nodes.d.ts +4 -0
  127. package/dist/commands/search/nodes.js +101 -0
  128. package/dist/commands/search/workflow.d.ts +4 -0
  129. package/dist/commands/search/workflow.js +100 -0
  130. package/dist/commands/trace/index.d.ts +1 -0
  131. package/dist/commands/trace/index.js +26 -0
  132. package/dist/commands/trace/startStudio.d.ts +8 -0
  133. package/dist/commands/trace/startStudio.js +116 -0
  134. package/dist/index.d.ts +17 -0
  135. package/dist/index.js +186 -0
  136. package/dist/services/commander.d.ts +9 -0
  137. package/dist/services/commander.js +20 -0
  138. package/dist/services/constants.d.ts +1 -0
  139. package/dist/services/constants.js +3 -0
  140. package/dist/services/local-token-manager.d.ts +14 -0
  141. package/dist/services/local-token-manager.js +99 -0
  142. package/dist/services/non-interactive.d.ts +5 -0
  143. package/dist/services/non-interactive.js +30 -0
  144. package/dist/services/package-manager.d.ts +35 -0
  145. package/dist/services/package-manager.js +111 -0
  146. package/dist/services/posthog.d.ts +31 -0
  147. package/dist/services/posthog.js +159 -0
  148. package/dist/services/registry-manager.d.ts +9 -0
  149. package/dist/services/registry-manager.js +26 -0
  150. package/dist/services/runtime-detector.d.ts +23 -0
  151. package/dist/services/runtime-detector.js +181 -0
  152. package/dist/services/runtime-setup.d.ts +36 -0
  153. package/dist/services/runtime-setup.js +250 -0
  154. package/dist/services/utils.d.ts +2 -0
  155. package/dist/services/utils.js +29 -0
  156. package/dist/services/workflow-loader.d.ts +30 -0
  157. package/dist/services/workflow-loader.js +46 -0
  158. package/dist/studio-dist/assets/charts-Dso0hPUR.js +68 -0
  159. package/dist/studio-dist/assets/graph-CsV2nWGn.js +23 -0
  160. package/dist/studio-dist/assets/icons-zP8LLgPh.js +311 -0
  161. package/dist/studio-dist/assets/index-CLyEkXMx.css +1 -0
  162. package/dist/studio-dist/assets/index-CNXFX_ar.js +27 -0
  163. package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +17 -0
  164. package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +1 -0
  165. package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +25 -0
  166. package/dist/studio-dist/assets/tanstack-table-DhwRvuH2.js +22 -0
  167. package/dist/studio-dist/favicon.svg +5 -0
  168. package/dist/studio-dist/index.html +21 -0
  169. package/package.json +75 -0
@@ -0,0 +1,418 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import readline from "node:readline";
4
+ import * as p from "@clack/prompts";
5
+ import { Command } from "commander";
6
+ import figlet from "figlet";
7
+ import open from "open";
8
+ import color from "picocolors";
9
+ import { program, trackCommandExecution } from "../../services/commander.js";
10
+ import { isNonInteractive } from "../../services/non-interactive.js";
11
+ import { getPreferredEditor } from "../../services/utils.js";
12
+ import NodeFileWriter from "./NodeFileWriter.js";
13
+ import NodeGenerator from "./NodeGenerator.js";
14
+ import RegisterNode from "./RegisterNode.js";
15
+ import RuntimeGenerator, { isSupportedLanguage } from "./RuntimeGenerator.js";
16
+ import TriggerGenerator from "./TriggerGenerator.js";
17
+ import WorkflowGenerator from "./WorkflowGenerator.js";
18
+ const create = new Command("generate").description("Generate code snippets using AI");
19
+ create
20
+ .command("ai-node")
21
+ .description("Generate a Node.js code snippet using AI")
22
+ .option("-n, --name <value>", "Name of the Node code snippet")
23
+ .option("-p, --prompt <value>", "Prompt for AI code generation")
24
+ .option("-t, --type <value>", "Type of code snippet (default: 'class')")
25
+ .option("-s, --style <value>", "Node style: 'function' (default, defineNode) or 'class' (extends BlokService)")
26
+ .option("-u, --update", "Update existing Node code snippet")
27
+ .option("-c, --code <value>", "Code string to use with --update (skip multiline readline)")
28
+ .option("--code-file <value>", "File path to read code from for --update")
29
+ .option("-k, --api-key <value>", "OpenAI API key (optional, uses environment variable OPENAI_API_KEY if not provided)")
30
+ .action(async (options) => {
31
+ await trackCommandExecution({
32
+ command: "generate ai-node",
33
+ args: options,
34
+ execution: async () => {
35
+ console.log(figlet.textSync("blok CLI".toUpperCase(), {
36
+ font: "Digital",
37
+ horizontalLayout: "default",
38
+ verticalLayout: "default",
39
+ width: 100,
40
+ whitespaceBreak: true,
41
+ }));
42
+ console.log("");
43
+ if (!options.name) {
44
+ if (!options.update && !options.prompt) {
45
+ console.error("Both --name and --prompt options are required.");
46
+ }
47
+ else {
48
+ console.error("The --name option is required.");
49
+ }
50
+ process.exit(1);
51
+ }
52
+ if (!options.apiKey && !process.env.OPENAI_API_KEY) {
53
+ console.error("An OpenAI API key is required. Please provide it using --api-key or set the OPENAI_API_KEY environment variable.");
54
+ process.exit(1);
55
+ }
56
+ p.intro(color.inverse(" Create a New Node Code Snippet "));
57
+ const s = p.spinner();
58
+ let node = {};
59
+ let cleaned = "";
60
+ let nodeType = "class";
61
+ const nodeStyle = options.style || "function";
62
+ if (!options.update) {
63
+ s.start(`Generating ${nodeStyle === "function" ? "function-first" : "class-based"} Node code snippet...`);
64
+ const generator = new NodeGenerator();
65
+ node = await generator.generateNode(options.name.toLowerCase().replace(/\s+/g, "-"), options.prompt, options.apiKey || process.env.OPENAI_API_KEY, false, nodeStyle);
66
+ cleaned = node.code.replace(/^```typescript\s*([\s\S]*?)\s*```$/gm, "$1");
67
+ nodeType = options.type || "class";
68
+ }
69
+ else {
70
+ const nodeName = options.name.toLowerCase().replace(/\s+/g, "-");
71
+ p.intro(color.inverse(`🛠️ Update Existing Node: ${nodeName}`));
72
+ let multilineInput = "";
73
+ if (options.code) {
74
+ multilineInput = options.code;
75
+ }
76
+ else if (options.codeFile) {
77
+ if (!fs.existsSync(options.codeFile)) {
78
+ console.error(`File not found: ${options.codeFile}`);
79
+ process.exit(1);
80
+ }
81
+ multilineInput = fs.readFileSync(options.codeFile, "utf-8");
82
+ }
83
+ else if (isNonInteractive()) {
84
+ console.error("Missing required flag --code or --code-file (non-interactive mode). " +
85
+ "Provide code via --code or --code-file when using --update in non-interactive mode.");
86
+ process.exit(1);
87
+ }
88
+ else {
89
+ const rl = readline.createInterface({
90
+ input: process.stdin,
91
+ output: process.stdout,
92
+ terminal: true,
93
+ });
94
+ const lines = [];
95
+ console.log("\n Enter your code below:");
96
+ console.log(" - Type 'quit' on a new line to finish");
97
+ console.log(" - Press Ctrl+C to cancel");
98
+ console.log(" ----------------------------------------\n");
99
+ multilineInput = await new Promise((resolve) => {
100
+ rl.on("line", (input) => {
101
+ if (input.trim().toLocaleLowerCase() === "quit") {
102
+ rl.close();
103
+ resolve(lines.join("\n"));
104
+ }
105
+ else {
106
+ lines.push(input);
107
+ rl.prompt();
108
+ }
109
+ });
110
+ rl.on("SIGINT", () => {
111
+ console.log("\nInput cancelled");
112
+ rl.close();
113
+ resolve("");
114
+ });
115
+ rl.on("close", () => {
116
+ if (lines.length > 0) {
117
+ resolve(lines.join("\n"));
118
+ }
119
+ else {
120
+ resolve("");
121
+ }
122
+ });
123
+ rl.prompt();
124
+ });
125
+ }
126
+ s.start(`Updating ${nodeStyle === "function" ? "function-first" : "class-based"} Node code...`);
127
+ const generator = new NodeGenerator();
128
+ node = await generator.generateNode(options.name.toLowerCase().replace(/\s+/g, "-"), multilineInput, options.apiKey || process.env.OPENAI_API_KEY, true, nodeStyle);
129
+ cleaned = node.code.replace(/^```typescript\s*([\s\S]*?)\s*```$/gm, "$1");
130
+ nodeType = options.type || "class";
131
+ }
132
+ const filePath = await new NodeFileWriter().generateFile(node.nodeName, nodeType, cleaned, options.apiKey || process.env.OPENAI_API_KEY, nodeStyle);
133
+ s.message(`Registering node "${node.nodeName}" in Nodes.ts...`);
134
+ const register = new RegisterNode();
135
+ const nodesFilePath = await register.generateNodesFile(node.nodeName, `./nodes/${node.nodeName}`, node.code, options.apiKey || process.env.OPENAI_API_KEY);
136
+ const editor = getPreferredEditor();
137
+ await open(filePath, { app: { name: editor }, wait: false });
138
+ if (!options.update) {
139
+ await open(nodesFilePath, { app: { name: editor }, wait: false });
140
+ }
141
+ s.stop(`Node code snippet "${node.nodeName}" generated and registered successfully!`);
142
+ if (nodeStyle === "function") {
143
+ console.log(color.cyan("\n✨ Function-First Node Generated!"));
144
+ console.log(" • Type-safe with Zod validation");
145
+ console.log(" • 60% less boilerplate than class-based");
146
+ console.log(" • AI-friendly for code generation");
147
+ console.log("\n📖 Learn more: https://blok.build/docs/nodes/function-first\n");
148
+ }
149
+ },
150
+ });
151
+ });
152
+ create
153
+ .command("ai-workflow")
154
+ .description("Generate a workflow JSON configuration using AI")
155
+ .option("-n, --name <value>", "Name of the workflow")
156
+ .option("-p, --prompt <value>", "Prompt describing the workflow behavior")
157
+ .option("-t, --trigger <value>", "Trigger type: http, queue, pubsub, cron, webhook, websocket, sse (default: 'auto')")
158
+ .option("-u, --update <value>", "Path to existing workflow JSON to update")
159
+ .option("-k, --api-key <value>", "OpenAI API key (optional, uses environment variable OPENAI_API_KEY if not provided)")
160
+ .action(async (options) => {
161
+ await trackCommandExecution({
162
+ command: "generate ai-workflow",
163
+ args: options,
164
+ execution: async () => {
165
+ console.log(figlet.textSync("BLOK AI".toUpperCase(), {
166
+ font: "Digital",
167
+ horizontalLayout: "default",
168
+ verticalLayout: "default",
169
+ width: 100,
170
+ whitespaceBreak: true,
171
+ }));
172
+ console.log("");
173
+ if (!options.name) {
174
+ console.error("The --name option is required.");
175
+ process.exit(1);
176
+ }
177
+ if (!options.prompt && !options.update) {
178
+ console.error("The --prompt option is required for new workflows.");
179
+ process.exit(1);
180
+ }
181
+ if (!options.apiKey && !process.env.OPENAI_API_KEY) {
182
+ console.error("An OpenAI API key is required. Please provide it using --api-key or set the OPENAI_API_KEY environment variable.");
183
+ process.exit(1);
184
+ }
185
+ const isUpdate = !!options.update;
186
+ p.intro(color.inverse(isUpdate ? " Update Existing Workflow " : " Generate a New Workflow "));
187
+ const s = p.spinner();
188
+ const workflowName = options.name.toLowerCase().replace(/\s+/g, "-");
189
+ const triggerType = options.trigger || "auto";
190
+ const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
191
+ s.start(`Generating workflow "${workflowName}" with ${triggerType === "auto" ? "auto-detected" : triggerType} trigger...`);
192
+ const generator = new WorkflowGenerator();
193
+ const result = await generator.generateWorkflow(workflowName, options.prompt || "Update this workflow with improvements", apiKey, triggerType, isUpdate, isUpdate ? options.update : undefined);
194
+ const dirPath = process.cwd();
195
+ const workflowsDir = path.join(dirPath, "workflows", "json");
196
+ if (!fs.existsSync(workflowsDir)) {
197
+ fs.mkdirSync(workflowsDir, { recursive: true });
198
+ }
199
+ const filePath = path.join(workflowsDir, `${workflowName}.json`);
200
+ fs.writeFileSync(filePath, result.json, "utf8");
201
+ if (result.validationResult) {
202
+ if (result.validationResult.valid) {
203
+ s.stop(`Workflow "${workflowName}" generated successfully!`);
204
+ }
205
+ else {
206
+ s.stop(`Workflow "${workflowName}" generated with validation warnings.`);
207
+ console.log(color.yellow("\n⚠️ Validation Issues:"));
208
+ for (const error of result.validationResult.errors) {
209
+ console.log(color.red(` ✗ ${error}`));
210
+ }
211
+ }
212
+ if (result.validationResult.warnings.length > 0) {
213
+ console.log(color.yellow("\n⚠️ Warnings:"));
214
+ for (const warning of result.validationResult.warnings) {
215
+ console.log(color.yellow(` ! ${warning}`));
216
+ }
217
+ }
218
+ console.log(color.dim(`\n Attempts: ${result.validationResult.attempts}/${3}`));
219
+ }
220
+ const editor = getPreferredEditor();
221
+ await open(filePath, { app: { name: editor }, wait: false });
222
+ console.log(color.cyan(`\n✨ Workflow Generated: ${filePath}`));
223
+ console.log(` Trigger: ${result.triggerType}`);
224
+ console.log(` Name: ${workflowName}`);
225
+ console.log(color.dim("\n To use this workflow, ensure all referenced nodes are installed or created.\n"));
226
+ },
227
+ });
228
+ });
229
+ create
230
+ .command("ai-trigger")
231
+ .description("Generate a trigger implementation using AI")
232
+ .option("-n, --name <value>", "Name of the trigger")
233
+ .option("-t, --type <value>", "Trigger type: queue, pubsub, cron, webhook, websocket, sse, custom")
234
+ .option("-p, --prompt <value>", "Prompt describing the trigger behavior")
235
+ .option("-u, --update <value>", "Path to existing trigger file to update")
236
+ .option("-k, --api-key <value>", "OpenAI API key (optional, uses environment variable OPENAI_API_KEY if not provided)")
237
+ .action(async (options) => {
238
+ await trackCommandExecution({
239
+ command: "generate ai-trigger",
240
+ args: options,
241
+ execution: async () => {
242
+ console.log(figlet.textSync("BLOK AI".toUpperCase(), {
243
+ font: "Digital",
244
+ horizontalLayout: "default",
245
+ verticalLayout: "default",
246
+ width: 100,
247
+ whitespaceBreak: true,
248
+ }));
249
+ console.log("");
250
+ if (!options.name) {
251
+ console.error("The --name option is required.");
252
+ process.exit(1);
253
+ }
254
+ if (!options.type) {
255
+ console.error("The --type option is required. Valid types: queue, pubsub, cron, webhook, websocket, sse, custom");
256
+ process.exit(1);
257
+ }
258
+ if (!options.prompt && !options.update) {
259
+ console.error("The --prompt option is required for new triggers.");
260
+ process.exit(1);
261
+ }
262
+ if (!options.apiKey && !process.env.OPENAI_API_KEY) {
263
+ console.error("An OpenAI API key is required. Please provide it using --api-key or set the OPENAI_API_KEY environment variable.");
264
+ process.exit(1);
265
+ }
266
+ const isUpdate = !!options.update;
267
+ p.intro(color.inverse(isUpdate ? " Update Existing Trigger " : " Generate a New Trigger "));
268
+ const s = p.spinner();
269
+ const triggerName = options.name.toLowerCase().replace(/\s+/g, "-");
270
+ const triggerType = options.type.toLowerCase();
271
+ const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
272
+ s.start(`Generating ${triggerType} trigger "${triggerName}"...`);
273
+ const generator = new TriggerGenerator();
274
+ const result = await generator.generateTrigger(triggerName, triggerType, options.prompt || "Update this trigger with improvements", apiKey, isUpdate, isUpdate ? options.update : undefined);
275
+ const dirPath = process.cwd();
276
+ const triggerDir = path.join(dirPath, "triggers", triggerName, "src");
277
+ if (!fs.existsSync(triggerDir)) {
278
+ fs.mkdirSync(triggerDir, { recursive: true });
279
+ }
280
+ const className = triggerName
281
+ .split("-")
282
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
283
+ .join("");
284
+ const filePath = path.join(triggerDir, `${className}Trigger.ts`);
285
+ const cleaned = result.code.replace(/^```typescript\s*([\s\S]*?)\s*```$/gm, "$1");
286
+ fs.writeFileSync(filePath, cleaned, "utf8");
287
+ const indexPath = path.join(triggerDir, "index.ts");
288
+ if (!fs.existsSync(indexPath)) {
289
+ fs.writeFileSync(indexPath, `export { default } from "./${className}Trigger.js";\nexport * from "./${className}Trigger.js";\n`, "utf8");
290
+ }
291
+ if (result.validationResult) {
292
+ if (result.validationResult.valid) {
293
+ s.stop(`Trigger "${triggerName}" generated successfully!`);
294
+ }
295
+ else {
296
+ s.stop(`Trigger "${triggerName}" generated with validation warnings.`);
297
+ console.log(color.yellow("\n⚠️ Validation Issues:"));
298
+ for (const error of result.validationResult.errors) {
299
+ console.log(color.red(` ✗ ${error}`));
300
+ }
301
+ }
302
+ if (result.validationResult.warnings.length > 0) {
303
+ console.log(color.yellow("\n⚠️ Warnings:"));
304
+ for (const warning of result.validationResult.warnings) {
305
+ console.log(color.yellow(` ! ${warning}`));
306
+ }
307
+ }
308
+ console.log(color.dim(`\n Attempts: ${result.validationResult.attempts}/${3}`));
309
+ }
310
+ const editor = getPreferredEditor();
311
+ await open(filePath, { app: { name: editor }, wait: false });
312
+ console.log(color.cyan(`\n✨ Trigger Generated: ${filePath}`));
313
+ console.log(` Type: ${triggerType}`);
314
+ console.log(` Class: ${className}Trigger`);
315
+ console.log(color.dim("\n To use this trigger:"));
316
+ console.log(color.dim(" 1. Install any required dependencies"));
317
+ console.log(color.dim(" 2. Create a server entry point to instantiate and start the trigger"));
318
+ console.log(color.dim(` 3. Configure workflows with trigger type "${triggerType}"\n`));
319
+ },
320
+ });
321
+ });
322
+ create
323
+ .command("ai-runtime")
324
+ .description("Generate a runtime SDK for a specific programming language")
325
+ .option("-l, --language <value>", "Target language: go, java, rust, python, csharp, php, ruby")
326
+ .option("-p, --prompt <value>", "Additional instructions for the runtime generation")
327
+ .option("-u, --update <value>", "Path to existing runtime directory to update")
328
+ .option("-k, --api-key <value>", "OpenAI API key (optional, uses environment variable OPENAI_API_KEY if not provided)")
329
+ .action(async (options) => {
330
+ await trackCommandExecution({
331
+ command: "generate ai-runtime",
332
+ args: options,
333
+ execution: async () => {
334
+ console.log(figlet.textSync("BLOK AI".toUpperCase(), {
335
+ font: "Digital",
336
+ horizontalLayout: "default",
337
+ verticalLayout: "default",
338
+ width: 100,
339
+ whitespaceBreak: true,
340
+ }));
341
+ console.log("");
342
+ if (!options.language) {
343
+ console.error("The --language option is required. Valid languages: go, java, rust, python, csharp, php, ruby");
344
+ process.exit(1);
345
+ }
346
+ const language = options.language.toLowerCase();
347
+ if (!isSupportedLanguage(language)) {
348
+ console.error(`Unsupported language: "${language}". Valid languages: go, java, rust, python, csharp, php, ruby`);
349
+ process.exit(1);
350
+ }
351
+ if (!options.prompt && !options.update) {
352
+ console.error("The --prompt option is required for new runtimes.");
353
+ process.exit(1);
354
+ }
355
+ if (!options.apiKey && !process.env.OPENAI_API_KEY) {
356
+ console.error("An OpenAI API key is required. Please provide it using --api-key or set the OPENAI_API_KEY environment variable.");
357
+ process.exit(1);
358
+ }
359
+ const isUpdate = !!options.update;
360
+ p.intro(color.inverse(isUpdate
361
+ ? ` Update Existing ${language.toUpperCase()} Runtime `
362
+ : ` Generate ${language.toUpperCase()} Runtime SDK `));
363
+ const s = p.spinner();
364
+ const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
365
+ const userPrompt = options.prompt || "Generate a complete runtime SDK with example nodes";
366
+ s.start(`Generating ${language} runtime SDK...`);
367
+ const generator = new RuntimeGenerator();
368
+ const result = await generator.generateRuntime(language, userPrompt, apiKey, isUpdate, isUpdate ? options.update : undefined);
369
+ const dirPath = process.cwd();
370
+ const runtimeDir = path.join(dirPath, "runtimes", language);
371
+ let filesWritten = 0;
372
+ for (const file of result.files) {
373
+ const filePath = path.join(runtimeDir, file.path);
374
+ const fileDir = path.dirname(filePath);
375
+ if (!fs.existsSync(fileDir)) {
376
+ fs.mkdirSync(fileDir, { recursive: true });
377
+ }
378
+ fs.writeFileSync(filePath, file.content, "utf8");
379
+ filesWritten++;
380
+ }
381
+ if (result.validationResult) {
382
+ if (result.validationResult.valid) {
383
+ s.stop(`${language.toUpperCase()} runtime SDK generated successfully!`);
384
+ }
385
+ else {
386
+ s.stop(`${language.toUpperCase()} runtime SDK generated with validation warnings.`);
387
+ console.log(color.yellow("\n\u26a0\ufe0f Validation Issues:"));
388
+ for (const error of result.validationResult.errors) {
389
+ console.log(color.red(` \u2717 ${error}`));
390
+ }
391
+ }
392
+ if (result.validationResult.warnings.length > 0) {
393
+ console.log(color.yellow("\n\u26a0\ufe0f Warnings:"));
394
+ for (const warning of result.validationResult.warnings) {
395
+ console.log(color.yellow(` ! ${warning}`));
396
+ }
397
+ }
398
+ console.log(color.dim(`\n Attempts: ${result.validationResult.attempts}/${3}`));
399
+ }
400
+ console.log(color.cyan(`\n\u2728 Runtime SDK Generated: ${runtimeDir}`));
401
+ console.log(` Language: ${language}`);
402
+ console.log(` Files: ${filesWritten}`);
403
+ if (result.files.length > 0) {
404
+ console.log(color.dim("\n Generated files:"));
405
+ for (const file of result.files) {
406
+ console.log(color.dim(` \u2022 ${file.path}`));
407
+ }
408
+ }
409
+ console.log(color.dim("\n To use this runtime:"));
410
+ console.log(color.dim(" 1. cd into the generated directory"));
411
+ console.log(color.dim(" 2. Build the runtime (see the generated README or Dockerfile)"));
412
+ console.log(color.dim(` 3. Run: docker build -t blok-runtime-${language} .`));
413
+ console.log(color.dim(` 4. Run: docker run -p 8080:8080 blok-runtime-${language}`));
414
+ console.log(color.dim(" 5. Register the Docker adapter in your Blok configuration\n"));
415
+ },
416
+ });
417
+ });
418
+ program.addCommand(create);
@@ -0,0 +1,5 @@
1
+ declare const createFnNodeSystemPrompt: {
2
+ prompt: string;
3
+ updatePrompt: string;
4
+ };
5
+ export default createFnNodeSystemPrompt;
@@ -0,0 +1,256 @@
1
+ const createFnNodeSystemPrompt = {
2
+ prompt: `You are a senior backend engineer specializing in bloks using the \`@blok\` framework. Your task is to generate a fully working **function-first Node file** that performs the described logic using a Zod schema-based API with the \`defineNode\` helper.
3
+
4
+ What to return:
5
+
6
+ * Return only a complete \`index.ts\` file, ready to be saved directly into \`src/nodes/<node-name>/index.ts\`.
7
+ * It must include:
8
+
9
+ 1. Proper imports:
10
+ * \`z\` from \`zod\`
11
+ * \`Context\` from \`@blok/shared\`
12
+ * \`defineNode\` from \`@blok/runner\`
13
+ 2. A clear and structured \`input\` schema using Zod (z.object with proper types).
14
+ 3. A matching \`output\` schema using Zod.
15
+ 4. A single exported node instance created via \`defineNode\` with:
16
+ * \`name\`: the node key/name (e.g., "fetch-user")
17
+ * \`description\`: short human-readable description
18
+ * \`input\`: Zod input schema
19
+ * \`output\`: Zod output schema
20
+ * \`execute(ctx, input)\`: the full business logic implementation.
21
+
22
+ Constraints:
23
+
24
+ * **Do NOT use classes.** Do not extend \`BlokService\` directly; always use the \`defineNode\` helper. The helper internally takes care of \`BlokService\`, \`handle\`, and \`BlokResponse\` wiring.
25
+ * The Zod \`input\` schema must fully describe the expected input object with proper types.
26
+ * The Zod \`output\` schema must fully describe the object returned by \`execute\`.
27
+ * Inside \`execute(ctx, input)\`:
28
+ * Use the strongly-typed \`input\`, which is automatically inferred from the Zod schema.
29
+ * Use \`ctx\` to access request data, configuration, and cross-node state when needed:
30
+ * \`ctx.request.body\`, \`ctx.request.query\`, \`ctx.request.params\` for HTTP data
31
+ * \`ctx.vars\` for reading/writing values shared between nodes
32
+ * \`ctx.logger\` for logging
33
+ * \`ctx.env\` for environment variables
34
+ * Do **not** construct or return \`BlokResponse\` here; just return a plain object matching the output schema. The wrapper created by \`defineNode\` will call \`setSuccess\` / \`setError\` and handle \`GlobalError\`.
35
+ * On validation errors or runtime errors, you do NOT manually throw \`GlobalError\`; throw/rethrow normal errors. The \`defineNode\` wrapper will catch them and map them to \`GlobalError\` consistently with proper error codes:
36
+ * Zod validation errors → 400 Bad Request
37
+ * Runtime errors → 500 Internal Server Error
38
+ * Node output should be JSON-serializable and match the output schema. Avoid returning functions, class instances, or non-serializable structures.
39
+ * Use Zod's built-in validators (z.string().email(), z.number().positive(), z.string().url(), etc.) for proper validation.
40
+ * Use optional fields with .optional() and defaults with .default() as needed.
41
+
42
+ Formatting:
43
+
44
+ * No explanations, comments, or markdown fences outside the TypeScript file.
45
+ * The output must be a single valid TypeScript module.
46
+ * Export the node as default: \`export default defineNode({...})\`
47
+
48
+ Real-World Examples to Guide You:
49
+
50
+ **Example 1: API Call Node**
51
+
52
+ \`\`\`typescript
53
+ import type { Context } from "@blok/shared";
54
+ import { z } from "zod";
55
+ import { defineNode } from "@blok/runner";
56
+
57
+ export default defineNode({
58
+ name: "api-call",
59
+ description: "Makes HTTP API calls with automatic JSON handling",
60
+
61
+ input: z.object({
62
+ url: z.string().url("Must be a valid URL"),
63
+ method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
64
+ headers: z.record(z.string()).optional(),
65
+ body: z.any().optional(),
66
+ timeout: z.number().positive().optional().default(30000),
67
+ }),
68
+
69
+ output: z.object({
70
+ status: z.number().int().min(100).max(599),
71
+ statusText: z.string(),
72
+ data: z.any(),
73
+ headers: z.record(z.string()),
74
+ duration: z.number(),
75
+ }),
76
+
77
+ async execute(ctx, input) {
78
+ const startTime = performance.now();
79
+
80
+ ctx.logger.log(\`Making \${input.method} request to \${input.url}\`);
81
+
82
+ const response = await fetch(input.url, {
83
+ method: input.method,
84
+ headers: {
85
+ "Content-Type": "application/json",
86
+ ...input.headers,
87
+ },
88
+ body: input.body ? JSON.stringify(input.body) : undefined,
89
+ signal: AbortSignal.timeout(input.timeout),
90
+ });
91
+
92
+ const contentType = response.headers.get("content-type") || "";
93
+ let data: unknown;
94
+
95
+ if (contentType.includes("application/json")) {
96
+ data = await response.json();
97
+ } else {
98
+ data = await response.text();
99
+ }
100
+
101
+ const duration = performance.now() - startTime;
102
+
103
+ ctx.logger.log(\`Request completed in \${duration.toFixed(2)}ms with status \${response.status}\`);
104
+
105
+ if (ctx.vars) {
106
+ ctx.vars["api-response"] = { status: response.status, data };
107
+ }
108
+
109
+ return {
110
+ status: response.status,
111
+ statusText: response.statusText,
112
+ data,
113
+ headers: Object.fromEntries(response.headers.entries()),
114
+ duration,
115
+ };
116
+ },
117
+ });
118
+ \`\`\`
119
+
120
+ **Example 2: Fetch User Node**
121
+
122
+ \`\`\`typescript
123
+ import type { Context } from "@blok/shared";
124
+ import { z } from "zod";
125
+ import { defineNode } from "@blok/runner";
126
+
127
+ export default defineNode({
128
+ name: "fetch-user",
129
+ description: "Fetches user profile by ID from database",
130
+
131
+ input: z.object({
132
+ userId: z.string().uuid("userId must be a valid UUID"),
133
+ includeMetadata: z.boolean().optional().default(false),
134
+ }),
135
+
136
+ output: z.object({
137
+ user: z.object({
138
+ id: z.string().uuid(),
139
+ name: z.string().min(1),
140
+ email: z.string().email(),
141
+ createdAt: z.string().datetime(),
142
+ metadata: z.record(z.unknown()).optional(),
143
+ }),
144
+ }),
145
+
146
+ async execute(ctx, input) {
147
+ ctx.logger.log(\`Fetching user: \${input.userId}\`);
148
+
149
+ // Simulate database fetch
150
+ const user = await fetchUserFromDatabase(input.userId, input.includeMetadata);
151
+
152
+ // Store in context for downstream nodes
153
+ if (ctx.vars) {
154
+ ctx.vars["current-user"] = user;
155
+ }
156
+
157
+ return { user };
158
+ },
159
+ });
160
+
161
+ async function fetchUserFromDatabase(userId: string, includeMetadata: boolean) {
162
+ // Your database logic here
163
+ return {
164
+ id: userId,
165
+ name: "John Doe",
166
+ email: "john@example.com",
167
+ createdAt: new Date().toISOString(),
168
+ ...(includeMetadata && {
169
+ metadata: { lastLogin: new Date().toISOString(), loginCount: 42 },
170
+ }),
171
+ };
172
+ }
173
+ \`\`\`
174
+
175
+ Template to follow (adapt and fill based on the user's request):
176
+
177
+ import type { Context } from "@blok/shared";
178
+ import { z } from "zod";
179
+ import { defineNode } from "@blok/runner";
180
+
181
+ /**
182
+ * [Brief description of what this node does]
183
+ */
184
+ export default defineNode({
185
+ name: "[node-key]", // e.g., "fetch-user"
186
+ description: "[Short description of what this node does]",
187
+
188
+ // Input schema using Zod - automatically validated
189
+ input: z.object({
190
+ // TODO: Define input fields based on the requested functionality
191
+ // Example:
192
+ // userId: z.string().uuid(),
193
+ // includeProfile: z.boolean().optional().default(false),
194
+ }),
195
+
196
+ // Output schema using Zod - automatically validated
197
+ output: z.object({
198
+ // TODO: Define output fields that represent the successful result
199
+ // Example:
200
+ // user: z.object({
201
+ // id: z.string(),
202
+ // name: z.string(),
203
+ // email: z.string().email(),
204
+ // }),
205
+ }),
206
+
207
+ // Execute function - type-safe with inferred types from Zod schemas
208
+ async execute(ctx, input) {
209
+ // Implement the core business logic here using ctx + input.
210
+ //
211
+ // Common patterns:
212
+ // - Read HTTP params: const id = ctx.request.params.id;
213
+ // - Read previous node output: const prev = ctx.vars["previous-node-key"];
214
+ // - Write for future nodes: ctx.vars["this-node-key"] = someValue;
215
+ // - Use input.* fields that match the input schema (TypeScript infers the type automatically)
216
+ // - Log messages: ctx.logger.log("Processing request");
217
+ // - Access environment: const apiKey = ctx.env.API_KEY;
218
+
219
+ // TODO: Implement business logic here
220
+
221
+ // The returned value MUST conform to the output schema
222
+ return {
223
+ // TODO: Return the final result matching the output schema
224
+ };
225
+ },
226
+ });`,
227
+ updatePrompt: `You are a senior backend engineer specializing in bloks using the \`@blok\` framework. Your task is to update an existing function-first Node file (using \`defineNode\`) with new functionality while preserving its core structure.
228
+
229
+ Given the existing code below, enhance or modify it according to the user's requirements while maintaining the following:
230
+
231
+ 1. Keep the existing imports and node structure
232
+ 2. Preserve the defineNode pattern (no classes)
233
+ 3. Maintain Zod schemas for input/output validation
234
+ 4. Keep the execute function signature and return pattern
235
+ 5. Maintain type safety and proper TypeScript usage
236
+ 6. Follow the same code style and formatting
237
+
238
+ What to return:
239
+ * Return only the full updated Node file
240
+ * Preserve existing functionality unless explicitly asked to change it
241
+ * Add new functionality as requested
242
+ * Ensure all Zod schemas and types remain in sync
243
+ * Keep input/output schemas comprehensive and accurate
244
+
245
+ Format:
246
+ * No explanations or comments outside the code
247
+ * Return the complete file as it would appear in the .ts file
248
+ * Keep existing JSDoc comments unless they need updating
249
+ * Maintain the function-first pattern with defineNode
250
+
251
+ The code should seamlessly integrate with the existing blok framework and leverage Zod validation for type safety and runtime validation.
252
+
253
+ Current Code to be improved:
254
+ `,
255
+ };
256
+ export default createFnNodeSystemPrompt;