@agents-at-scale/ark 0.1.34 → 0.1.35-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/arkServices.d.ts +50 -0
  2. package/dist/arkServices.js +153 -0
  3. package/dist/charts/charts.d.ts +5 -0
  4. package/dist/charts/charts.js +6 -0
  5. package/dist/charts/dependencies.d.ts +6 -0
  6. package/dist/charts/dependencies.js +50 -0
  7. package/dist/charts/types.d.ts +40 -0
  8. package/dist/charts/types.js +1 -0
  9. package/dist/commands/agents/index.d.ts +2 -0
  10. package/dist/commands/agents/index.js +56 -0
  11. package/dist/commands/agents/selector.d.ts +8 -0
  12. package/dist/commands/agents/selector.js +53 -0
  13. package/dist/commands/agents.d.ts +2 -0
  14. package/dist/commands/agents.js +53 -0
  15. package/dist/commands/chat/index.d.ts +2 -0
  16. package/dist/commands/chat/index.js +45 -0
  17. package/dist/commands/chat.d.ts +2 -0
  18. package/dist/commands/chat.js +45 -0
  19. package/dist/commands/cluster/get.d.ts +2 -0
  20. package/dist/commands/cluster/get.js +39 -0
  21. package/dist/commands/cluster/index.js +2 -4
  22. package/dist/commands/completion/index.d.ts +2 -0
  23. package/dist/commands/completion/index.js +268 -0
  24. package/dist/commands/completion.js +159 -2
  25. package/dist/commands/config/index.d.ts +2 -0
  26. package/dist/commands/config/index.js +42 -0
  27. package/dist/commands/config.d.ts +0 -3
  28. package/dist/commands/config.js +38 -321
  29. package/dist/commands/dashboard/index.d.ts +3 -0
  30. package/dist/commands/dashboard/index.js +39 -0
  31. package/dist/commands/dashboard.d.ts +3 -0
  32. package/dist/commands/dashboard.js +39 -0
  33. package/dist/commands/dev/index.d.ts +2 -0
  34. package/dist/commands/dev/index.js +9 -0
  35. package/dist/commands/dev/tool/check.d.ts +2 -0
  36. package/dist/commands/dev/tool/check.js +142 -0
  37. package/dist/commands/dev/tool/clean.d.ts +2 -0
  38. package/dist/commands/dev/tool/clean.js +153 -0
  39. package/dist/commands/dev/tool/generate.d.ts +2 -0
  40. package/dist/commands/dev/tool/generate.js +28 -0
  41. package/dist/commands/dev/tool/index.d.ts +2 -0
  42. package/dist/commands/dev/tool/index.js +14 -0
  43. package/dist/commands/dev/tool/init.d.ts +2 -0
  44. package/dist/commands/dev/tool/init.js +320 -0
  45. package/dist/commands/dev/tool/shared.d.ts +5 -0
  46. package/dist/commands/dev/tool/shared.js +256 -0
  47. package/dist/commands/dev/tool/status.d.ts +2 -0
  48. package/dist/commands/dev/tool/status.js +136 -0
  49. package/dist/commands/dev/tool.d.ts +2 -0
  50. package/dist/commands/dev/tool.js +559 -0
  51. package/dist/commands/generate/config.js +5 -24
  52. package/dist/commands/generate/generators/mcpserver.d.ts +2 -1
  53. package/dist/commands/generate/generators/mcpserver.js +26 -5
  54. package/dist/commands/install/index.d.ts +6 -0
  55. package/dist/commands/install/index.js +165 -0
  56. package/dist/commands/install.d.ts +3 -0
  57. package/dist/commands/install.js +147 -0
  58. package/dist/commands/models/create.d.ts +1 -0
  59. package/dist/commands/models/create.js +213 -0
  60. package/dist/commands/models/index.d.ts +2 -0
  61. package/dist/commands/models/index.js +65 -0
  62. package/dist/commands/models/selector.d.ts +8 -0
  63. package/dist/commands/models/selector.js +53 -0
  64. package/dist/commands/routes/index.d.ts +2 -0
  65. package/dist/commands/routes/index.js +101 -0
  66. package/dist/commands/routes.d.ts +2 -0
  67. package/dist/commands/routes.js +101 -0
  68. package/dist/commands/status/index.d.ts +3 -0
  69. package/dist/commands/status/index.js +33 -0
  70. package/dist/commands/status.d.ts +3 -0
  71. package/dist/commands/status.js +33 -0
  72. package/dist/commands/targets/index.d.ts +2 -0
  73. package/dist/commands/targets/index.js +65 -0
  74. package/dist/commands/targets.d.ts +2 -0
  75. package/dist/commands/targets.js +65 -0
  76. package/dist/commands/teams/index.d.ts +2 -0
  77. package/dist/commands/teams/index.js +54 -0
  78. package/dist/commands/teams/selector.d.ts +8 -0
  79. package/dist/commands/teams/selector.js +55 -0
  80. package/dist/commands/tools/index.d.ts +2 -0
  81. package/dist/commands/tools/index.js +54 -0
  82. package/dist/commands/tools/selector.d.ts +8 -0
  83. package/dist/commands/tools/selector.js +53 -0
  84. package/dist/commands/uninstall/index.d.ts +2 -0
  85. package/dist/commands/uninstall/index.js +84 -0
  86. package/dist/commands/uninstall.d.ts +2 -0
  87. package/dist/commands/uninstall.js +83 -0
  88. package/dist/components/ChatUI.d.ts +16 -0
  89. package/dist/components/ChatUI.js +801 -0
  90. package/dist/components/StatusView.d.ts +10 -0
  91. package/dist/components/StatusView.js +39 -0
  92. package/dist/components/statusChecker.d.ts +10 -13
  93. package/dist/components/statusChecker.js +128 -65
  94. package/dist/config.js +3 -10
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.js +31 -36
  97. package/dist/lib/arkApiClient.d.ts +53 -0
  98. package/dist/lib/arkApiClient.js +102 -0
  99. package/dist/lib/arkApiProxy.d.ts +9 -0
  100. package/dist/lib/arkApiProxy.js +22 -0
  101. package/dist/lib/arkServiceProxy.d.ts +14 -0
  102. package/dist/lib/arkServiceProxy.js +93 -0
  103. package/dist/lib/arkStatus.d.ts +5 -0
  104. package/dist/lib/arkStatus.js +20 -0
  105. package/dist/lib/chatClient.d.ts +33 -0
  106. package/dist/lib/chatClient.js +101 -0
  107. package/dist/lib/cluster.d.ts +2 -1
  108. package/dist/lib/cluster.js +27 -3
  109. package/dist/lib/commandUtils.d.ts +4 -0
  110. package/dist/lib/commandUtils.js +18 -0
  111. package/dist/lib/commandUtils.test.d.ts +1 -0
  112. package/dist/lib/commandUtils.test.js +44 -0
  113. package/dist/lib/config.d.ts +24 -80
  114. package/dist/lib/config.js +68 -205
  115. package/dist/lib/config.test.d.ts +1 -0
  116. package/dist/lib/config.test.js +93 -0
  117. package/dist/lib/dev/tools/analyzer.d.ts +30 -0
  118. package/dist/lib/dev/tools/analyzer.js +190 -0
  119. package/dist/lib/dev/tools/discover_tools.py +392 -0
  120. package/dist/lib/dev/tools/mcp-types.d.ts +28 -0
  121. package/dist/lib/dev/tools/mcp-types.js +86 -0
  122. package/dist/lib/dev/tools/types.d.ts +50 -0
  123. package/dist/lib/dev/tools/types.js +1 -0
  124. package/dist/lib/output.d.ts +36 -0
  125. package/dist/lib/output.js +89 -0
  126. package/dist/lib/types.d.ts +8 -3
  127. package/dist/types/types.d.ts +40 -0
  128. package/dist/types/types.js +1 -0
  129. package/dist/ui/MainMenu.js +158 -90
  130. package/dist/ui/statusFormatter.d.ts +4 -1
  131. package/dist/ui/statusFormatter.js +91 -19
  132. package/package.json +16 -4
