agent-planner-mcp 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.
- package/README.md +324 -0
- package/package.json +53 -0
- package/src/api-client.js +540 -0
- package/src/index.js +68 -0
- package/src/integrations/search-integration.js +88 -0
- package/src/search-plan-wrapper.js +33 -0
- package/src/setup.js +347 -0
- package/src/test-server.js +3 -0
- package/src/tools/search-wrapper.js +188 -0
- package/src/tools.js +995 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper module for the search_plan functionality
|
|
3
|
+
*
|
|
4
|
+
* This wrapper extracts the results array from the API response format
|
|
5
|
+
* which is an object with { query, results, count } structure
|
|
6
|
+
*/
|
|
7
|
+
const apiClient = require('./api-client');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Search a plan and return the results array directly
|
|
11
|
+
* @param {string} planId - The ID of the plan to search
|
|
12
|
+
* @param {string} query - The search query
|
|
13
|
+
* @returns {Promise<Array>} - Array of search results
|
|
14
|
+
*/
|
|
15
|
+
async function searchPlan(planId, query) {
|
|
16
|
+
try {
|
|
17
|
+
// Call the API through the client
|
|
18
|
+
const response = await apiClient.search.searchPlan(planId, query);
|
|
19
|
+
|
|
20
|
+
// Extract just the results array
|
|
21
|
+
if (response && response.results && Array.isArray(response.results)) {
|
|
22
|
+
return response.results;
|
|
23
|
+
} else {
|
|
24
|
+
console.error('Unexpected search response format:', response);
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error in search_plan wrapper:', error.message);
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { searchPlan };
|
package/src/setup.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Planner MCP - Automated Setup Wizard
|
|
5
|
+
*
|
|
6
|
+
* This script helps users configure the MCP server for Claude Desktop:
|
|
7
|
+
* 1. Checks API server availability
|
|
8
|
+
* 2. Guides token creation and validates it
|
|
9
|
+
* 3. Creates .env file
|
|
10
|
+
* 4. Detects and updates Claude Desktop config
|
|
11
|
+
* 5. Tests the connection
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const readline = require('readline');
|
|
17
|
+
const { exec } = require('child_process');
|
|
18
|
+
const axios = require('axios');
|
|
19
|
+
|
|
20
|
+
// Colors for terminal output
|
|
21
|
+
const colors = {
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
bright: '\x1b[1m',
|
|
24
|
+
green: '\x1b[32m',
|
|
25
|
+
blue: '\x1b[34m',
|
|
26
|
+
yellow: '\x1b[33m',
|
|
27
|
+
red: '\x1b[31m',
|
|
28
|
+
cyan: '\x1b[36m',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function log(message, color = 'reset') {
|
|
32
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function logStep(step, total, message) {
|
|
36
|
+
log(`\nStep ${step}/${total}: ${message}`, 'bright');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function logSuccess(message) {
|
|
40
|
+
log(`✓ ${message}`, 'green');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function logError(message) {
|
|
44
|
+
log(`✗ ${message}`, 'red');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function logInfo(message) {
|
|
48
|
+
log(`ℹ ${message}`, 'cyan');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Create readline interface
|
|
52
|
+
const rl = readline.createInterface({
|
|
53
|
+
input: process.stdin,
|
|
54
|
+
output: process.stdout
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function question(query) {
|
|
58
|
+
return new Promise(resolve => rl.question(query, resolve));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Detect Claude Desktop config location based on OS
|
|
62
|
+
function getClaudeConfigPath() {
|
|
63
|
+
const platform = process.platform;
|
|
64
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
65
|
+
|
|
66
|
+
if (platform === 'darwin') {
|
|
67
|
+
// macOS
|
|
68
|
+
return path.join(homeDir, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
69
|
+
} else if (platform === 'win32') {
|
|
70
|
+
// Windows
|
|
71
|
+
return path.join(homeDir, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
72
|
+
} else {
|
|
73
|
+
// Linux
|
|
74
|
+
return path.join(homeDir, '.config', 'Claude', 'claude_desktop_config.json');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if API server is accessible
|
|
79
|
+
async function checkApiHealth(apiUrl) {
|
|
80
|
+
try {
|
|
81
|
+
const response = await axios.get(`${apiUrl}/health`, { timeout: 5000 });
|
|
82
|
+
return response.status === 200;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validate API token
|
|
89
|
+
async function validateToken(apiUrl, token) {
|
|
90
|
+
try {
|
|
91
|
+
const response = await axios.get(`${apiUrl}/plans`, {
|
|
92
|
+
headers: {
|
|
93
|
+
'Authorization': `ApiKey ${token}`
|
|
94
|
+
},
|
|
95
|
+
timeout: 5000
|
|
96
|
+
});
|
|
97
|
+
return response.status === 200;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error.response && error.response.status === 401) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
// If it's another error (like network), we can't validate
|
|
103
|
+
throw new Error(`Cannot validate token: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Open URL in default browser
|
|
108
|
+
function openBrowser(url) {
|
|
109
|
+
const command = process.platform === 'darwin' ? 'open' :
|
|
110
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
111
|
+
|
|
112
|
+
exec(`${command} ${url}`, (error) => {
|
|
113
|
+
if (error) {
|
|
114
|
+
logInfo(`Could not open browser automatically. Please open: ${url}`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create .env file
|
|
120
|
+
function createEnvFile(config) {
|
|
121
|
+
const envContent = `# Agent Planner MCP Configuration
|
|
122
|
+
# Generated on ${new Date().toISOString()}
|
|
123
|
+
|
|
124
|
+
API_URL=${config.apiUrl}
|
|
125
|
+
USER_API_TOKEN=${config.token}
|
|
126
|
+
MCP_SERVER_NAME=planning-system
|
|
127
|
+
MCP_SERVER_VERSION=0.2.0
|
|
128
|
+
NODE_ENV=production
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
const envPath = path.join(__dirname, '..', '.env');
|
|
132
|
+
fs.writeFileSync(envPath, envContent);
|
|
133
|
+
return envPath;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Update Claude Desktop config
|
|
137
|
+
function updateClaudeConfig(configPath, mcpServerPath, apiUrl, token) {
|
|
138
|
+
let config = {};
|
|
139
|
+
|
|
140
|
+
// Read existing config if it exists
|
|
141
|
+
if (fs.existsSync(configPath)) {
|
|
142
|
+
try {
|
|
143
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
144
|
+
config = JSON.parse(content);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
logInfo('Could not parse existing config, creating new one');
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
// Create directory if it doesn't exist
|
|
150
|
+
const configDir = path.dirname(configPath);
|
|
151
|
+
if (!fs.existsSync(configDir)) {
|
|
152
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Ensure mcpServers exists
|
|
157
|
+
if (!config.mcpServers) {
|
|
158
|
+
config.mcpServers = {};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Add or update planning-system server
|
|
162
|
+
config.mcpServers['planning-system'] = {
|
|
163
|
+
command: 'node',
|
|
164
|
+
args: [path.join(mcpServerPath, 'src', 'index.js')],
|
|
165
|
+
env: {
|
|
166
|
+
API_URL: apiUrl,
|
|
167
|
+
USER_API_TOKEN: token
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Write config
|
|
172
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
173
|
+
return configPath;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Main setup wizard
|
|
177
|
+
async function runSetup() {
|
|
178
|
+
log('\n🚀 Agent Planner MCP Setup Wizard\n', 'bright');
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
// Step 1: API Configuration
|
|
182
|
+
logStep(1, 5, 'API Configuration');
|
|
183
|
+
|
|
184
|
+
log('\nWhere is your Agent Planner API running?');
|
|
185
|
+
log('1. Local development (localhost)');
|
|
186
|
+
log('2. Google Cloud Run');
|
|
187
|
+
log('3. Custom URL');
|
|
188
|
+
|
|
189
|
+
const deploymentType = await question('\nSelect option (1/2/3, default: 1): ');
|
|
190
|
+
|
|
191
|
+
let apiUrl;
|
|
192
|
+
let uiUrl;
|
|
193
|
+
|
|
194
|
+
if (deploymentType === '2') {
|
|
195
|
+
log('\nFor Cloud Run, you can find your URLs at:');
|
|
196
|
+
log('https://console.cloud.google.com/run?project=ta-agent-planner');
|
|
197
|
+
|
|
198
|
+
const apiUrlInput = await question('\nEnter API URL (e.g., https://agent-planner-api-xxx.run.app): ');
|
|
199
|
+
apiUrl = apiUrlInput.trim();
|
|
200
|
+
|
|
201
|
+
// Derive UI URL from API URL or ask for it
|
|
202
|
+
const suggestedUiUrl = apiUrl.replace('api-', 'ui-').replace('api.', 'ui.');
|
|
203
|
+
const uiUrlInput = await question(`Enter UI URL (default: ${suggestedUiUrl}): `);
|
|
204
|
+
uiUrl = uiUrlInput.trim() || suggestedUiUrl;
|
|
205
|
+
} else if (deploymentType === '3') {
|
|
206
|
+
const apiUrlInput = await question('\nEnter API URL: ');
|
|
207
|
+
apiUrl = apiUrlInput.trim();
|
|
208
|
+
|
|
209
|
+
const uiUrlInput = await question('Enter UI URL: ');
|
|
210
|
+
uiUrl = uiUrlInput.trim();
|
|
211
|
+
} else {
|
|
212
|
+
// Local development (default)
|
|
213
|
+
apiUrl = 'http://localhost:3000';
|
|
214
|
+
uiUrl = 'http://localhost:3001';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
log('Checking API server...');
|
|
218
|
+
const apiAvailable = await checkApiHealth(apiUrl);
|
|
219
|
+
|
|
220
|
+
if (!apiAvailable) {
|
|
221
|
+
logError(`Cannot connect to API server at ${apiUrl}`);
|
|
222
|
+
logInfo('Make sure the API server is running: cd agent-planner && npm start');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
logSuccess(`Found API server at ${apiUrl}`);
|
|
227
|
+
|
|
228
|
+
// Step 2: API Token Setup
|
|
229
|
+
logStep(2, 5, 'API Token Setup');
|
|
230
|
+
|
|
231
|
+
log('\nPlease generate an API token:');
|
|
232
|
+
log('1. Open ' + uiUrl + '/app/settings in your browser');
|
|
233
|
+
log('2. Navigate to the "API Tokens" section');
|
|
234
|
+
log('3. Click "Create MCP Token" or "Create New Token"');
|
|
235
|
+
log('4. Enter a name (e.g., "MCP Server")');
|
|
236
|
+
log('5. Copy the generated token\n');
|
|
237
|
+
|
|
238
|
+
logInfo('Opening settings page in your browser...');
|
|
239
|
+
openBrowser(uiUrl + '/app/settings');
|
|
240
|
+
|
|
241
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
242
|
+
|
|
243
|
+
const token = await question('\nEnter your API token: ');
|
|
244
|
+
|
|
245
|
+
if (!token || token.trim().length < 10) {
|
|
246
|
+
logError('Invalid token provided');
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
log('Validating token...');
|
|
251
|
+
try {
|
|
252
|
+
const isValid = await validateToken(apiUrl, token.trim());
|
|
253
|
+
|
|
254
|
+
if (!isValid) {
|
|
255
|
+
logError('Token validation failed - please check the token and try again');
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
logSuccess('Token validated successfully');
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logError(`Token validation failed: ${error.message}`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Step 3: Environment Configuration
|
|
266
|
+
logStep(3, 5, 'Environment Configuration');
|
|
267
|
+
|
|
268
|
+
const envPath = createEnvFile({ apiUrl, token: token.trim() });
|
|
269
|
+
logSuccess(`Created .env file at: ${envPath}`);
|
|
270
|
+
|
|
271
|
+
// Step 4: Claude Desktop Configuration
|
|
272
|
+
logStep(4, 5, 'Claude Desktop Configuration');
|
|
273
|
+
|
|
274
|
+
const configPath = getClaudeConfigPath();
|
|
275
|
+
const mcpServerPath = path.join(__dirname, '..');
|
|
276
|
+
|
|
277
|
+
log(`Detected Claude Desktop config at: ${configPath}`);
|
|
278
|
+
|
|
279
|
+
const shouldUpdateConfig = await question('Update Claude Desktop config? (y/n): ');
|
|
280
|
+
|
|
281
|
+
if (shouldUpdateConfig.toLowerCase() === 'y' || shouldUpdateConfig.toLowerCase() === 'yes') {
|
|
282
|
+
try {
|
|
283
|
+
updateClaudeConfig(configPath, mcpServerPath, apiUrl, token.trim());
|
|
284
|
+
logSuccess('Added planning-system MCP server to Claude Desktop config');
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logError(`Failed to update config: ${error.message}`);
|
|
287
|
+
logInfo('You can manually add the configuration later');
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
logInfo('Skipped Claude Desktop config update');
|
|
291
|
+
log('\nManual configuration:');
|
|
292
|
+
log(JSON.stringify({
|
|
293
|
+
"mcpServers": {
|
|
294
|
+
"planning-system": {
|
|
295
|
+
"command": "node",
|
|
296
|
+
"args": [path.join(mcpServerPath, 'src', 'index.js')],
|
|
297
|
+
"env": {
|
|
298
|
+
"API_URL": apiUrl,
|
|
299
|
+
"USER_API_TOKEN": token.trim().substring(0, 10) + '...'
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}, null, 2));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Step 5: Testing Connection
|
|
307
|
+
logStep(5, 5, 'Testing Connection');
|
|
308
|
+
|
|
309
|
+
log('Testing MCP server connection...');
|
|
310
|
+
|
|
311
|
+
// Simple test by loading the API client
|
|
312
|
+
try {
|
|
313
|
+
process.env.API_URL = apiUrl;
|
|
314
|
+
process.env.USER_API_TOKEN = token.trim();
|
|
315
|
+
|
|
316
|
+
const apiClient = require('./api-client');
|
|
317
|
+
const plans = await apiClient.plans.getPlans();
|
|
318
|
+
|
|
319
|
+
logSuccess(`MCP server can connect to API`);
|
|
320
|
+
logSuccess(`Successfully retrieved ${plans.length} plan(s)`);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
logError(`Connection test failed: ${error.message}`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Success!
|
|
326
|
+
log('\n🎉 Setup complete!\n', 'green');
|
|
327
|
+
|
|
328
|
+
log('Next steps:', 'bright');
|
|
329
|
+
log('1. Restart Claude Desktop to load the MCP server');
|
|
330
|
+
log('2. Look for the 🔨 icon in Claude Desktop - you should see planning tools');
|
|
331
|
+
log('3. Try asking: "List my plans" or "Create a new plan called \'Test Project\'"');
|
|
332
|
+
log('');
|
|
333
|
+
log('Configuration saved to:', 'bright');
|
|
334
|
+
log(` .env file: ${envPath}`);
|
|
335
|
+
log(` Claude config: ${configPath}`);
|
|
336
|
+
log('');
|
|
337
|
+
|
|
338
|
+
} catch (error) {
|
|
339
|
+
logError(`\nSetup failed: ${error.message}`);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
} finally {
|
|
342
|
+
rl.close();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Run the setup wizard
|
|
347
|
+
runSetup();
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search wrapper for the agent-planner-mcp
|
|
3
|
+
*
|
|
4
|
+
* This module provides wrapper functions for the search API calls
|
|
5
|
+
* that properly handle the response format (extracting the results array
|
|
6
|
+
* from the {query, results, count} response structure).
|
|
7
|
+
*/
|
|
8
|
+
const apiClient = require('../api-client');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Search within a plan and return only the results array
|
|
12
|
+
*
|
|
13
|
+
* @param {string} planId - ID of the plan to search
|
|
14
|
+
* @param {string} query - Search query text
|
|
15
|
+
* @returns {Promise<Array>} - Array of search results
|
|
16
|
+
*/
|
|
17
|
+
async function searchPlan(planId, query) {
|
|
18
|
+
try {
|
|
19
|
+
// Call the original search function from the API client
|
|
20
|
+
const response = await apiClient.search.searchPlan(planId, query);
|
|
21
|
+
|
|
22
|
+
// Log the actual response for debugging
|
|
23
|
+
if (process.env.NODE_ENV === 'development') {
|
|
24
|
+
console.log('Search plan response:', typeof response, Object.keys(response || {}));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Handle different response formats
|
|
28
|
+
if (!response) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If response is already an array, return it
|
|
33
|
+
if (Array.isArray(response)) {
|
|
34
|
+
return response;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If response has results array
|
|
38
|
+
if (response.results && Array.isArray(response.results)) {
|
|
39
|
+
return response.results;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If response has other properties that are arrays
|
|
43
|
+
const results = [];
|
|
44
|
+
Object.keys(response).forEach(key => {
|
|
45
|
+
if (Array.isArray(response[key])) {
|
|
46
|
+
response[key].forEach(item => {
|
|
47
|
+
results.push({
|
|
48
|
+
...item,
|
|
49
|
+
type: item.type || key,
|
|
50
|
+
source: 'plan_search'
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (results.length > 0) {
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Try to parse response as a search result object
|
|
61
|
+
if (response.query !== undefined && response.count !== undefined) {
|
|
62
|
+
return response.results || [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.error('Unexpected search response format:', JSON.stringify(response).substring(0, 200));
|
|
66
|
+
return [];
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('Error in searchPlan wrapper:', error.message);
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Global search across all plans and return only the results
|
|
75
|
+
*
|
|
76
|
+
* @param {string} query - Search query text
|
|
77
|
+
* @returns {Promise<Array>} - Flattened array of all search results
|
|
78
|
+
*/
|
|
79
|
+
async function globalSearch(query) {
|
|
80
|
+
try {
|
|
81
|
+
// Call the original global search function
|
|
82
|
+
const response = await apiClient.search.globalSearch(query);
|
|
83
|
+
|
|
84
|
+
if (process.env.NODE_ENV === 'development') {
|
|
85
|
+
console.log('Global search response type:', typeof response);
|
|
86
|
+
if (response) {
|
|
87
|
+
console.log('Response keys:', Object.keys(response));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Handle different response formats
|
|
92
|
+
if (!response) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// If response is already an array of results
|
|
97
|
+
if (Array.isArray(response)) {
|
|
98
|
+
return response.map(item => ({
|
|
99
|
+
...item,
|
|
100
|
+
type: item.type || 'unknown',
|
|
101
|
+
source: 'global_search'
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// If response has a results property
|
|
106
|
+
if (response.results !== undefined) {
|
|
107
|
+
// If results is already an array, return it
|
|
108
|
+
if (Array.isArray(response.results)) {
|
|
109
|
+
return response.results.map(item => ({
|
|
110
|
+
...item,
|
|
111
|
+
type: item.type || 'unknown',
|
|
112
|
+
source: 'global_search'
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// If results is an object with categories
|
|
117
|
+
if (typeof response.results === 'object') {
|
|
118
|
+
const allResults = [];
|
|
119
|
+
|
|
120
|
+
// Collect results from each category (plans, nodes, comments, etc.)
|
|
121
|
+
Object.keys(response.results).forEach(category => {
|
|
122
|
+
if (Array.isArray(response.results[category])) {
|
|
123
|
+
// Add category type to each result
|
|
124
|
+
const categoryResults = response.results[category].map(item => ({
|
|
125
|
+
...item,
|
|
126
|
+
type: item.type || category,
|
|
127
|
+
category,
|
|
128
|
+
source: 'global_search'
|
|
129
|
+
}));
|
|
130
|
+
allResults.push(...categoryResults);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return allResults;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check if response has categorized results (plans, nodes, etc.)
|
|
139
|
+
const categories = ['plans', 'nodes', 'comments', 'logs', 'artifacts'];
|
|
140
|
+
const allResults = [];
|
|
141
|
+
|
|
142
|
+
categories.forEach(category => {
|
|
143
|
+
if (response[category] && Array.isArray(response[category])) {
|
|
144
|
+
response[category].forEach(item => {
|
|
145
|
+
allResults.push({
|
|
146
|
+
...item,
|
|
147
|
+
type: item.type || category.slice(0, -1), // Remove 's' from category
|
|
148
|
+
category,
|
|
149
|
+
source: 'global_search'
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (allResults.length > 0) {
|
|
156
|
+
return allResults;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Generic handler for any object with arrays
|
|
160
|
+
Object.keys(response).forEach(key => {
|
|
161
|
+
if (Array.isArray(response[key]) && key !== 'results') {
|
|
162
|
+
response[key].forEach(item => {
|
|
163
|
+
allResults.push({
|
|
164
|
+
...item,
|
|
165
|
+
type: item.type || key,
|
|
166
|
+
category: key,
|
|
167
|
+
source: 'global_search'
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return allResults;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('Error in globalSearch wrapper:', error.message);
|
|
176
|
+
if (error.response) {
|
|
177
|
+
console.error('API error status:', error.response.status);
|
|
178
|
+
console.error('API error data:', error.response.data);
|
|
179
|
+
}
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Export the wrapper functions
|
|
185
|
+
module.exports = {
|
|
186
|
+
searchPlan,
|
|
187
|
+
globalSearch
|
|
188
|
+
};
|