@eko-ai/eko 1.0.1 → 1.0.3

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 (44) hide show
  1. package/README.md +56 -44
  2. package/dist/core/eko.d.ts +1 -0
  3. package/dist/core/tool-registry.d.ts +1 -1
  4. package/dist/extension/content/index.d.ts +12 -4
  5. package/dist/extension/index.d.ts +0 -2
  6. package/dist/extension/script/build_dom_tree.d.ts +38 -0
  7. package/dist/extension/script/build_dom_tree.js +7 -3
  8. package/dist/extension/tools/browser.d.ts +8 -8
  9. package/dist/extension/tools/browser_use.d.ts +1 -0
  10. package/dist/extension/tools/html_script.d.ts +1 -12
  11. package/dist/extension/tools/index.d.ts +1 -2
  12. package/dist/extension.cjs.js +171 -346
  13. package/dist/extension.esm.js +172 -346
  14. package/dist/extension_content_script.js +105 -76
  15. package/dist/index.cjs.js +59 -49
  16. package/dist/index.esm.js +59 -49
  17. package/dist/models/action.d.ts +3 -2
  18. package/dist/nodejs/core.d.ts +2 -0
  19. package/dist/nodejs/index.d.ts +1 -0
  20. package/dist/nodejs/tools/command_execute.d.ts +12 -0
  21. package/dist/nodejs/tools/file_read.d.ts +11 -0
  22. package/dist/nodejs/tools/file_write.d.ts +15 -0
  23. package/dist/nodejs/tools/index.d.ts +3 -1
  24. package/dist/nodejs.cjs.js +227 -3
  25. package/dist/nodejs.esm.js +226 -3
  26. package/dist/schemas/workflow.schema.d.ts +3 -0
  27. package/dist/services/llm/claude-provider.d.ts +1 -0
  28. package/dist/services/llm/openai-provider.d.ts +1 -0
  29. package/dist/types/action.types.d.ts +2 -0
  30. package/dist/web/core.d.ts +2 -0
  31. package/dist/web/index.d.ts +1 -0
  32. package/dist/web/script/build_dom_tree.d.ts +10 -0
  33. package/dist/web/tools/browser.d.ts +20 -0
  34. package/dist/web/tools/browser_use.d.ts +19 -0
  35. package/dist/web/tools/element_click.d.ts +12 -0
  36. package/dist/web/tools/export_file.d.ts +18 -0
  37. package/dist/web/tools/extract_content.d.ts +17 -0
  38. package/dist/web/tools/find_element_position.d.ts +12 -0
  39. package/dist/web/tools/html_script.d.ts +10 -0
  40. package/dist/web/tools/index.d.ts +7 -1
  41. package/dist/web/tools/screenshot.d.ts +18 -0
  42. package/dist/web.cjs.js +9377 -3
  43. package/dist/web.esm.js +9376 -3
  44. package/package.json +2 -1
package/dist/index.esm.js CHANGED
@@ -165,9 +165,10 @@ function createReturnTool(outputSchema) {
165
165
  }
