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,179 @@
1
+ import { Command } from "../../services/commander.js";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { createOpenAI } from "@ai-sdk/openai";
5
+ import { generateText } from "ai";
6
+ import color from "picocolors";
7
+ import pluralize from "pluralize";
8
+ import { Indexer } from "./indexer.js";
9
+ export class SearchService {
10
+ MAX_TOKENS = 3000;
11
+ indexer = new Indexer();
12
+ extractKeywords(text) {
13
+ return text
14
+ .toLowerCase()
15
+ .replace(/[^\w\s]/g, "")
16
+ .split(/\s+/)
17
+ .map((w) => pluralize.singular(w))
18
+ .filter(Boolean);
19
+ }
20
+ estimateTokens(text) {
21
+ return Math.ceil(text.length / 4);
22
+ }
23
+ async getRankedFilesFromIndex(index, question) {
24
+ const keywords = this.extractKeywords(question);
25
+ const fileScores = {};
26
+ for (const word of keywords) {
27
+ let matches = index.words.filter((w) => w.value === word);
28
+ if (matches.length === 0 && word.length > 3) {
29
+ matches = index.words.filter((w) => w.value.includes(word) || word.includes(w.value));
30
+ }
31
+ if (matches.length === 0)
32
+ continue;
33
+ for (const match of matches) {
34
+ for (const fileEntry of match.files) {
35
+ const matchScore = match.value === word ? fileEntry.score : fileEntry.score * 0.5;
36
+ fileScores[fileEntry.path] = (fileScores[fileEntry.path] || 0) + matchScore;
37
+ }
38
+ }
39
+ }
40
+ return Object.entries(fileScores)
41
+ .map(([path, score]) => ({ path, score }))
42
+ .sort((a, b) => b.score - a.score);
43
+ }
44
+ async getLimitedContext(files) {
45
+ let total = 0;
46
+ const chunks = [];
47
+ for (const { path: file } of files) {
48
+ try {
49
+ const content = await fs.readFile(file, "utf-8");
50
+ let formattedContent;
51
+ if (file.endsWith(".json")) {
52
+ const jsonContent = JSON.parse(content);
53
+ formattedContent = [
54
+ `# ${jsonContent.name || path.basename(file)}`,
55
+ `Description: ${jsonContent.description || "No description"}`,
56
+ jsonContent.type ? `Type: ${jsonContent.type}` : "",
57
+ "",
58
+ "Configuration:",
59
+ "```json",
60
+ JSON.stringify(jsonContent, null, 2),
61
+ "```",
62
+ ]
63
+ .filter(Boolean)
64
+ .join("\n");
65
+ }
66
+ else {
67
+ formattedContent = content;
68
+ }
69
+ const tokens = this.estimateTokens(formattedContent);
70
+ if (total + tokens > this.MAX_TOKENS)
71
+ break;
72
+ total += tokens;
73
+ chunks.push(`---\n# File: ${path.basename(file)}\n${formattedContent}`);
74
+ }
75
+ catch (error) {
76
+ console.log(color.red(`Error reading file: ${file}`));
77
+ }
78
+ }
79
+ return chunks.join("\n\n");
80
+ }
81
+ async ask(question, options = {}) {
82
+ const index = await this.indexer.getIndex(options.noCache);
83
+ const ranked = await this.getRankedFilesFromIndex(index, question);
84
+ if (ranked.length === 0) {
85
+ console.log(color.yellow("No relevant files found for the search terms"));
86
+ return;
87
+ }
88
+ const context = await this.getLimitedContext(ranked);
89
+ if (!context) {
90
+ console.log(color.yellow("No content could be read from the files"));
91
+ return;
92
+ }
93
+ try {
94
+ const openai = createOpenAI({
95
+ compatibility: "strict",
96
+ apiKey: process.env.OPENAI_API_KEY,
97
+ });
98
+ console.log(color.cyan("\nAnalyzing documentation..."));
99
+ const response = await generateText({
100
+ model: openai("gpt-4"),
101
+ system: `You are a helpful assistant that answers questions based on the documentation provided.
102
+ Your responses should be clear, concise, and directly based on the content in the documentation.
103
+ If the documentation includes code examples or configuration, include relevant parts in your response.
104
+ Format your response using markdown for better readability.`,
105
+ prompt: `Context:\n${context}\n\nQuestion: ${question}`,
106
+ temperature: 0.2,
107
+ });
108
+ console.log(`\n${color.dim("Sources used:")}`);
109
+ for (const { path: file, score } of ranked) {
110
+ console.log(color.dim(`- ${path.basename(file)} (relevance: ${score.toFixed(2)})`));
111
+ }
112
+ console.log();
113
+ console.log(color.green("Answer:"));
114
+ console.log("─".repeat(process.stdout.columns || 80));
115
+ console.log(this.formatResponseText(response.text));
116
+ console.log("─".repeat(process.stdout.columns || 80));
117
+ console.log();
118
+ }
119
+ catch (error) {
120
+ console.log(color.red("\nError generating response:"));
121
+ console.log(color.red(error instanceof Error ? error.message : "Unknown error"));
122
+ }
123
+ }
124
+ formatResponseText(text) {
125
+ return text
126
+ .split("\n")
127
+ .map((line) => {
128
+ if (line.startsWith("```")) {
129
+ return color.dim("─".repeat(process.stdout.columns || 80));
130
+ }
131
+ if (line.includes("`")) {
132
+ return line.replace(/`([^`]+)`/g, (_, code) => color.cyan(code));
133
+ }
134
+ if (line.includes("**")) {
135
+ return line.replace(/\*\*([^*]+)\*\*/g, (_, text) => color.bold(text));
136
+ }
137
+ if (line.startsWith("#")) {
138
+ const headerMatch = line.match(/^#+/);
139
+ if (headerMatch) {
140
+ const title = line.replace(/^#+\s*/, "");
141
+ return color.bold(color.blue(title));
142
+ }
143
+ }
144
+ if (line.match(/^[-*]\s/)) {
145
+ return `${color.dim("•")} ${line.replace(/^[-*]\s/, "")}`;
146
+ }
147
+ if (line.match(/^\d+\.\s/)) {
148
+ const numMatch = line.match(/^\d+/);
149
+ if (numMatch) {
150
+ return `${color.dim(`${numMatch[0]}.`)}${line.replace(/^\d+\.\s/, " ")}`;
151
+ }
152
+ }
153
+ if (line.match(/^\s*["{[]/) && line.match(/[}"\]]$/)) {
154
+ try {
155
+ return color.gray(line);
156
+ }
157
+ catch {
158
+ return line;
159
+ }
160
+ }
161
+ return line;
162
+ })
163
+ .join("\n");
164
+ }
165
+ }
166
+ export default new Command()
167
+ .command("docs")
168
+ .description("This command allows you to search for information in the documentation.")
169
+ .option("-q, --question <value>", "Question to search for")
170
+ .option("--no-cache", "Force rebuild of search index")
171
+ .action(async (options) => {
172
+ const question = options.question;
173
+ if (!question) {
174
+ console.error("Question is required");
175
+ process.exit(1);
176
+ }
177
+ const searchService = new SearchService();
178
+ await searchService.ask(question, { noCache: !options.cache });
179
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { program } from "../../services/commander.js";
2
+ import docs from "./docs.js";
3
+ import node from "./nodes.js";
4
+ import workflow from "./workflow.js";
5
+ program.command("search").description("Search commands").addCommand(node).addCommand(workflow).addCommand(docs);
@@ -0,0 +1,10 @@
1
+ export declare class Indexer {
2
+ private readonly INDEX_PATH;
3
+ private readonly ONE_DAY_MS;
4
+ private extractKeywords;
5
+ private indexDirectory;
6
+ private indexJsonDirectory;
7
+ private mergeWordMaps;
8
+ private buildIndex;
9
+ getIndex(forceRebuild?: boolean): Promise<ReturnType<typeof this.buildIndex>>;
10
+ }
@@ -0,0 +1,265 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import fg from "fast-glob";
5
+ import color from "picocolors";
6
+ import pluralize from "pluralize";
7
+ export class Indexer {
8
+ INDEX_PATH = `${os.homedir()}/.blok/search_indexes.json`;
9
+ ONE_DAY_MS = 24 * 60 * 60 * 1000;
10
+ extractKeywords(text) {
11
+ const stopWords = new Set([
12
+ "a",
13
+ "an",
14
+ "the",
15
+ "in",
16
+ "on",
17
+ "at",
18
+ "to",
19
+ "for",
20
+ "of",
21
+ "with",
22
+ "by",
23
+ "el",
24
+ "la",
25
+ "los",
26
+ "las",
27
+ "un",
28
+ "una",
29
+ "unos",
30
+ "unas",
31
+ "de",
32
+ "del",
33
+ "is",
34
+ "are",
35
+ "was",
36
+ "were",
37
+ "be",
38
+ "been",
39
+ "being",
40
+ "es",
41
+ "son",
42
+ "está",
43
+ "están",
44
+ "this",
45
+ "that",
46
+ "these",
47
+ "those",
48
+ "here",
49
+ "there",
50
+ "este",
51
+ "esta",
52
+ "estos",
53
+ "estas",
54
+ "aquí",
55
+ "allí",
56
+ "function",
57
+ "class",
58
+ "const",
59
+ "let",
60
+ "var",
61
+ "return",
62
+ "import",
63
+ "export",
64
+ ]);
65
+ const words = text
66
+ .toLowerCase()
67
+ .replace(/```[\s\S]*?```/g, " ")
68
+ .replace(/`.*?`/g, " ")
69
+ .replace(/\[.*?\]/g, " ")
70
+ .replace(/\(.*?\)/g, " ")
71
+ .replace(/[#*_]/g, " ")
72
+ .replace(/[^a-z\s]/g, " ")
73
+ .split(/\s+/)
74
+ .filter((word) => word && word.length > 2 && !stopWords.has(word))
75
+ .map((word) => pluralize.singular(word));
76
+ return [...new Set(words)];
77
+ }
78
+ async indexDirectory(dirPath, sourceLabel) {
79
+ const wordMap = {};
80
+ try {
81
+ await fs.access(dirPath);
82
+ const files = await fg(["**/*.md", "**/README.md", "**/readme.md"], {
83
+ cwd: dirPath,
84
+ absolute: true,
85
+ onlyFiles: true,
86
+ followSymbolicLinks: false,
87
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"],
88
+ });
89
+ for (const file of files) {
90
+ try {
91
+ const raw = await fs.readFile(file, "utf-8");
92
+ const words = this.extractKeywords(raw);
93
+ const fileWordMap = {};
94
+ const wordFreq = {};
95
+ for (const word of words) {
96
+ wordFreq[word] = (wordFreq[word] || 0) + 1;
97
+ }
98
+ for (const [word, freq] of Object.entries(wordFreq)) {
99
+ const score = freq * Math.sqrt(word.length);
100
+ const entry = { path: file, score, source: sourceLabel };
101
+ if (!fileWordMap[word]) {
102
+ fileWordMap[word] = [];
103
+ }
104
+ else if (!Array.isArray(fileWordMap[word])) {
105
+ fileWordMap[word] = [];
106
+ }
107
+ fileWordMap[word].push(entry);
108
+ }
109
+ for (const [word, entries] of Object.entries(fileWordMap)) {
110
+ if (!wordMap[word]) {
111
+ wordMap[word] = [];
112
+ }
113
+ else if (!Array.isArray(wordMap[word])) {
114
+ wordMap[word] = [];
115
+ }
116
+ wordMap[word].push(...entries);
117
+ }
118
+ }
119
+ catch {
120
+ }
121
+ }
122
+ }
123
+ catch {
124
+ }
125
+ return wordMap;
126
+ }
127
+ async indexJsonDirectory(dirPath, sourceLabel) {
128
+ const wordMap = {};
129
+ try {
130
+ await fs.access(dirPath);
131
+ const files = await fg(["**/*.json"], {
132
+ cwd: dirPath,
133
+ absolute: true,
134
+ onlyFiles: true,
135
+ followSymbolicLinks: false,
136
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"],
137
+ });
138
+ for (const file of files) {
139
+ try {
140
+ const raw = await fs.readFile(file, "utf-8");
141
+ const jsonContent = JSON.parse(raw);
142
+ const importantFields = {
143
+ name: 3,
144
+ description: 2,
145
+ type: 1.5,
146
+ };
147
+ const processField = (field, content, weight) => {
148
+ if (typeof content === "string") {
149
+ const words = this.extractKeywords(content);
150
+ for (const word of words) {
151
+ if (!wordMap[word])
152
+ wordMap[word] = [];
153
+ wordMap[word].push({
154
+ path: file,
155
+ score: weight * Math.sqrt(word.length),
156
+ source: sourceLabel,
157
+ });
158
+ }
159
+ }
160
+ };
161
+ for (const [field, weight] of Object.entries(importantFields)) {
162
+ const content = jsonContent[field];
163
+ if (content && typeof content === "string") {
164
+ processField(field, content, weight);
165
+ }
166
+ }
167
+ const textContent = JSON.stringify(jsonContent, null, 2);
168
+ const words = this.extractKeywords(textContent);
169
+ const fileWordMap = {};
170
+ const wordFreq = {};
171
+ for (const word of words) {
172
+ wordFreq[word] = (wordFreq[word] || 0) + 1;
173
+ }
174
+ for (const [word, freq] of Object.entries(wordFreq)) {
175
+ const score = freq * Math.sqrt(word.length);
176
+ const entry = { path: file, score, source: sourceLabel };
177
+ if (!fileWordMap[word]) {
178
+ fileWordMap[word] = [];
179
+ }
180
+ else if (!Array.isArray(fileWordMap[word])) {
181
+ fileWordMap[word] = [];
182
+ }
183
+ fileWordMap[word].push(entry);
184
+ }
185
+ for (const [word, entries] of Object.entries(fileWordMap)) {
186
+ if (!wordMap[word]) {
187
+ wordMap[word] = [];
188
+ }
189
+ else if (!Array.isArray(wordMap[word])) {
190
+ wordMap[word] = [];
191
+ }
192
+ wordMap[word].push(...entries);
193
+ }
194
+ }
195
+ catch {
196
+ }
197
+ }
198
+ }
199
+ catch {
200
+ }
201
+ return wordMap;
202
+ }
203
+ mergeWordMaps(...maps) {
204
+ const combined = {};
205
+ for (const map of maps) {
206
+ for (const [word, entries] of Object.entries(map)) {
207
+ if (!combined[word]) {
208
+ combined[word] = [];
209
+ }
210
+ else if (!Array.isArray(combined[word])) {
211
+ combined[word] = [];
212
+ }
213
+ combined[word].push(...entries);
214
+ }
215
+ }
216
+ return combined;
217
+ }
218
+ async buildIndex() {
219
+ const HOME_DIR = os.homedir();
220
+ const NANOCTL_DIR = path.join(HOME_DIR, ".blok");
221
+ const GITHUB_REPO_LOCAL = path.join(NANOCTL_DIR, "blok");
222
+ const homeDocsPath = path.join(GITHUB_REPO_LOCAL, "docs");
223
+ const userNodesPath = path.resolve(process.cwd(), "src/nodes");
224
+ const notesPath = path.resolve(process.cwd(), "notes");
225
+ const jsonPath = path.resolve(process.cwd(), "workflows/json");
226
+ await fs.mkdir(NANOCTL_DIR, { recursive: true });
227
+ const docsMap = await this.indexDirectory(homeDocsPath, "docs");
228
+ const userMap = await this.indexDirectory(userNodesPath, "user-nodes");
229
+ const notesMap = await this.indexDirectory(notesPath, "notes");
230
+ const jsonMap = await this.indexJsonDirectory(jsonPath, "json-examples");
231
+ const combinedMap = this.mergeWordMaps(docsMap, userMap, notesMap, jsonMap);
232
+ const sortedWords = Object.entries(combinedMap)
233
+ .sort(([a], [b]) => a.localeCompare(b))
234
+ .map(([value, files]) => ({
235
+ value,
236
+ files,
237
+ }));
238
+ const index = {
239
+ words: sortedWords,
240
+ lastIndexDateTime: new Date().toISOString(),
241
+ };
242
+ await fs.writeFile(this.INDEX_PATH, JSON.stringify(index, null, 2));
243
+ return index;
244
+ }
245
+ async getIndex(forceRebuild = false) {
246
+ try {
247
+ if (!forceRebuild) {
248
+ const stat = await fs.stat(this.INDEX_PATH);
249
+ const age = Date.now() - new Date(stat.mtime).getTime();
250
+ if (age <= this.ONE_DAY_MS) {
251
+ const raw = await fs.readFile(this.INDEX_PATH, "utf-8");
252
+ return JSON.parse(raw);
253
+ }
254
+ console.log(color.yellow("Index outdated. Rebuilding..."));
255
+ }
256
+ else {
257
+ console.log(color.yellow("Force rebuilding index..."));
258
+ }
259
+ }
260
+ catch {
261
+ console.log(color.yellow("No index found. Creating new one..."));
262
+ }
263
+ return await this.buildIndex();
264
+ }
265
+ }
@@ -0,0 +1,4 @@
1
+ import { Command, type OptionValues } from "../../services/commander.js";
2
+ export declare function search(opts: OptionValues): Promise<void>;
3
+ declare const _default: Command;
4
+ export default _default;
@@ -0,0 +1,101 @@
1
+ import * as p from "@clack/prompts";
2
+ import { Command, trackCommandExecution } from "../../services/commander.js";
3
+ import { BLOK_URL } from "../../services/constants.js";
4
+ import { tokenManager } from "../../services/local-token-manager.js";
5
+ import { isNonInteractive } from "../../services/non-interactive.js";
6
+ import { install } from "../install/node.js";
7
+ async function searchPkg(opts) {
8
+ const response = await fetch(`${BLOK_URL}/package-list?searchTerm=${opts.node}&format=${opts.format}`, {
9
+ method: "GET",
10
+ headers: {
11
+ Authorization: `Bearer ${opts.token}`,
12
+ },
13
+ });
14
+ if (!response.ok)
15
+ throw new Error(response.statusText);
16
+ const pkgList = await response.json();
17
+ return pkgList.packages;
18
+ }
19
+ export async function search(opts) {
20
+ const token = tokenManager.getToken();
21
+ const logger = p.spinner();
22
+ try {
23
+ if (!token)
24
+ throw new Error("Token is required.");
25
+ if (!opts.node)
26
+ throw new Error("Node name is required.");
27
+ opts.token = token;
28
+ logger.start("Searching for packages...");
29
+ const pkgList = await searchPkg(opts);
30
+ if (pkgList.length === 0) {
31
+ throw new Error("No packages found.");
32
+ }
33
+ if (opts.list || (isNonInteractive() && !opts.install)) {
34
+ logger.stop(`Found ${pkgList.length} package(s):`);
35
+ for (const pkg of pkgList) {
36
+ p.log.info(pkg.package);
37
+ }
38
+ return;
39
+ }
40
+ if (opts.install) {
41
+ const pkgInfo = pkgList.find((pkg) => pkg.package === opts.install);
42
+ if (!pkgInfo)
43
+ throw new Error(`Package "${opts.install}" not found in search results.`);
44
+ logger.stop(`Starting installation of ${pkgInfo.package}...`);
45
+ await trackCommandExecution({
46
+ command: "install",
47
+ args: opts,
48
+ execution: async () => {
49
+ opts.node = pkgInfo.package;
50
+ if (!opts.directory)
51
+ opts.directory = process.cwd();
52
+ await install(opts);
53
+ },
54
+ });
55
+ return;
56
+ }
57
+ const selectedPkg = await p.select({
58
+ message: "Select a package to install",
59
+ options: pkgList.map((pkg) => ({
60
+ label: pkg.package,
61
+ value: pkg,
62
+ })),
63
+ });
64
+ if (!selectedPkg || typeof selectedPkg === "symbol")
65
+ throw new Error("No package selected.");
66
+ const pkgInfo = pkgList.find((pkg) => pkg.package === selectedPkg.package);
67
+ if (!pkgInfo)
68
+ throw new Error("Selected package not found.");
69
+ logger.stop(`Starting installation of ${pkgInfo.package}...`);
70
+ await trackCommandExecution({
71
+ command: "install",
72
+ args: opts,
73
+ execution: async () => {
74
+ opts.node = pkgInfo.package;
75
+ if (!opts.directory)
76
+ opts.directory = process.cwd();
77
+ await install(opts);
78
+ },
79
+ });
80
+ }
81
+ catch (error) {
82
+ logger.error(error.message);
83
+ }
84
+ }
85
+ export default new Command()
86
+ .command("node")
87
+ .description("Publish a node to the registry")
88
+ .option("-f, --format <value>", "Package format", "npm")
89
+ .option("-i, --install <value>", "Package name to auto-install (skip select prompt)")
90
+ .option("-l, --list", "List results without prompting to install")
91
+ .argument("<node>", "Node name")
92
+ .action(async (node, options) => {
93
+ await trackCommandExecution({
94
+ command: "search node",
95
+ args: options,
96
+ execution: async () => {
97
+ options.node = node;
98
+ await search(options);
99
+ },
100
+ });
101
+ });
@@ -0,0 +1,4 @@
1
+ import { Command, type OptionValues } from "../../services/commander.js";
2
+ export declare function search(opts: OptionValues): Promise<void>;
3
+ declare const _default: Command;
4
+ export default _default;
@@ -0,0 +1,100 @@
1
+ import * as p from "@clack/prompts";
2
+ import { Command, trackCommandExecution } from "../../services/commander.js";
3
+ import { BLOK_URL } from "../../services/constants.js";
4
+ import { tokenManager } from "../../services/local-token-manager.js";
5
+ import { isNonInteractive } from "../../services/non-interactive.js";
6
+ import { install } from "../install/workflow.js";
7
+ async function searchWorkflow(opts) {
8
+ const response = await fetch(`${BLOK_URL}/published-workflow?workflow_name=${opts.workflow}`, {
9
+ method: "GET",
10
+ headers: {
11
+ Authorization: `Bearer ${opts.token}`,
12
+ },
13
+ });
14
+ if (!response.ok)
15
+ throw new Error(response.statusText);
16
+ const searchs = await response.json();
17
+ return searchs.documents;
18
+ }
19
+ export async function search(opts) {
20
+ const token = tokenManager.getToken();
21
+ const logger = p.spinner();
22
+ try {
23
+ if (!token)
24
+ throw new Error("Token is required.");
25
+ if (!opts.workflow)
26
+ throw new Error("Workflow argument is required.");
27
+ opts.token = token;
28
+ logger.start("Searching for workflow...");
29
+ const searchs = await searchWorkflow(opts);
30
+ if (searchs.length === 0) {
31
+ throw new Error("No workflow found.");
32
+ }
33
+ if (opts.list || (isNonInteractive() && !opts.install)) {
34
+ logger.stop(`Found ${searchs.length} workflow(s):`);
35
+ for (const data of searchs) {
36
+ p.log.info(`${data.id} (${data.workflow.name}:${data.workflow.version} - ${data.workflow.description})`);
37
+ }
38
+ return;
39
+ }
40
+ if (opts.install) {
41
+ const workflowInfo = searchs.find((workflow) => workflow.id === opts.install);
42
+ if (!workflowInfo)
43
+ throw new Error(`Workflow "${opts.install}" not found in search results.`);
44
+ logger.stop(`Starting installation of ${workflowInfo.id}...`);
45
+ await trackCommandExecution({
46
+ command: "install",
47
+ args: opts,
48
+ execution: async () => {
49
+ opts.workflow = workflowInfo.id;
50
+ if (!opts.directory)
51
+ opts.directory = process.cwd();
52
+ await install(opts);
53
+ },
54
+ });
55
+ return;
56
+ }
57
+ const selectedWorkflow = await p.select({
58
+ message: "Select a workflow to install",
59
+ options: searchs.map((data) => ({
60
+ label: `${data.id} (${data.workflow.name}:${data.workflow.version} - ${data.workflow.description})`,
61
+ value: data,
62
+ })),
63
+ });
64
+ if (!selectedWorkflow || typeof selectedWorkflow === "symbol")
65
+ throw new Error("No workflow selected.");
66
+ const workflowInfo = searchs.find((workflow) => workflow.id === selectedWorkflow.id);
67
+ if (!workflowInfo)
68
+ throw new Error("Selected workflow not found.");
69
+ logger.stop(`Starting installation of ${workflowInfo.id}...`);
70
+ await trackCommandExecution({
71
+ command: "install",
72
+ args: opts,
73
+ execution: async () => {
74
+ opts.workflow = workflowInfo.id;
75
+ if (!opts.directory)
76
+ opts.directory = process.cwd();
77
+ await install(opts);
78
+ },
79
+ });
80
+ }
81
+ catch (error) {
82
+ logger.error(error.message);
83
+ }
84
+ }
85
+ export default new Command()
86
+ .command("workflow")
87
+ .description("Search for a workflow")
88
+ .option("-i, --install <value>", "Workflow ID to auto-install (skip select prompt)")
89
+ .option("-l, --list", "List results without prompting to install")
90
+ .argument("<workflow>", "Workflow hints")
91
+ .action(async (workflow, options) => {
92
+ await trackCommandExecution({
93
+ command: "search workflow",
94
+ args: options,
95
+ execution: async () => {
96
+ options.workflow = workflow;
97
+ await search(options);
98
+ },
99
+ });
100
+ });
@@ -0,0 +1 @@
1
+ export {};