@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
@@ -1,223 +1,86 @@
1
- /**
2
- * Configuration management for ARK CLI
3
- */
4
1
  import fs from 'fs';
5
2
  import path from 'path';
6
3
  import os from 'os';
7
- import { SecurityUtils } from './security.js';
8
- import { ValidationError } from './errors.js';
9
- export const DEFAULT_CONFIG = {
10
- defaultProjectType: 'with-samples',
11
- defaultDestination: process.cwd(),
12
- skipGitByDefault: false,
13
- skipModelsbyDefault: false,
14
- preferredEditor: process.env.EDITOR || 'code',
15
- colorOutput: true,
16
- verboseOutput: false,
17
- defaultModelProvider: 'azure',
18
- customTemplates: {},
19
- parallelOperations: true,
20
- maxConcurrentFiles: 10,
21
- fileWatchingEnabled: false,
22
- telemetryEnabled: false,
23
- errorReporting: false,
24
- };
25
- export class ConfigManager {
26
- constructor() {
27
- this.configPath = this.getConfigPath();
28
- this.config = this.loadConfig();
29
- }
30
- /**
31
- * Get the path to the configuration file
32
- */
33
- getConfigPath() {
34
- const configDir = process.env.ARK_CONFIG_DIR || path.join(os.homedir(), '.config', 'ark');
35
- // Ensure config directory exists
36
- if (!fs.existsSync(configDir)) {
37
- fs.mkdirSync(configDir, { recursive: true, mode: 0o755 });
38
- }
39
- return path.join(configDir, 'config.json');
40
- }
41
- /**
42
- * Load configuration from file or create with defaults
43
- */
44
- loadConfig() {
4
+ import yaml from 'yaml';
5
+ /**
6
+ * Load configuration from multiple sources with proper precedence:
7
+ * 1. Defaults
8
+ * 2. ~/.arkrc.yaml (user config)
9
+ * 3. .arkrc.yaml (project config)
10
+ * 4. Environment variables (override all)
11
+ */
12
+ export function loadConfig() {
13
+ // Start with defaults
14
+ const config = {
15
+ chat: {
16
+ streaming: true,
17
+ outputFormat: 'text',
18
+ },
19
+ };
20
+ // Load user config from home directory
21
+ const userConfigPath = path.join(os.homedir(), '.arkrc.yaml');
22
+ if (fs.existsSync(userConfigPath)) {
45
23
  try {
46
- if (fs.existsSync(this.configPath)) {
47
- const configContent = fs.readFileSync(this.configPath, 'utf-8');
48
- const userConfig = JSON.parse(configContent);
49
- // Merge with defaults to ensure all properties exist
50
- return { ...DEFAULT_CONFIG, ...userConfig };
51
- }
24
+ const userConfig = yaml.parse(fs.readFileSync(userConfigPath, 'utf-8'));
25
+ mergeConfig(config, userConfig);
52
26
  }
53
- catch (error) {
54
- console.warn(`Warning: Failed to load config from ${this.configPath}: ${error}`);
27
+ catch (_e) {
28
+ // Silently ignore invalid config files
55
29
  }
56
- // Return defaults and save them
57
- this.saveConfig(DEFAULT_CONFIG);
58
- return { ...DEFAULT_CONFIG };
59
30
  }
60
- /**
61
- * Save configuration to file
62
- */
63
- saveConfig(config) {
31
+ // Load project config from current directory
32
+ const projectConfigPath = path.join(process.cwd(), '.arkrc.yaml');
33
+ if (fs.existsSync(projectConfigPath)) {
64
34
  try {
65
- const configContent = JSON.stringify(config, null, 2);
66
- SecurityUtils.validatePath(this.configPath, 'config file');
67
- fs.writeFileSync(this.configPath, configContent, {
68
- mode: 0o600, // Owner read/write only
69
- flag: 'w',
70
- });
35
+ const projectConfig = yaml.parse(fs.readFileSync(projectConfigPath, 'utf-8'));
36
+ mergeConfig(config, projectConfig);
71
37
  }
72
- catch (error) {
73
- throw new ValidationError(`Failed to save configuration: ${error}`, 'config', [
74
- 'Check file permissions',
75
- 'Ensure config directory exists',
76
- 'Verify disk space',
77
- ]);
38
+ catch (_e) {
39
+ // Silently ignore invalid config files
78
40
  }
79
41
  }
80
- /**
81
- * Get the current configuration
82
- */
83
- getConfig() {
84
- return { ...this.config };
85
- }
86
- /**
87
- * Update configuration
88
- */
89
- updateConfig(updates) {
90
- this.config = { ...this.config, ...updates };
91
- this.saveConfig(this.config);
92
- }
93
- /**
94
- * Reset configuration to defaults
95
- */
96
- resetConfig() {
97
- this.config = { ...DEFAULT_CONFIG };
98
- this.saveConfig(this.config);
42
+ // Apply environment variable overrides
43
+ if (process.env.ARK_CHAT_STREAMING !== undefined) {
44
+ config.chat = config.chat || {};
45
+ config.chat.streaming =
46
+ process.env.ARK_CHAT_STREAMING === '1' ||
47
+ process.env.ARK_CHAT_STREAMING === 'true';
99
48
  }
100
- /**
101
- * Get a specific configuration value
102
- */
103
- get(key) {
104
- return this.config[key];
105
- }
106
- /**
107
- * Set a specific configuration value
108
- */
109
- set(key, value) {
110
- this.config[key] = value;
111
- this.saveConfig(this.config);
112
- }
113
- /**
114
- * Validate configuration values
115
- */
116
- validateConfig() {
117
- const config = this.config;
118
- // Validate project type
119
- if (!['empty', 'with-samples'].includes(config.defaultProjectType)) {
120
- throw new ValidationError(`Invalid defaultProjectType: ${config.defaultProjectType}`, 'defaultProjectType', ['Must be "empty" or "with-samples"']);
121
- }
122
- // Validate model provider
123
- const validProviders = ['azure', 'openai', 'claude', 'gemini', 'custom'];
124
- if (!validProviders.includes(config.defaultModelProvider)) {
125
- throw new ValidationError(`Invalid defaultModelProvider: ${config.defaultModelProvider}`, 'defaultModelProvider', [`Must be one of: ${validProviders.join(', ')}`]);
126
- }
127
- // Validate numeric values
128
- if (config.maxConcurrentFiles < 1 || config.maxConcurrentFiles > 100) {
129
- throw new ValidationError(`Invalid maxConcurrentFiles: ${config.maxConcurrentFiles}`, 'maxConcurrentFiles', ['Must be between 1 and 100']);
130
- }
131
- // Validate paths
132
- if (config.defaultDestination) {
133
- SecurityUtils.validatePath(config.defaultDestination, 'default destination');
134
- }
135
- if (config.templateDirectory) {
136
- SecurityUtils.validatePath(config.templateDirectory, 'template directory');
137
- }
138
- }
139
- /**
140
- * Get environment variable overrides
141
- */
142
- getEnvironmentOverrides() {
143
- const overrides = {};
144
- // Check for environment variable overrides
145
- if (process.env.ARK_DEFAULT_PROJECT_TYPE) {
146
- const projectType = process.env.ARK_DEFAULT_PROJECT_TYPE;
147
- if (['empty', 'with-samples'].includes(projectType)) {
148
- overrides.defaultProjectType = projectType;
149
- }
150
- }
151
- if (process.env.ARK_DEFAULT_DESTINATION) {
152
- overrides.defaultDestination = process.env.ARK_DEFAULT_DESTINATION;
49
+ if (process.env.ARK_CHAT_OUTPUT_FORMAT !== undefined) {
50
+ config.chat = config.chat || {};
51
+ const format = process.env.ARK_CHAT_OUTPUT_FORMAT.toLowerCase();
52
+ if (format === 'markdown' || format === 'text') {
53
+ config.chat.outputFormat = format;
153
54
  }
154
- if (process.env.ARK_SKIP_GIT) {
155
- overrides.skipGitByDefault = process.env.ARK_SKIP_GIT === 'true';
156
- }
157
- if (process.env.ARK_SKIP_MODELS) {
158
- overrides.skipModelsbyDefault = process.env.ARK_SKIP_MODELS === 'true';
159
- }
160
- if (process.env.ARK_COLOR_OUTPUT) {
161
- overrides.colorOutput = process.env.ARK_COLOR_OUTPUT !== 'false';
162
- }
163
- if (process.env.ARK_VERBOSE) {
164
- overrides.verboseOutput = process.env.ARK_VERBOSE === 'true';
165
- }
166
- if (process.env.ARK_DEFAULT_MODEL_PROVIDER) {
167
- const provider = process.env.ARK_DEFAULT_MODEL_PROVIDER;
168
- const validProviders = [
169
- 'azure',
170
- 'openai',
171
- 'claude',
172
- 'gemini',
173
- 'custom',
174
- ];
175
- if (validProviders.includes(provider)) {
176
- overrides.defaultModelProvider =
177
- provider;
178
- }
179
- }
180
- return overrides;
181
55
  }
182
- /**
183
- * Get merged configuration with environment overrides
184
- */
185
- getMergedConfig() {
186
- const envOverrides = this.getEnvironmentOverrides();
187
- return { ...this.config, ...envOverrides };
188
- }
189
- /**
190
- * Export configuration for backup
191
- */
192
- exportConfig() {
193
- return JSON.stringify(this.config, null, 2);
194
- }
195
- /**
196
- * Import configuration from backup
197
- */
198
- importConfig(configJson) {
199
- try {
200
- const importedConfig = JSON.parse(configJson);
201
- // Validate the imported config
202
- const tempManager = new ConfigManager();
203
- tempManager.config = { ...DEFAULT_CONFIG, ...importedConfig };
204
- tempManager.validateConfig();
205
- // If validation passes, update our config
206
- this.config = tempManager.config;
207
- this.saveConfig(this.config);
56
+ return config;
57
+ }
58
+ /**
59
+ * Merge source config into target config (mutates target)
60
+ */
61
+ function mergeConfig(target, source) {
62
+ if (source.chat) {
63
+ target.chat = target.chat || {};
64
+ if (source.chat.streaming !== undefined) {
65
+ target.chat.streaming = source.chat.streaming;
208
66
  }
209
- catch (error) {
210
- throw new ValidationError(`Failed to import configuration: ${error}`, 'config', [
211
- 'Check JSON syntax',
212
- 'Ensure all required fields are present',
213
- 'Verify configuration values are valid',
214
- ]);
67
+ if (source.chat.outputFormat !== undefined) {
68
+ target.chat.outputFormat = source.chat.outputFormat;
215
69
  }
216
70
  }
217
- /**
218
- * Get configuration file path for CLI display
219
- */
220
- getConfigFilePath() {
221
- return this.configPath;
222
- }
71
+ }
72
+ /**
73
+ * Get the paths checked for config files
74
+ */
75
+ export function getConfigPaths() {
76
+ return {
77
+ user: path.join(os.homedir(), '.arkrc.yaml'),
78
+ project: path.join(process.cwd(), '.arkrc.yaml'),
79
+ };
80
+ }
81
+ /**
82
+ * Format config as YAML for display
83
+ */
84
+ export function formatConfig(config) {
85
+ return yaml.stringify(config);
223
86
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
2
+ import { loadConfig } from './config.js';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import os from 'os';
6
+ describe('Config', () => {
7
+ const originalEnv = process.env;
8
+ const testProjectConfig = path.join(process.cwd(), '.arkrc.yaml');
9
+ const testUserConfig = path.join(os.homedir(), '.arkrc.yaml');
10
+ beforeEach(() => {
11
+ // Reset environment
12
+ process.env = { ...originalEnv };
13
+ // Clean up any existing test configs
14
+ if (fs.existsSync(testProjectConfig)) {
15
+ fs.unlinkSync(testProjectConfig);
16
+ }
17
+ if (fs.existsSync(testUserConfig)) {
18
+ fs.unlinkSync(testUserConfig);
19
+ }
20
+ });
21
+ afterEach(() => {
22
+ // Restore environment
23
+ process.env = originalEnv;
24
+ // Clean up test configs
25
+ if (fs.existsSync(testProjectConfig)) {
26
+ fs.unlinkSync(testProjectConfig);
27
+ }
28
+ if (fs.existsSync(testUserConfig)) {
29
+ fs.unlinkSync(testUserConfig);
30
+ }
31
+ });
32
+ it('should load default config when no files or env vars exist', () => {
33
+ const config = loadConfig();
34
+ expect(config.chat?.streaming).toBe(true);
35
+ expect(config.chat?.outputFormat).toBe('text');
36
+ });
37
+ it('should override defaults with environment variables', () => {
38
+ process.env.ARK_CHAT_STREAMING = '0';
39
+ process.env.ARK_CHAT_OUTPUT_FORMAT = 'markdown';
40
+ const config = loadConfig();
41
+ expect(config.chat?.streaming).toBe(false);
42
+ expect(config.chat?.outputFormat).toBe('markdown');
43
+ });
44
+ it('should accept "1" and "true" for streaming', () => {
45
+ process.env.ARK_CHAT_STREAMING = '1';
46
+ let config = loadConfig();
47
+ expect(config.chat?.streaming).toBe(true);
48
+ process.env.ARK_CHAT_STREAMING = 'true';
49
+ config = loadConfig();
50
+ expect(config.chat?.streaming).toBe(true);
51
+ });
52
+ it('should load project config file', () => {
53
+ const configContent = `
54
+ chat:
55
+ streaming: false
56
+ outputFormat: markdown
57
+ `;
58
+ fs.writeFileSync(testProjectConfig, configContent);
59
+ const config = loadConfig();
60
+ expect(config.chat?.streaming).toBe(false);
61
+ expect(config.chat?.outputFormat).toBe('markdown');
62
+ });
63
+ it('should prioritize env vars over config files', () => {
64
+ const configContent = `
65
+ chat:
66
+ streaming: false
67
+ outputFormat: markdown
68
+ `;
69
+ fs.writeFileSync(testProjectConfig, configContent);
70
+ process.env.ARK_CHAT_STREAMING = '1';
71
+ process.env.ARK_CHAT_OUTPUT_FORMAT = 'text';
72
+ const config = loadConfig();
73
+ expect(config.chat?.streaming).toBe(true);
74
+ expect(config.chat?.outputFormat).toBe('text');
75
+ });
76
+ it('should handle invalid config files gracefully', () => {
77
+ fs.writeFileSync(testProjectConfig, 'invalid: yaml: content: {{{');
78
+ // Should fall back to defaults without throwing
79
+ const config = loadConfig();
80
+ expect(config.chat?.streaming).toBe(true);
81
+ expect(config.chat?.outputFormat).toBe('text');
82
+ });
83
+ it('should handle partial configs', () => {
84
+ const configContent = `
85
+ chat:
86
+ streaming: false
87
+ `;
88
+ fs.writeFileSync(testProjectConfig, configContent);
89
+ const config = loadConfig();
90
+ expect(config.chat?.streaming).toBe(false);
91
+ expect(config.chat?.outputFormat).toBe('text'); // Should use default
92
+ });
93
+ });
@@ -0,0 +1,30 @@
1
+ import { ArkDevToolStatus, DiscoveryResult, ProjectDiscoveryResult } from './types.js';
2
+ export declare class ArkDevToolAnalyzer {
3
+ private discoverToolsScript;
4
+ constructor();
5
+ /**
6
+ * Analyze a tool directory and return its status
7
+ */
8
+ analyzeToolDirectory(toolPath: string): Promise<ArkDevToolStatus>;
9
+ /**
10
+ * Get project information by checking for Python project files
11
+ */
12
+ private getProjectInfo;
13
+ /**
14
+ * Discover project configuration
15
+ */
16
+ discoverProject(targetPath: string): Promise<ProjectDiscoveryResult | undefined>;
17
+ /**
18
+ * Discover tools using the Python script
19
+ */
20
+ discoverTools(targetPath: string): Promise<DiscoveryResult | undefined>;
21
+ /**
22
+ * Recursively find all MCP tools in a project
23
+ * This is a naive implementation that searches all Python files in the project tree
24
+ */
25
+ findProjectTools(projectRoot: string): Promise<any>;
26
+ /**
27
+ * Extract all tools from discovery result
28
+ */
29
+ private extractTools;
30
+ }
@@ -0,0 +1,190 @@
1
+ import { execSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ export class ArkDevToolAnalyzer {
8
+ constructor() {
9
+ // The Python script is always adjacent to this file
10
+ // In dev: src/lib/dev/tools/discover_tools.py
11
+ // In prod: dist/lib/dev/tools/discover_tools.py (copied by postbuild)
12
+ this.discoverToolsScript = path.join(__dirname, 'discover_tools.py');
13
+ }
14
+ /**
15
+ * Analyze a tool directory and return its status
16
+ */
17
+ async analyzeToolDirectory(toolPath) {
18
+ const absolutePath = path.resolve(toolPath);
19
+ // Check if path exists
20
+ if (!fs.existsSync(absolutePath)) {
21
+ throw new Error(`Path not found: ${absolutePath}`);
22
+ }
23
+ // Get project info
24
+ const projectInfo = this.getProjectInfo(absolutePath);
25
+ // Discover tools using Python script
26
+ const discovery = await this.discoverTools(absolutePath);
27
+ // Extract all tools from discovery
28
+ const tools = this.extractTools(discovery);
29
+ return {
30
+ ...projectInfo,
31
+ discovery,
32
+ tools,
33
+ };
34
+ }
35
+ /**
36
+ * Get project information by checking for Python project files
37
+ */
38
+ getProjectInfo(dirPath) {
39
+ const info = {
40
+ path: dirPath,
41
+ platform: 'python3',
42
+ projectType: 'unknown',
43
+ hasVenv: false,
44
+ fastMCP: false,
45
+ };
46
+ // Check for virtual environment
47
+ info.hasVenv =
48
+ fs.existsSync(path.join(dirPath, '.venv')) ||
49
+ fs.existsSync(path.join(dirPath, 'venv'));
50
+ // Check Python project type and FastMCP presence
51
+ const pyprojectPath = path.join(dirPath, 'pyproject.toml');
52
+ const requirementsPath = path.join(dirPath, 'requirements.txt');
53
+ if (fs.existsSync(pyprojectPath)) {
54
+ info.projectType = 'pyproject';
55
+ const content = fs.readFileSync(pyprojectPath, 'utf-8');
56
+ if (content.includes('fastmcp')) {
57
+ info.fastMCP = true;
58
+ // Try to extract version
59
+ const versionMatch = content.match(/fastmcp[>=<~]*([0-9.]+)/);
60
+ if (versionMatch) {
61
+ info.fastMCPVersion = versionMatch[1];
62
+ }
63
+ }
64
+ }
65
+ else if (fs.existsSync(requirementsPath)) {
66
+ info.projectType = 'requirements';
67
+ const content = fs.readFileSync(requirementsPath, 'utf-8');
68
+ if (content.includes('fastmcp')) {
69
+ info.fastMCP = true;
70
+ const versionMatch = content.match(/fastmcp[>=<~]*([0-9.]+)/);
71
+ if (versionMatch) {
72
+ info.fastMCPVersion = versionMatch[1];
73
+ }
74
+ }
75
+ }
76
+ return info;
77
+ }
78
+ /**
79
+ * Discover project configuration
80
+ */
81
+ async discoverProject(targetPath) {
82
+ try {
83
+ // Check if Python is available
84
+ try {
85
+ execSync('python3 --version', { stdio: 'ignore' });
86
+ }
87
+ catch {
88
+ console.warn('Python 3 not found');
89
+ return undefined;
90
+ }
91
+ // Check if discover_tools.py exists
92
+ if (!fs.existsSync(this.discoverToolsScript)) {
93
+ console.warn(`discover_tools.py not found at ${this.discoverToolsScript}`);
94
+ return undefined;
95
+ }
96
+ // Run the discovery script with 'project' command
97
+ const result = execSync(`python3 "${this.discoverToolsScript}" project "${targetPath}"`, {
98
+ encoding: 'utf-8',
99
+ maxBuffer: 1024 * 1024, // 1MB buffer
100
+ });
101
+ return JSON.parse(result);
102
+ }
103
+ catch (error) {
104
+ console.error('Project discovery failed:', error);
105
+ return undefined;
106
+ }
107
+ }
108
+ /**
109
+ * Discover tools using the Python script
110
+ */
111
+ async discoverTools(targetPath) {
112
+ try {
113
+ // Check if Python is available
114
+ try {
115
+ execSync('python3 --version', { stdio: 'ignore' });
116
+ }
117
+ catch {
118
+ console.warn('Python 3 not found, skipping tool discovery');
119
+ return undefined;
120
+ }
121
+ // Check if discover_tools.py exists
122
+ if (!fs.existsSync(this.discoverToolsScript)) {
123
+ console.warn(`discover_tools.py not found at ${this.discoverToolsScript}`);
124
+ return undefined;
125
+ }
126
+ // Run the discovery script with 'tools' command
127
+ const result = execSync(`python3 "${this.discoverToolsScript}" tools "${targetPath}"`, {
128
+ encoding: 'utf-8',
129
+ maxBuffer: 1024 * 1024 * 10, // 10MB buffer
130
+ });
131
+ return JSON.parse(result);
132
+ }
133
+ catch (error) {
134
+ console.error('Tool discovery failed:', error);
135
+ return undefined;
136
+ }
137
+ }
138
+ /**
139
+ * Recursively find all MCP tools in a project
140
+ * This is a naive implementation that searches all Python files in the project tree
141
+ */
142
+ async findProjectTools(projectRoot) {
143
+ try {
144
+ // Check if Python is available
145
+ try {
146
+ execSync('python3 --version', { stdio: 'ignore' });
147
+ }
148
+ catch {
149
+ console.warn('Python 3 not found');
150
+ return null;
151
+ }
152
+ // Check if discover_tools.py exists
153
+ if (!fs.existsSync(this.discoverToolsScript)) {
154
+ console.warn(`discover_tools.py not found at ${this.discoverToolsScript}`);
155
+ return null;
156
+ }
157
+ // Run the discovery script with 'project-tools' command
158
+ const result = execSync(`python3 "${this.discoverToolsScript}" project-tools "${projectRoot}"`, {
159
+ encoding: 'utf-8',
160
+ maxBuffer: 1024 * 1024 * 10, // 10MB buffer
161
+ });
162
+ return JSON.parse(result);
163
+ }
164
+ catch (error) {
165
+ console.error('Project tools discovery failed:', error);
166
+ return null;
167
+ }
168
+ }
169
+ /**
170
+ * Extract all tools from discovery result
171
+ */
172
+ extractTools(discovery) {
173
+ if (!discovery)
174
+ return [];
175
+ // Check if it's a directory result
176
+ if ('files' in discovery) {
177
+ const dirResult = discovery;
178
+ const tools = [];
179
+ for (const file of dirResult.files) {
180
+ if (file.success && file.tools) {
181
+ tools.push(...file.tools);
182
+ }
183
+ }
184
+ return tools;
185
+ }
186
+ // Single file result
187
+ const fileResult = discovery;
188
+ return fileResult.success ? fileResult.tools : [];
189
+ }
190
+ }