166
166
  class ActionImpl {
167
167
  constructor(type, // Only support prompt type
168
- name, tools, llmProvider, llmConfig, config) {
168
+ name, description, tools, llmProvider, llmConfig, config) {
169
169
  this.type = type;
170
170
  this.name = name;
171
+ this.description = description;
171
172
  this.tools = tools;
172
173
  this.llmProvider = llmProvider;
173
174
  this.llmConfig = llmConfig;
@@ -191,7 +192,6 @@ class ActionImpl {
191
192
  const handler = {
192
193
  onContent: (content) => {
193
194
  if (content.trim()) {
194
- console.log('LLM:', content);
195
195
  assistantTextMessage += content;
196
196
  }
197
197
  },
@@ -309,12 +309,10 @@ class ActionImpl {
309
309
  (_a = context.tools) === null || _a === void 0 ? void 0 : _a.forEach((tool) => toolMap.set(tool.name, tool));
310
310
  toolMap.set(returnTool.name, returnTool);
311
311
  // Prepare initial messages
312
- const messages = input && Object.keys(input).length > 0
313
- ? [
314
- { role: 'system', content: this.formatSystemPrompt(context) },
315
- { role: 'user', content: this.formatUserPrompt(input) },
316
- ]
317
- : [{ role: 'user', content: this.formatSystemPrompt(context) }];
312
+ const messages = [
313
+ { role: 'system', content: this.formatSystemPrompt() },
314
+ { role: 'user', content: this.formatUserPrompt(context, input) },
315
+ ];
318
316
  console.log('Starting LLM conversation...');
319
317
  console.log('Initial messages:', messages);
320
318
  console.log('Output schema:', outputSchema);
@@ -339,6 +337,7 @@ class ActionImpl {
339
337
  if (!hasToolUse && response) {
340
338
  // LLM sent a message without using tools - request explicit return
341
339
  console.log('No tool use detected, requesting explicit return');
340
+ console.log('Response:', response);
342
341
  const returnOnlyParams = {
343
342
  ...params,
344
343
  tools: [
@@ -391,32 +390,30 @@ class ActionImpl {
391
390
  }
392
391
  return output;
393
392
  }
394
- formatSystemPrompt(context) {
393
+ formatSystemPrompt() {
394
+ return `You are a task executor. You need to complete the task specified by the user, using the tools provided. When you need to store results or outputs, use the write_context tool. When you are ready to return the final output, use the return_output tool.
395
+
396
+ Remember to:
397
+ 1. Use tools when needed to accomplish the task
398
+ 2. Store important results using write_context, including intermediate and final results
399
+ 3. Think step by step about what needs to be done`;
400
+ }
401
+ formatUserPrompt(context, input) {
395
402
  // Create a description of the current context
396
403
  const contextDescription = Array.from(context.variables.entries())
397
404
  .map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
398
405
  .join('\n');
399
- return `You are executing the action "${this.name}". You have access to the following context:
400
-
401
- ${contextDescription || 'No context variables set'}
406
+ return `You are executing the action "${this.name}". The specific instructions are: "${this.description}". You have access to the following context:
402
407
 
403
- You can use the provided tools to accomplish your task. When you need to store results or outputs,
404
- use the write_context tool to save them to the workflow context.
408
+ ${contextDescription || 'No context variables set'}
405
409
 
406
- Remember to:
407
- 1. Use tools when needed to accomplish the task
408
- 2. Store important results using write_context
409
- 3. Think step by step about what needs to be done`;
410
- }
411
- formatUserPrompt(input) {
412
- if (typeof input === 'string') {
413
- return input;
414
- }
415
- return JSON.stringify(input, null, 2);
410
+ You have been provided with the following input:
411
+ ${(typeof input === 'string' ? input : JSON.stringify(input, null, 2)) || 'No additional input provided'}
412
+ `;
416
413
  }
417
414
  // Static factory method
418
- static createPromptAction(name, tools, llmProvider, llmConfig) {
419
- return new ActionImpl('prompt', name, tools, llmProvider, llmConfig);
415
+ static createPromptAction(name, description, tools, llmProvider, llmConfig) {
416
+ return new ActionImpl('prompt', name, description, tools, llmProvider, llmConfig);
420
417
  }
421
418
  }
422
419
 
@@ -440,7 +437,8 @@ Generate a complete workflow that:
440
437
  1. Only uses the tools listed above
441
438
  2. Properly sequences tool usage based on dependencies
442
439
  3. Ensures each action has appropriate input/output schemas
443
- 4. Creates a clear, logical flow to accomplish the user's goal`;
440
+ 4. Creates a clear, logical flow to accomplish the user's goal
441
+ 5. Includes detailed descriptions for each action, ensuring that the actions, when combined, is a complete solution to the user's problem`;
444
442
  },
445
443
  formatUserPrompt: (requirement) => `Create a workflow for the following requirement: ${requirement}`,
446
444
  };
@@ -560,7 +558,7 @@ class WorkflowGenerator {
560
558
  if (Array.isArray(data.nodes)) {
561
559
  data.nodes.forEach((nodeData) => {
562
560
  const tools = nodeData.action.tools.map((toolName) => this.toolRegistry.getTool(toolName));
563
- const action = ActionImpl.createPromptAction(nodeData.action.name, tools, this.llmProvider, { maxTokens: 1000 });
561
+ const action = ActionImpl.createPromptAction(nodeData.action.name, nodeData.action.description, tools, this.llmProvider, { maxTokens: 1000 });
564
562
  const node = {
565
563
  id: nodeData.id,
566
564
  name: nodeData.name || nodeData.id,
@@ -3282,16 +3280,21 @@ Anthropic.ModelInfosPage = ModelInfosPage;
3282
3280
  Anthropic.Beta = Beta$1;
3283
3281
 
3284
3282
  class ClaudeProvider {
3285
- constructor(apiKey, defaultModel, options) {
3283
+ constructor(param, defaultModel, options) {
3286
3284
  this.defaultModel = 'claude-3-5-sonnet-20241022';
3287
3285
  if (defaultModel) {
3288
3286
  this.defaultModel = defaultModel;
3289
3287
  }
3290
- this.client = new Anthropic({
3291
- apiKey: apiKey,
3292
- dangerouslyAllowBrowser: true,
3293
- ...options,
3294
- });
3288
+ if (typeof param == 'string') {
3289
+ this.client = new Anthropic({
3290
+ apiKey: param,
3291
+ dangerouslyAllowBrowser: true,
3292
+ ...options,
3293
+ });
3294
+ }
3295
+ else {
3296
+ this.client = new Anthropic(param);
3297
+ }
3295
3298
  }
3296
3299
  processResponse(response) {
3297
3300
  const toolCalls = response.content
@@ -3385,7 +3388,7 @@ class ClaudeProvider {
3385
3388
  const toolCall = {
3386
3389
  id: currentToolUse.id,
3387
3390
  name: currentToolUse.name,
3388
- input: JSON.parse(currentToolUse.accumulatedJson),
3391
+ input: JSON.parse(currentToolUse.accumulatedJson || '{}'),
3389
3392
  };
3390
3393
  (_d = handler.onToolUse) === null || _d === void 0 ? void 0 : _d.call(handler, toolCall);
3391
3394
  currentToolUse = null;
@@ -8684,16 +8687,21 @@ OpenAI.BatchesPage = BatchesPage;
8684
8687
  OpenAI.Uploads = Uploads;
8685
8688
 
8686
8689
  class OpenaiProvider {
8687
- constructor(apiKey, defaultModel, options) {
8690
+ constructor(param, defaultModel, options) {
8688
8691
  this.defaultModel = 'gpt-4o';
8689
8692
  if (defaultModel) {
8690
8693
  this.defaultModel = defaultModel;
8691
8694
  }
8692
- this.client = new OpenAI({
8693
- apiKey,
8694
- dangerouslyAllowBrowser: true,
8695
- ...options,
8696
- });
8695
+ if (typeof param == 'string') {
8696
+ this.client = new OpenAI({
8697
+ apiKey: param,
8698
+ dangerouslyAllowBrowser: true,
8699
+ ...options,
8700
+ });
8701
+ }
8702
+ else {
8703
+ this.client = new OpenAI(param);
8704
+ }
8697
8705
  }
8698
8706
  buildParams(messages, params, stream) {
8699
8707
  let tools = undefined;
@@ -8971,7 +8979,7 @@ const workflowSchema = {
8971
8979
  },
8972
8980
  action: {
8973
8981
  type: "object",
8974
- required: ["type", "name"],
8982
+ required: ["type", "name", "description"],
8975
8983
  properties: {
8976
8984
  type: {
8977
8985
  type: "string",
@@ -8979,6 +8987,7 @@ const workflowSchema = {
8979
8987
  enum: ["prompt"],
8980
8988
  },
8981
8989
  name: { type: "string" },
8990
+ description: { type: "string" },
8982
8991
  params: { type: "object" },
8983
8992
  tools: {
8984
8993
  type: "array",
@@ -9002,16 +9011,10 @@ class ToolRegistry {
9002
9011
  this.tools = new Map();
9003
9012
  }
9004
9013
  registerTool(tool) {
9005
- if (this.tools.has(tool.name)) {
9006
- throw new Error(`Tool with name ${tool.name} already registered`);
9007
- }
9008
9014
  this.tools.set(tool.name, tool);
9009
9015
  }
9010
9016
  unregisterTool(toolName) {
9011
- if (!this.tools.has(toolName)) {
9012
- throw new Error(`Tool with name ${toolName} not found`);
9013
- }
9014
- this.tools.delete(toolName);
9017
+ return this.tools.delete(toolName);
9015
9018
  }
9016
9019
  getTool(toolName) {
9017
9020
  const tool = this.tools.get(toolName);
@@ -9077,6 +9080,7 @@ class Eko {
9077
9080
  else {
9078
9081
  this.llmProvider = config;
9079
9082
  }
9083
+ Eko.tools.forEach((tool) => this.toolRegistry.registerTool(tool));
9080
9084
  }
9081
9085
  async generateWorkflow(prompt, param) {
9082
9086
  let toolRegistry = this.toolRegistry;
@@ -9103,6 +9107,9 @@ class Eko {
9103
9107
  if (this.toolRegistry.hasTools([toolName])) {
9104
9108
  tool = this.toolRegistry.getTool(toolName);
9105
9109
  }
9110
+ else if (Eko.tools.has(toolName)) {
9111
+ tool = Eko.tools.get(toolName);
9112
+ }
9106
9113
  else {
9107
9114
  throw new Error(`Tool with name ${toolName} not found`);
9108
9115
  }
@@ -9131,6 +9138,7 @@ class Eko {
9131
9138
  this.toolRegistry.unregisterTool(toolName);
9132
9139
  }
9133
9140
  }
9141
+ Eko.tools = new Map();
9134
9142
 
9135
9143
  class WorkflowParser {
9136
9144
  /**
@@ -9289,6 +9297,7 @@ class WorkflowParser {
9289
9297
  action: {
9290
9298
  type: nodeJson.action.type,
9291
9299
  name: nodeJson.action.name,
9300
+ description: nodeJson.action.description,
9292
9301
  tools: nodeJson.action.tools || [],
9293
9302
  execute: async (input, context) => {
9294
9303
  // Default implementation - should be overridden by specific action types
@@ -9319,6 +9328,7 @@ class WorkflowParser {
9319
9328
  action: {
9320
9329
  type: node.action.type,
9321
9330
  name: node.action.name,
9331
+ description: node.action.description,
9322
9332
  tools: node.action.tools,
9323
9333
  },
9324
9334
  })),
@@ -3,18 +3,19 @@ import { LLMProvider, LLMParameters } from '../types/llm.types';
3
3
  export declare class ActionImpl implements Action {
4
4
  type: 'prompt';
5
5
  name: string;
6
+ description: string;
6
7
  tools: Tool<any, any>[];
7
8
  private llmProvider;
8
9
  private llmConfig?;
9
10
  private readonly maxRounds;
10
11
  private writeContextTool;
11
12
  constructor(type: 'prompt', // Only support prompt type
12
- name: string, tools: Tool<any, any>[], llmProvider: LLMProvider, llmConfig?: LLMParameters | undefined, config?: {
13
+ name: string, description: string, tools: Tool<any, any>[], llmProvider: LLMProvider, llmConfig?: LLMParameters | undefined, config?: {
13
14
  maxRounds?: number;
14
15
  });
15
16
  private executeSingleRound;
16
17
  execute(input: unknown, context: ExecutionContext, outputSchema?: unknown): Promise<unknown>;
17
18
  private formatSystemPrompt;
18
19
  private formatUserPrompt;
19
- static createPromptAction(name: string, tools: Tool<any, any>[], llmProvider: LLMProvider, llmConfig?: LLMParameters): Action;
20
+ static createPromptAction(name: string, description: string, tools: Tool<any, any>[], llmProvider: LLMProvider, llmConfig?: LLMParameters): Action;
20
21
  }
@@ -0,0 +1,2 @@
1
+ import { Tool } from '../types';
2
+ export declare function getAllTools(): Map<string, Tool<any, any>>;
@@ -1,2 +1,3 @@
1
+ export * from './core';
1
2
  import * as tools from './tools';
2
3
  export { tools };
@@ -0,0 +1,12 @@
1
+ import { Tool, InputSchema, ExecutionContext } from '../../types/action.types';
2
+ export interface CommandExecuteParams {
3
+ command: string;
4
+ cwd?: string;
5
+ }
6
+ export declare class CommandExecute implements Tool<CommandExecuteParams, any> {
7
+ name: string;
8
+ description: string;
9
+ input_schema: InputSchema;
10
+ private getUserConfirmation;
11
+ execute(context: ExecutionContext, params: CommandExecuteParams): Promise<any>;
12
+ }
@@ -0,0 +1,11 @@
1
+ import { Tool, InputSchema, ExecutionContext } from '../../types/action.types';
2
+ export interface FileReadParams {
3
+ path: string;
4
+ encoding?: BufferEncoding;
5
+ }
6
+ export declare class FileRead implements Tool<FileReadParams, any> {
7
+ name: string;
8
+ description: string;
9
+ input_schema: InputSchema;
10
+ execute(context: ExecutionContext, params: FileReadParams): Promise<any>;
11
+ }
@@ -0,0 +1,15 @@
1
+ import { Tool, InputSchema, ExecutionContext } from '../../types/action.types';
2
+ export interface FileWriteParams {
3
+ path: string;
4
+ content: string;
5
+ append?: boolean;
6
+ encoding?: BufferEncoding;
7
+ }
8
+ export declare class FileWrite implements Tool<FileWriteParams, any> {
9
+ name: string;
10
+ description: string;
11
+ input_schema: InputSchema;
12
+ private checkFileExists;
13
+ private getUserConfirmation;
14
+ execute(context: ExecutionContext, params: FileWriteParams): Promise<any>;
15
+ }
@@ -1 +1,3 @@
1
- export {};
1
+ export { CommandExecute } from './command_execute';
2
+ export { FileRead } from './file_read';
3
+ export { FileWrite } from './file_write';
@@ -1,7 +1,231 @@
1
1
  'use strict';
2
2
 
3
- var index = /*#__PURE__*/Object.freeze({
4
- __proto__: null
3
+ var readline = require('readline');
4
+ var child_process = require('child_process');
5
+ var util = require('util');
6
+ var promises = require('fs/promises');
7
+ var path = require('path');
8
+ var fs = require('fs');
9
+
10
+ const execAsync = util.promisify(child_process.exec);
11
+ class CommandExecute {
12
+ constructor() {
13
+ this.name = 'command_execute';
14
+ this.description = 'Execute a shell command with user confirmation';
15
+ this.input_schema = {
16
+ type: 'object',
17
+ properties: {
18
+ command: {
19
+ type: 'string',
20
+ description: 'The command to execute. Ensure that the command is non-interactive and does not require user input.'
21
+ },
22
+ cwd: {
23
+ type: 'string',
24
+ description: 'Working directory for command execution'
25
+ }
26
+ },
27
+ required: ['command']
28
+ };
29
+ }
30
+ async getUserConfirmation(command) {
31
+ const rl = readline.createInterface({
32
+ input: process.stdin,
33
+ output: process.stdout
34
+ });
35
+ return new Promise(resolve => {
36
+ rl.question(`Are you sure you want to execute command: "${command}"? (y/N) `, answer => {
37
+ rl.close();
38
+ resolve(answer.toLowerCase() === 'y');
39
+ });
40
+ });
41
+ }
42
+ async execute(context, params) {
43
+ const confirmed = await this.getUserConfirmation(params.command);
44
+ if (!confirmed) {
45
+ return {
46
+ executed: false,
47
+ reason: 'User cancelled execution'
48
+ };
49
+ }
50
+ try {
51
+ const { stdout, stderr } = await execAsync(params.command, {
52
+ cwd: params.cwd
53
+ });
54
+ return {
55
+ executed: true,
56
+ stdout,
57
+ stderr
58
+ };
59
+ }
60
+ catch (error) {
61
+ const err = error;
62
+ return {
63
+ executed: false,
64
+ error: err.message,
65
+ code: err.code,
66
+ stderr: err.stderr
67
+ };
68
+ }
69
+ }
70
+ }
71
+
72
+ class FileRead {
73
+ constructor() {
74
+ this.name = 'file_read';
75
+ this.description = 'Read content from a file';
76
+ this.input_schema = {
77
+ type: 'object',
78
+ properties: {
79
+ path: {
80
+ type: 'string',
81
+ description: 'Path to the file to read'
82
+ },
83
+ encoding: {
84
+ type: 'string',
85
+ description: 'File encoding (default: utf8)',
86
+ enum: ['utf8', 'ascii', 'utf16le', 'base64', 'binary']
87
+ }
88
+ },
89
+ required: ['path']
90
+ };
91
+ }
92
+ async execute(context, params) {
93
+ try {
94
+ const fullPath = path.resolve(params.path);
95
+ const content = await promises.readFile(fullPath, {
96
+ encoding: params.encoding || 'utf8'
97
+ });
98
+ return {
99
+ success: true,
100
+ path: fullPath,
101
+ content
102
+ };
103
+ }
104
+ catch (error) {
105
+ const err = error;
106
+ return {
107
+ success: false,
108
+ error: err.message,
109
+ code: err.code
110
+ };
111
+ }
112
+ }
113
+ }
114
+
115
+ class FileWrite {
116
+ constructor() {
117
+ this.name = 'file_write';
118
+ this.description = 'Write content to a file with user confirmation';
119
+ this.input_schema = {
120
+ type: 'object',
121
+ properties: {
122
+ path: {
123
+ type: 'string',
124
+ description: 'Path to write the file'
125
+ },
126
+ content: {
127
+ type: 'string',
128
+ description: 'Content to write to the file'
129
+ },
130
+ append: {
131
+ type: 'boolean',
132
+ description: 'Whether to append to existing file (default: false)'
133
+ },
134
+ encoding: {
135
+ type: 'string',
136
+ description: 'File encoding (default: utf8)',
137
+ enum: ['utf8', 'ascii', 'utf16le', 'base64', 'binary']
138
+ }
139
+ },
140
+ required: ['path', 'content']
141
+ };
142
+ }
143
+ async checkFileExists(path) {
144
+ try {
145
+ await promises.access(path, fs.constants.F_OK);
146
+ return true;
147
+ }
148
+ catch (_a) {
149
+ return false;
150
+ }
151
+ }
152
+ async getUserConfirmation(path, exists, append) {
153
+ const rl = readline.createInterface({
154
+ input: process.stdin,
155
+ output: process.stdout
156
+ });
157
+ const action = exists
158
+ ? (append ? 'append to' : 'overwrite')
159
+ : 'create';
160
+ return new Promise(resolve => {
161
+ rl.question(`Are you sure you want to ${action} file at "${path}"? (y/N) `, answer => {
162
+ rl.close();
163
+ resolve(answer.toLowerCase() === 'y');
164
+ });
165
+ });
166
+ }
167
+ async execute(context, params) {
168
+ try {
169
+ const fullPath = path.resolve(params.path);
170
+ const exists = await this.checkFileExists(fullPath);
171
+ const append = params.append || false;
172
+ const confirmed = await this.getUserConfirmation(fullPath, exists, append);
173
+ if (!confirmed) {
174
+ return {
175
+ success: false,
176
+ reason: 'User cancelled operation'
177
+ };
178
+ }
179
+ if (append) {
180
+ await promises.appendFile(fullPath, params.content, {
181
+ encoding: params.encoding || 'utf8'
182
+ });
183
+ }
184
+ else {
185
+ await promises.writeFile(fullPath, params.content, {
186
+ encoding: params.encoding || 'utf8'
187
+ });
188
+ }
189
+ return {
190
+ success: true,
191
+ path: fullPath,
192
+ action: append ? 'append' : 'write'
193
+ };
194
+ }
195
+ catch (error) {
196
+ const err = error;
197
+ return {
198
+ success: false,
199
+ error: err.message,
200
+ code: err.code
201
+ };
202
+ }
203
+ }
204
+ }
205
+
206
+ var tools = /*#__PURE__*/Object.freeze({
207
+ __proto__: null,
208
+ CommandExecute: CommandExecute,
209
+ FileRead: FileRead,
210
+ FileWrite: FileWrite
5
211
  });
6
212
 
7
- exports.tools = index;
213
+ function getAllTools() {
214
+ let toolsMap = new Map();
215
+ for (const key in tools) {
216
+ let tool = tools[key];
217
+ if (typeof tool === 'function' && tool.prototype && 'execute' in tool.prototype) {
218
+ try {
219
+ let instance = new tool();
220
+ toolsMap.set(instance.name || key, instance);
221
+ }
222
+ catch (e) {
223
+ console.error(`Failed to instantiate ${key}:`, e);
224
+ }
225
+ }
226
+ }
227
+ return toolsMap;
228
+ }
229
+
230
+ exports.getAllTools = getAllTools;
231
+ exports.tools = tools;