@@ -0,0 +1,392 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Discover MCP tools in Python files using static analysis.
4
+ Uses only Python stdlib - no external dependencies required.
5
+ """
6
+
7
+ import ast
8
+ import json
9
+ import sys
10
+ import os
11
+ from typing import Dict, List, Any
12
+
13
+
14
+ class MCPToolDiscoverer(ast.NodeVisitor):
15
+ """AST visitor to find MCP tool definitions"""
16
+
17
+ def __init__(self):
18
+ self.tools = []
19
+ self.mcp_var_name = None
20
+ self.imports = {}
21
+ self.current_function = None # Track if we're inside a function
22
+
23
+ def visit_ImportFrom(self, node):
24
+ """Track imports to identify FastMCP usage"""
25
+ if node.module == 'fastmcp':
26
+ for alias in node.names:
27
+ if alias.name == 'FastMCP':
28
+ self.imports['FastMCP'] = alias.asname or alias.name
29
+ self.generic_visit(node)
30
+
31
+ def visit_Assign(self, node):
32
+ """Find mcp = FastMCP(...) assignments"""
33
+ if len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
34
+ var_name = node.targets[0].id
35
+ if isinstance(node.value, ast.Call):
36
+ if isinstance(node.value.func, ast.Name):
37
+ if node.value.func.id in self.imports.values():
38
+ self.mcp_var_name = var_name
39
+ # Extract server name if provided
40
+ if node.value.args:
41
+ if isinstance(node.value.args[0], ast.Constant):
42
+ self.server_name = node.value.args[0].value
43
+ self.generic_visit(node)
44
+
45
+ def visit_FunctionDef(self, node):
46
+ """Find functions decorated with @mcp.tool() - both at module level and nested"""
47
+ # Save the parent function context
48
+ parent_function = self.current_function
49
+ self.current_function = node.name
50
+
51
+ # Check if this function is decorated as a tool
52
+ for decorator in node.decorator_list:
53
+ is_mcp_tool = False
54
+ tool_config = {}
55
+
56
+ # Check for @mcp.tool() or @mcp.tool
57
+ if isinstance(decorator, ast.Call):
58
+ if isinstance(decorator.func, ast.Attribute):
59
+ # Check for mcp.tool where mcp is a variable
60
+ if (isinstance(decorator.func.value, ast.Name) and
61
+ decorator.func.value.id == self.mcp_var_name and
62
+ decorator.func.attr == 'tool'):
63
+ is_mcp_tool = True
64
+ # Extract any config from @mcp.tool(name="...", description="...")
65
+ for keyword in decorator.keywords:
66
+ if isinstance(keyword.value, ast.Constant):
67
+ tool_config[keyword.arg] = keyword.value.value
68
+ elif isinstance(decorator, ast.Attribute):
69
+ # Check for @mcp.tool without parentheses
70
+ if (isinstance(decorator.value, ast.Name) and
71
+ decorator.value.id == self.mcp_var_name and
72
+ decorator.attr == 'tool'):
73
+ is_mcp_tool = True
74
+ # Also check for @mcp.tool where mcp is a parameter (e.g., in register_tools(mcp))
75
+ elif (decorator.value.id if isinstance(decorator.value, ast.Name) else None) == 'mcp' and \
76
+ decorator.attr == 'tool':
77
+ is_mcp_tool = True
78
+
79
+ if is_mcp_tool:
80
+ tool_info = self.extract_function_info(node)
81
+ tool_info.update(tool_config)
82
+ if parent_function:
83
+ tool_info['registered_in'] = parent_function
84
+ self.tools.append(tool_info)
85
+
86
+ # Visit nested functions to find tools defined inside this function
87
+ self.generic_visit(node)
88
+
89
+ # Restore parent function context
90
+ self.current_function = parent_function
91
+
92
+ def visit_AsyncFunctionDef(self, node):
93
+ """Handle async functions the same way as regular functions"""
94
+ # Save the parent function context
95
+ parent_function = self.current_function
96
+ self.current_function = node.name
97
+
98
+ # Check if this async function is decorated as a tool
99
+ for decorator in node.decorator_list:
100
+ is_mcp_tool = False
101
+ tool_config = {}
102
+
103
+ # Check for @mcp.tool() or @mcp.tool
104
+ if isinstance(decorator, ast.Call):
105
+ if isinstance(decorator.func, ast.Attribute):
106
+ if (isinstance(decorator.func.value, ast.Name) and
107
+ decorator.func.attr == 'tool'):
108
+ # Could be mcp.tool where mcp is the var or a parameter
109
+ if decorator.func.value.id == self.mcp_var_name or decorator.func.value.id == 'mcp':
110
+ is_mcp_tool = True
111
+ for keyword in decorator.keywords:
112
+ if isinstance(keyword.value, ast.Constant):
113
+ tool_config[keyword.arg] = keyword.value.value
114
+ elif isinstance(decorator, ast.Attribute):
115
+ if decorator.attr == 'tool':
116
+ # Could be @mcp.tool where mcp is the var or a parameter
117
+ if isinstance(decorator.value, ast.Name):
118
+ if decorator.value.id == self.mcp_var_name or decorator.value.id == 'mcp':
119
+ is_mcp_tool = True
120
+
121
+ if is_mcp_tool:
122
+ tool_info = self.extract_function_info(node)
123
+ tool_info.update(tool_config)
124
+ if parent_function:
125
+ tool_info['registered_in'] = parent_function
126
+ self.tools.append(tool_info)
127
+
128
+ # Visit nested functions
129
+ self.generic_visit(node)
130
+
131
+ # Restore parent function context
132
+ self.current_function = parent_function
133
+
134
+ def extract_function_info(self, node):
135
+ """Extract function name, parameters, and docstring"""
136
+ info = {
137
+ 'name': node.name,
138
+ 'parameters': [],
139
+ 'return_type': None,
140
+ 'docstring': ast.get_docstring(node)
141
+ }
142
+
143
+ # Extract parameters
144
+ for arg in node.args.args:
145
+ param = {'name': arg.arg}
146
+ if arg.annotation:
147
+ param['type'] = ast.unparse(arg.annotation) if hasattr(ast, 'unparse') else self.unparse_annotation(arg.annotation)
148
+ info['parameters'].append(param)
149
+
150
+ # Extract return type
151
+ if node.returns:
152
+ info['return_type'] = ast.unparse(node.returns) if hasattr(ast, 'unparse') else self.unparse_annotation(node.returns)
153
+
154
+ return info
155
+
156
+ def unparse_annotation(self, annotation):
157
+ """Fallback for Python < 3.9 without ast.unparse"""
158
+ if isinstance(annotation, ast.Name):
159
+ return annotation.id
160
+ elif isinstance(annotation, ast.Constant):
161
+ return repr(annotation.value)
162
+ else:
163
+ return 'Any'
164
+
165
+
166
+ def discover_tools_in_file(filepath):
167
+ """Discover MCP tools in a single Python file"""
168
+ try:
169
+ with open(filepath, 'r') as f:
170
+ content = f.read()
171
+
172
+ tree = ast.parse(content)
173
+ discoverer = MCPToolDiscoverer()
174
+ discoverer.visit(tree)
175
+
176
+ return {
177
+ 'success': True,
178
+ 'file': filepath,
179
+ 'tools': discoverer.tools,
180
+ 'uses_fastmcp': bool(discoverer.mcp_var_name),
181
+ 'mcp_instance': discoverer.mcp_var_name,
182
+ 'server_name': getattr(discoverer, 'server_name', None)
183
+ }
184
+ except SyntaxError as e:
185
+ return {
186
+ 'success': False,
187
+ 'file': filepath,
188
+ 'error': f'Syntax error: {str(e)}',
189
+ 'tools': []
190
+ }
191
+ except Exception as e:
192
+ return {
193
+ 'success': False,
194
+ 'file': filepath,
195
+ 'error': str(e),
196
+ 'tools': []
197
+ }
198
+
199
+
200
+ def discover_tools_in_directory(dirpath):
201
+ """Discover MCP tools in Python files in root directory only (no recursion)"""
202
+ results = {
203
+ 'directory': dirpath,
204
+ 'files': [],
205
+ 'total_tools': 0,
206
+ 'uses_fastmcp': False
207
+ }
208
+
209
+ # Only check Python files in the root directory
210
+ for file in os.listdir(dirpath):
211
+ if file.endswith('.py'):
212
+ filepath = os.path.join(dirpath, file)
213
+ if os.path.isfile(filepath):
214
+ file_result = discover_tools_in_file(filepath)
215
+ results['files'].append(file_result)
216
+ results['total_tools'] += len(file_result.get('tools', []))
217
+ if file_result.get('uses_fastmcp'):
218
+ results['uses_fastmcp'] = True
219
+
220
+ return results
221
+
222
+
223
+ def discover_project(dirpath):
224
+ """Discover project configuration and type"""
225
+ result = {
226
+ 'path': dirpath,
227
+ 'exists': os.path.exists(dirpath),
228
+ 'is_directory': os.path.isdir(dirpath) if os.path.exists(dirpath) else False,
229
+ 'platform': None,
230
+ 'project_type': None,
231
+ 'project_file': None,
232
+ 'project_name': None,
233
+ 'project_version': None,
234
+ 'has_fastmcp': False,
235
+ 'fastmcp_version': None
236
+ }
237
+
238
+ if not result['exists']:
239
+ return result
240
+
241
+ if not result['is_directory']:
242
+ return result
243
+
244
+ # Check for Python project files
245
+ pyproject_path = os.path.join(dirpath, 'pyproject.toml')
246
+ requirements_path = os.path.join(dirpath, 'requirements.txt')
247
+
248
+ if os.path.exists(pyproject_path):
249
+ result['platform'] = 'python3'
250
+ result['project_type'] = 'pyproject'
251
+ result['project_file'] = pyproject_path
252
+
253
+ # Parse pyproject.toml
254
+ with open(pyproject_path, 'r') as f:
255
+ content = f.read()
256
+
257
+ # Extract project name and version using basic parsing
258
+ # Look for [project] section
259
+ import re
260
+
261
+ # Try to find name in [project] section
262
+ name_match = re.search(r'^\s*name\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE)
263
+ if name_match:
264
+ result['project_name'] = name_match.group(1)
265
+
266
+ # Try to find version in [project] section
267
+ version_match = re.search(r'^\s*version\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE)
268
+ if version_match:
269
+ result['project_version'] = version_match.group(1)
270
+
271
+ # Check for fastmcp
272
+ if 'fastmcp' in content:
273
+ result['has_fastmcp'] = True
274
+ version_match = re.search(r'fastmcp[>=<~]*([0-9.]+)', content)
275
+ if version_match:
276
+ result['fastmcp_version'] = version_match.group(1)
277
+
278
+ elif os.path.exists(requirements_path):
279
+ result['platform'] = 'python3'
280
+ result['project_type'] = 'requirements'
281
+ result['project_file'] = requirements_path
282
+
283
+ # Check for fastmcp
284
+ with open(requirements_path, 'r') as f:
285
+ content = f.read()
286
+ if 'fastmcp' in content:
287
+ result['has_fastmcp'] = True
288
+ import re
289
+ version_match = re.search(r'fastmcp[>=<~]*([0-9.]+)', content)
290
+ if version_match:
291
+ result['fastmcp_version'] = version_match.group(1)
292
+
293
+ return result
294
+
295
+
296
+ def find_project_tools(project_root):
297
+ """
298
+ Naive recursive search for MCP tools in a Python project.
299
+ Searches all Python files in the project tree, excluding common non-source directories.
300
+
301
+ This is intentionally naive - it doesn't try to be smart about which files to search,
302
+ it just excludes obvious non-source directories and searches everything else.
303
+ """
304
+ # Common directories to exclude from search
305
+ EXCLUDE_DIRS = {
306
+ 'venv', '.venv', 'env', '.env', 'virtualenv',
307
+ 'dist', 'build', '__pycache__', '.eggs', 'egg-info',
308
+ '.git', '.pytest_cache', '.mypy_cache', '.tox', 'htmlcov',
309
+ 'node_modules', '.coverage', 'site-packages',
310
+ # Also exclude hidden directories
311
+ }
312
+
313
+ results = {
314
+ 'project_root': project_root,
315
+ 'files_searched': 0,
316
+ 'files_with_tools': 0,
317
+ 'total_tools': 0,
318
+ 'tools': [],
319
+ 'errors': []
320
+ }
321
+
322
+ for root, dirs, files in os.walk(project_root):
323
+ # Modify dirs in-place to skip excluded directories
324
+ dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS and not d.startswith('.')]
325
+
326
+ # Process Python files in this directory
327
+ for file in files:
328
+ if file.endswith('.py'):
329
+ filepath = os.path.join(root, file)
330
+ results['files_searched'] += 1
331
+
332
+ try:
333
+ file_result = discover_tools_in_file(filepath)
334
+ if file_result.get('success') and file_result.get('tools'):
335
+ results['files_with_tools'] += 1
336
+ for tool in file_result['tools']:
337
+ # Add source file information to each tool
338
+ tool['source_file'] = os.path.relpath(filepath, project_root)
339
+ results['tools'].append(tool)
340
+ results['total_tools'] += 1
341
+ except Exception as e:
342
+ results['errors'].append({
343
+ 'file': os.path.relpath(filepath, project_root),
344
+ 'error': str(e)
345
+ })
346
+
347
+ return results
348
+
349
+
350
+ def main():
351
+ if len(sys.argv) < 2:
352
+ print(json.dumps({
353
+ 'error': 'Usage: discover_tools.py <command> <path>'
354
+ }))
355
+ sys.exit(1)
356
+
357
+ command = sys.argv[1]
358
+
359
+ if command == 'project':
360
+ if len(sys.argv) < 3:
361
+ print(json.dumps({'error': 'Path required for project discovery'}))
362
+ sys.exit(1)
363
+ path = sys.argv[2]
364
+ result = discover_project(path)
365
+ elif command == 'tools':
366
+ if len(sys.argv) < 3:
367
+ print(json.dumps({'error': 'Path required for tool discovery'}))
368
+ sys.exit(1)
369
+ path = sys.argv[2]
370
+ if os.path.isfile(path):
371
+ result = discover_tools_in_file(path)
372
+ elif os.path.isdir(path):
373
+ result = discover_tools_in_directory(path)
374
+ else:
375
+ result = {'error': f'Path not found: {path}'}
376
+ elif command == 'project-tools':
377
+ if len(sys.argv) < 3:
378
+ print(json.dumps({'error': 'Path required for project tools discovery'}))
379
+ sys.exit(1)
380
+ path = sys.argv[2]
381
+ if os.path.isdir(path):
382
+ result = find_project_tools(path)
383
+ else:
384
+ result = {'error': f'Path is not a directory: {path}'}
385
+ else:
386
+ result = {'error': f'Unknown command: {command}'}
387
+
388
+ print(json.dumps(result, indent=2))
389
+
390
+
391
+ if __name__ == '__main__':
392
+ main()
@@ -0,0 +1,28 @@
1
+ /**
2
+ * MCP Tool type utilities for discovered tools
3
+ * Uses the official Model Context Protocol types from @modelcontextprotocol/sdk
4
+ */
5
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
6
+ export type { Tool as MCPTool } from '@modelcontextprotocol/sdk/types.js';
7
+ /**
8
+ * Convert a discovered Python tool to MCP format
9
+ */
10
+ export declare function toMCPTool(discoveredTool: any): Tool;
11
+ /**
12
+ * Format tool for OpenAI-compatible function calling
13
+ */
14
+ export declare function toOpenAIFunction(tool: Tool): {
15
+ type: "function";
16
+ function: {
17
+ name: string;
18
+ description: string | undefined;
19
+ parameters: {
20
+ [x: string]: unknown;
21
+ type: "object";
22
+ properties?: {
23
+ [x: string]: unknown;
24
+ } | undefined;
25
+ required?: string[] | undefined;
26
+ };
27
+ };
28
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * MCP Tool type utilities for discovered tools
3
+ * Uses the official Model Context Protocol types from @modelcontextprotocol/sdk
4
+ */
5
+ /**
6
+ * Convert a discovered Python tool to MCP format
7
+ */
8
+ export function toMCPTool(discoveredTool) {
9
+ // Extract first line of docstring as description
10
+ const description = discoveredTool.docstring
11
+ ? discoveredTool.docstring.split('\n')[0].trim()
12
+ : undefined;
13
+ // Build properties from parameters
14
+ const properties = {};
15
+ const required = [];
16
+ if (discoveredTool.parameters) {
17
+ for (const param of discoveredTool.parameters) {
18
+ // Map Python types to JSON Schema types
19
+ let jsonType = 'string';
20
+ if (param.type) {
21
+ const pythonType = param.type.toLowerCase();
22
+ if (pythonType === 'int' || pythonType === 'float') {
23
+ jsonType = 'number';
24
+ }
25
+ else if (pythonType === 'bool') {
26
+ jsonType = 'boolean';
27
+ }
28
+ else if (pythonType.includes('list') ||
29
+ pythonType.includes('array')) {
30
+ jsonType = 'array';
31
+ }
32
+ else if (pythonType.includes('dict') || pythonType === 'object') {
33
+ jsonType = 'object';
34
+ }
35
+ }
36
+ properties[param.name] = {
37
+ type: jsonType,
38
+ description: `Parameter ${param.name}`,
39
+ };
40
+ // For now, assume all parameters are required
41
+ // Could be enhanced to detect optional parameters
42
+ required.push(param.name);
43
+ }
44
+ }
45
+ // Build the base Tool object
46
+ const tool = {
47
+ name: discoveredTool.name,
48
+ title: toTitleCase(discoveredTool.name),
49
+ description,
50
+ inputSchema: {
51
+ type: 'object',
52
+ properties: Object.keys(properties).length > 0 ? properties : undefined,
53
+ required: required.length > 0 ? required : undefined,
54
+ },
55
+ };
56
+ // Add metadata if available
57
+ if (discoveredTool.source_file) {
58
+ tool.source_file = discoveredTool.source_file;
59
+ }
60
+ if (discoveredTool.registered_in) {
61
+ tool.registered_in = discoveredTool.registered_in;
62
+ }
63
+ return tool;
64
+ }
65
+ /**
66
+ * Convert snake_case to Title Case
67
+ */
68
+ function toTitleCase(snakeCase) {
69
+ return snakeCase
70
+ .split('_')
71
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
72
+ .join(' ');
73
+ }
74
+ /**
75
+ * Format tool for OpenAI-compatible function calling
76
+ */
77
+ export function toOpenAIFunction(tool) {
78
+ return {
79
+ type: 'function',
80
+ function: {
81
+ name: tool.name,
82
+ description: tool.description,
83
+ parameters: tool.inputSchema,
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,50 @@
1
+ export interface ToolParameter {
2
+ name: string;
3
+ type?: string;
4
+ }
5
+ export interface DiscoveredTool {
6
+ name: string;
7
+ parameters: ToolParameter[];
8
+ return_type?: string;
9
+ docstring?: string;
10
+ }
11
+ export interface FileDiscoveryResult {
12
+ success: boolean;
13
+ file: string;
14
+ tools: DiscoveredTool[];
15
+ uses_fastmcp: boolean;
16
+ mcp_instance?: string;
17
+ server_name?: string;
18
+ error?: string;
19
+ }
20
+ export interface DirectoryDiscoveryResult {
21
+ directory: string;
22
+ files: FileDiscoveryResult[];
23
+ total_tools: number;
24
+ uses_fastmcp: boolean;
25
+ }
26
+ export type DiscoveryResult = FileDiscoveryResult | DirectoryDiscoveryResult;
27
+ export interface ProjectDiscoveryResult {
28
+ path: string;
29
+ exists: boolean;
30
+ is_directory: boolean;
31
+ platform: 'python3' | null;
32
+ project_type: 'pyproject' | 'requirements' | null;
33
+ project_file: string | null;
34
+ project_name: string | null;
35
+ project_version: string | null;
36
+ has_fastmcp: boolean;
37
+ fastmcp_version: string | null;
38
+ }
39
+ export interface ProjectInfo {
40
+ path: string;
41
+ platform: 'python3';
42
+ projectType: 'pyproject' | 'requirements' | 'unknown';
43
+ hasVenv: boolean;
44
+ fastMCP: boolean;
45
+ fastMCPVersion?: string;
46
+ }
47
+ export interface ArkDevToolStatus extends ProjectInfo {
48
+ discovery?: DiscoveryResult;
49
+ tools: DiscoveredTool[];
50
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ export type StatusType = 'success' | 'warning' | 'info' | 'error';
2
+ declare const output: {
3
+ /**
4
+ * Display a status message with flexible formatting
5
+ */
6
+ statusMessage(type: StatusType, title: string, message?: string, ...args: unknown[]): void;
7
+ /**
8
+ * Display an error message with consistent formatting
9
+ */
10
+ error(message: string, ...args: unknown[]): void;
11
+ /**
12
+ * Display a success message with consistent formatting
13
+ */
14
+ success(message: string, ...args: unknown[]): void;
15
+ /**
16
+ * Display an info message (indented gray text)
17
+ */
18
+ info(message: string, ...args: unknown[]): void;
19
+ /**
20
+ * Display a warning message with consistent formatting
21
+ */
22
+ warning(message: string, ...args: unknown[]): void;
23
+ /**
24
+ * Display a status check item (like ark status format)
25
+ * @param status - 'found', 'missing', 'warning', 'error'
26
+ * @param label - The label to show (e.g., 'platform')
27
+ * @param value - The value in bright white (e.g., 'python3')
28
+ * @param details - Optional grey details
29
+ */
30
+ statusCheck(status: "found" | "missing" | "warning" | "error", label: string, value?: string, details?: string): void;
31
+ /**
32
+ * Display a section header (like 'ark services:')
33
+ */
34
+ section(title: string): void;
35
+ };
36
+ export default output;
@@ -0,0 +1,89 @@
1
+ import chalk from 'chalk';
2
+ const output = {
3
+ /**
4
+ * Display a status message with flexible formatting
5
+ */
6
+ statusMessage(type, title, message, ...args) {
7
+ const icons = {
8
+ success: chalk.green('✓'),
9
+ warning: chalk.yellow.bold('!'),
10
+ info: chalk.blue('ℹ'),
11
+ error: chalk.red('✗'),
12
+ };
13
+ const colors = {
14
+ success: chalk.green,
15
+ warning: chalk.yellow,
16
+ info: chalk.blue,
17
+ error: chalk.red,
18
+ };
19
+ const icon = icons[type];
20
+ const color = colors[type];
21
+ const logFn = type === 'error' ? console.error : console.log;
22
+ if (message) {
23
+ logFn(icon, color(`${title}:`), message, ...args);
24
+ }
25
+ else {
26
+ logFn(icon, title, ...args);
27
+ }
28
+ },
29
+ /**
30
+ * Display an error message with consistent formatting
31
+ */
32
+ error(message, ...args) {
33
+ this.statusMessage('error', 'error', message, ...args);
34
+ },
35
+ /**
36
+ * Display a success message with consistent formatting
37
+ */
38
+ success(message, ...args) {
39
+ this.statusMessage('success', message, undefined, ...args);
40
+ },
41
+ /**
42
+ * Display an info message (indented gray text)
43
+ */
44
+ info(message, ...args) {
45
+ console.log(chalk.gray(message), ...args);
46
+ },
47
+ /**
48
+ * Display a warning message with consistent formatting
49
+ */
50
+ warning(message, ...args) {
51
+ this.statusMessage('warning', 'warning', message, ...args);
52
+ },
53
+ /**
54
+ * Display a status check item (like ark status format)
55
+ * @param status - 'found', 'missing', 'warning', 'error'
56
+ * @param label - The label to show (e.g., 'platform')
57
+ * @param value - The value in bright white (e.g., 'python3')
58
+ * @param details - Optional grey details
59
+ */
60
+ statusCheck(status, label, value, details) {
61
+ const icons = {
62
+ found: chalk.green('✓'),
63
+ missing: chalk.yellow('?'),
64
+ warning: chalk.yellow('!'),
65
+ error: chalk.red('✗'),
66
+ };
67
+ const statusText = {
68
+ found: chalk.green(label),
69
+ missing: chalk.yellow(label),
70
+ warning: chalk.yellow(label),
71
+ error: chalk.red(label),
72
+ };
73
+ let output = ` ${icons[status]} ${statusText[status]}`;
74
+ if (value) {
75
+ output += ` ${chalk.bold.white(value)}`;
76
+ }
77
+ if (details) {
78
+ output += chalk.gray(` ${details}`);
79
+ }
80
+ console.log(output);
81
+ },
82
+ /**
83
+ * Display a section header (like 'ark services:')
84
+ */
85
+ section(title) {
86
+ console.log(chalk.cyan.bold(`${title}:`));
87
+ },
88
+ };
89
+ export default output;