delegate-sf-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.
Files changed (44) hide show
  1. package/.eslintrc.json +20 -0
  2. package/LICENSE +24 -0
  3. package/README.md +76 -0
  4. package/auth.js +148 -0
  5. package/bin/config-helper.js +51 -0
  6. package/bin/mcp-salesforce.js +12 -0
  7. package/bin/setup.js +266 -0
  8. package/bin/status.js +134 -0
  9. package/docs/README.md +52 -0
  10. package/docs/step1.png +0 -0
  11. package/docs/step2.png +0 -0
  12. package/docs/step3.png +0 -0
  13. package/docs/step4.png +0 -0
  14. package/examples/README.md +35 -0
  15. package/package.json +16 -0
  16. package/scripts/README.md +30 -0
  17. package/src/auth/file-storage.js +447 -0
  18. package/src/auth/oauth.js +417 -0
  19. package/src/auth/token-manager.js +207 -0
  20. package/src/backup/manager.js +949 -0
  21. package/src/index.js +168 -0
  22. package/src/salesforce/client.js +388 -0
  23. package/src/sf-client.js +79 -0
  24. package/src/tools/auth.js +190 -0
  25. package/src/tools/backup.js +486 -0
  26. package/src/tools/create.js +109 -0
  27. package/src/tools/delegate-hygiene.js +268 -0
  28. package/src/tools/delegate-validate.js +212 -0
  29. package/src/tools/delegate-verify.js +143 -0
  30. package/src/tools/delete.js +72 -0
  31. package/src/tools/describe.js +132 -0
  32. package/src/tools/installation-info.js +656 -0
  33. package/src/tools/learn-context.js +1077 -0
  34. package/src/tools/learn.js +351 -0
  35. package/src/tools/query.js +82 -0
  36. package/src/tools/repair-credentials.js +77 -0
  37. package/src/tools/setup.js +120 -0
  38. package/src/tools/time_machine.js +347 -0
  39. package/src/tools/update.js +138 -0
  40. package/src/tools.js +214 -0
  41. package/src/utils/cache.js +120 -0
  42. package/src/utils/debug.js +52 -0
  43. package/src/utils/logger.js +19 -0
  44. package/tokens.json +8 -0
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Salesforce Learn Tool
3
+ *
4
+ * This tool analyzes the complete Salesforce installation and creates
5
+ * a comprehensive local documentation cache of all objects, fields,
6
+ * relationships, and customizations.
7
+ */
8
+
9
+ import fs from 'fs/promises';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { debug as logger } from '../utils/debug.js';
13
+ import { getCacheFilePath, ensureCacheDirectory } from '../utils/cache.js';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+
18
+ // Path for storing the learned installation data
19
+ const INSTALLATION_FILE = getCacheFilePath('salesforce-installation.json');
20
+
21
+ export const salesforceLearnTool = {
22
+ name: "salesforce_learn",
23
+ description: "Analyzes the complete Salesforce installation and creates local documentation of all objects, fields, and customizations. This should be run once after initial setup to enable intelligent assistance.",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ force_refresh: {
28
+ type: "boolean",
29
+ description: "Forces a complete re-analysis even if documentation already exists",
30
+ default: false
31
+ },
32
+ include_unused: {
33
+ type: "boolean",
34
+ description: "Includes unused/inactive fields and objects in the documentation",
35
+ default: false
36
+ },
37
+ detailed_relationships: {
38
+ type: "boolean",
39
+ description: "Analyzes detailed relationships between objects",
40
+ default: true
41
+ }
42
+ }
43
+ }
44
+ };
45
+
46
+ export async function handleSalesforceLearn(args, salesforceClient) {
47
+ const { force_refresh = false, include_unused = false, detailed_relationships = true } = args;
48
+
49
+ try {
50
+ // Check if we need to create cache directory
51
+ await ensureCacheDirectory();
52
+
53
+ // Check if documentation already exists
54
+ const existingDoc = await getExistingDocumentation();
55
+ if (existingDoc && !force_refresh) {
56
+ return {
57
+ content: [{
58
+ type: "text",
59
+ text: `✅ Salesforce installation already documented (${existingDoc.metadata.learned_at})\n\n` +
60
+ `📊 **Overview:**\n` +
61
+ `- **${existingDoc.summary.total_objects}** total objects\n` +
62
+ `- **${existingDoc.summary.custom_objects}** custom objects\n` +
63
+ `- **${existingDoc.summary.total_fields}** total fields\n` +
64
+ `- **${existingDoc.summary.custom_fields}** custom fields\n\n` +
65
+ `💡 Use \`force_refresh: true\` to force re-analysis, or use the \`salesforce_installation_info\` tool to view details.`
66
+ }]
67
+ };
68
+ }
69
+
70
+ // Start learning process
71
+ logger.log('🔍 Starting Salesforce installation learning process...');
72
+
73
+ // Step 1: Get all available objects
74
+ const globalDescribe = await salesforceClient.describeGlobal();
75
+
76
+ logger.log(`📋 Found: ${globalDescribe.length} objects`);
77
+
78
+ // Step 2: Filter and categorize objects
79
+ const standardObjects = [];
80
+ const customObjects = [];
81
+
82
+ for (const obj of globalDescribe) {
83
+ if (obj.custom) {
84
+ customObjects.push(obj);
85
+ } else if (obj.name.match(/^(Account|Contact|Lead|Opportunity|Case|Task|Event|Campaign|Product2|Pricebook2|User|Profile)$/)) {
86
+ // Include important standard objects
87
+ standardObjects.push(obj);
88
+ }
89
+ }
90
+
91
+ logger.log(`📊 Standard objects: ${standardObjects.length}, Custom objects: ${customObjects.length}`);
92
+
93
+ // Step 3: Detailed analysis of selected objects
94
+ const documentation = {
95
+ metadata: {
96
+ learned_at: new Date().toISOString(),
97
+ salesforce_instance: salesforceClient.instanceUrl,
98
+ api_version: salesforceClient.version || 'v58.0',
99
+ learning_options: { include_unused, detailed_relationships }
100
+ },
101
+ summary: {
102
+ total_objects: standardObjects.length + customObjects.length,
103
+ standard_objects: standardObjects.length,
104
+ custom_objects: customObjects.length,
105
+ total_fields: 0,
106
+ custom_fields: 0
107
+ },
108
+ objects: {}
109
+ };
110
+
111
+ // Analyze objects in batches to avoid rate limits
112
+ const objectsToAnalyze = [...standardObjects, ...customObjects];
113
+ const batchSize = 10;
114
+
115
+ for (let i = 0; i < objectsToAnalyze.length; i += batchSize) {
116
+ const batch = objectsToAnalyze.slice(i, i + batchSize);
117
+ logger.log(`🔬 Analyzing batch ${Math.floor(i/batchSize) + 1}/${Math.ceil(objectsToAnalyze.length/batchSize)} (${batch.length} objects)`);
118
+
119
+ await Promise.all(batch.map(async (obj) => {
120
+ try {
121
+ const objectDoc = await analyzeObject(obj, salesforceClient, { include_unused, detailed_relationships });
122
+ documentation.objects[obj.name] = objectDoc;
123
+ documentation.summary.total_fields += objectDoc.field_count;
124
+ documentation.summary.custom_fields += objectDoc.custom_field_count;
125
+ } catch (error) {
126
+ logger.warn(`⚠️ Error analyzing ${obj.name}:`, error.message);
127
+ documentation.objects[obj.name] = {
128
+ error: `Analysis failed: ${error.message}`,
129
+ basic_info: {
130
+ name: obj.name,
131
+ label: obj.label,
132
+ custom: obj.custom
133
+ }
134
+ };
135
+ }
136
+ }));
137
+
138
+ // Small delay between batches
139
+ if (i + batchSize < objectsToAnalyze.length) {
140
+ await new Promise(resolve => setTimeout(resolve, 1000));
141
+ }
142
+ }
143
+
144
+ // Step 4: Save documentation
145
+ await fs.writeFile(INSTALLATION_FILE, JSON.stringify(documentation, null, 2), 'utf8');
146
+
147
+ logger.log('✅ Learning process completed');
148
+
149
+ return {
150
+ content: [{
151
+ type: "text",
152
+ text: `🎉 **Salesforce installation successfully learned!**\n\n` +
153
+ `📊 **Analyzed data:**\n` +
154
+ `- **${documentation.summary.total_objects}** total objects\n` +
155
+ `- **${documentation.summary.standard_objects}** standard objects\n` +
156
+ `- **${documentation.summary.custom_objects}** custom objects\n` +
157
+ `- **${documentation.summary.total_fields}** total fields\n` +
158
+ `- **${documentation.summary.custom_fields}** custom fields\n\n` +
159
+ `💾 **Documentation saved** to: \`${INSTALLATION_FILE}\`\n\n` +
160
+ `✨ **Next steps:**\n` +
161
+ `- Use \`salesforce_installation_info\` to get an overview\n` +
162
+ `- Ask about specific objects or fields\n` +
163
+ `- The AI can now work intelligently with your Salesforce installation!`
164
+ }]
165
+ };
166
+
167
+ } catch (error) {
168
+ logger.error('❌ Error during learning process:', error);
169
+ return {
170
+ content: [{
171
+ type: "text",
172
+ text: `❌ **Error learning Salesforce installation:**\n\n` +
173
+ `${error.message}\n\n` +
174
+ `🔧 **Possible solutions:**\n` +
175
+ `- Check your Salesforce connection\n` +
176
+ `- Ensure you have sufficient permissions\n` +
177
+ `- Try re-authenticating with \`salesforce_auth\``
178
+ }]
179
+ };
180
+ }
181
+ }
182
+
183
+ async function analyzeObject(obj, salesforceClient, options) {
184
+ const { include_unused, detailed_relationships } = options;
185
+
186
+ // Get detailed object description
187
+ const describe = await salesforceClient.describe(obj.name);
188
+
189
+ const objectDoc = {
190
+ basic_info: {
191
+ name: obj.name,
192
+ label: obj.label,
193
+ label_plural: obj.labelPlural,
194
+ custom: obj.custom,
195
+ api_name: obj.name
196
+ },
197
+ metadata: {
198
+ createable: describe.createable,
199
+ updateable: describe.updateable,
200
+ deletable: describe.deletable,
201
+ queryable: describe.queryable,
202
+ searchable: describe.searchable,
203
+ retrieveable: describe.retrieveable
204
+ },
205
+ field_count: describe.fields.length,
206
+ custom_field_count: describe.fields.filter(f => f.custom).length,
207
+ fields: {},
208
+ relationships: {
209
+ parent_relationships: [],
210
+ child_relationships: []
211
+ }
212
+ };
213
+
214
+ // Analyze fields
215
+ for (const field of describe.fields) {
216
+ // Skip system fields if not including unused
217
+ if (!include_unused && isSystemField(field)) {
218
+ continue;
219
+ }
220
+
221
+ const fieldDoc = {
222
+ name: field.name,
223
+ label: field.label,
224
+ type: field.type,
225
+ custom: field.custom,
226
+ required: !field.nillable && !field.defaultedOnCreate,
227
+ updateable: field.updateable,
228
+ createable: field.createable,
229
+ // Enhanced writability information
230
+ writability: {
231
+ fully_writable: field.updateable && field.createable,
232
+ create_only: field.createable && !field.updateable,
233
+ read_only: !field.updateable && !field.createable,
234
+ system_managed: isSystemManagedField(field),
235
+ calculated: field.calculated || false,
236
+ auto_number: field.type === 'autonumber',
237
+ formula: field.type === 'formula' || field.calculated,
238
+ rollup_summary: field.type === 'summary'
239
+ }
240
+ };
241
+
242
+ // Add type-specific information
243
+ if (field.type === 'picklist' || field.type === 'multipicklist') {
244
+ fieldDoc.picklist_values = field.picklistValues?.map(v => ({
245
+ value: v.value,
246
+ label: v.label,
247
+ active: v.active
248
+ })) || [];
249
+ }
250
+
251
+ if (field.type === 'reference') {
252
+ fieldDoc.reference_to = field.referenceTo;
253
+ if (detailed_relationships && field.referenceTo?.length > 0) {
254
+ objectDoc.relationships.parent_relationships.push({
255
+ field: field.name,
256
+ references: field.referenceTo
257
+ });
258
+ }
259
+ }
260
+
261
+ if (field.length) {
262
+ fieldDoc.max_length = field.length;
263
+ }
264
+
265
+ if (field.precision) {
266
+ fieldDoc.precision = field.precision;
267
+ fieldDoc.scale = field.scale;
268
+ }
269
+
270
+ objectDoc.fields[field.name] = fieldDoc;
271
+ }
272
+
273
+ // Analyze child relationships
274
+ if (detailed_relationships && describe.childRelationships) {
275
+ for (const childRel of describe.childRelationships) {
276
+ if (childRel.relationshipName) {
277
+ objectDoc.relationships.child_relationships.push({
278
+ child_object: childRel.childSObject,
279
+ relationship_name: childRel.relationshipName,
280
+ field: childRel.field
281
+ });
282
+ }
283
+ }
284
+ }
285
+
286
+ return objectDoc;
287
+ }
288
+
289
+ function isSystemField(field) {
290
+ const systemFields = [
291
+ 'Id', 'CreatedDate', 'CreatedById', 'LastModifiedDate', 'LastModifiedById',
292
+ 'SystemModstamp', 'IsDeleted', 'MasterRecordId', 'LastActivityDate',
293
+ 'LastViewedDate', 'LastReferencedDate'
294
+ ];
295
+ return systemFields.includes(field.name);
296
+ }
297
+
298
+ function isSystemManagedField(field) {
299
+ // System fields that are automatically managed by Salesforce
300
+ const systemManagedFields = [
301
+ 'Id', 'CreatedDate', 'CreatedById', 'LastModifiedDate', 'LastModifiedById',
302
+ 'SystemModstamp', 'IsDeleted', 'MasterRecordId', 'LastActivityDate',
303
+ 'LastViewedDate', 'LastReferencedDate', 'RecordTypeId'
304
+ ];
305
+
306
+ // Check if it's a system managed field by name
307
+ if (systemManagedFields.includes(field.name)) {
308
+ return true;
309
+ }
310
+
311
+ // Check if it's an auto-number field
312
+ if (field.type === 'autonumber') {
313
+ return true;
314
+ }
315
+
316
+ // Check if it's a formula field
317
+ if (field.type === 'formula' || field.calculated) {
318
+ return true;
319
+ }
320
+
321
+ // Check if it's a rollup summary field
322
+ if (field.type === 'summary') {
323
+ return true;
324
+ }
325
+
326
+ // Check if field is marked as not updateable and not createable (fully read-only)
327
+ if (!field.updateable && !field.createable) {
328
+ return true;
329
+ }
330
+
331
+ return false;
332
+ }
333
+
334
+
335
+ async function getExistingDocumentation() {
336
+ try {
337
+ const content = await fs.readFile(INSTALLATION_FILE, 'utf8');
338
+ return JSON.parse(content);
339
+ } catch {
340
+ return null;
341
+ }
342
+ }
343
+
344
+ export async function getInstallationDocumentation() {
345
+ return await getExistingDocumentation();
346
+ }
347
+
348
+ export async function hasInstallationDocumentation() {
349
+ const doc = await getExistingDocumentation();
350
+ return doc !== null;
351
+ }
@@ -0,0 +1,82 @@
1
+ import { hasInstallationDocumentation } from './learn.js';
2
+
3
+ export const queryTool = {
4
+ name: "salesforce_query",
5
+ description: "Execute SOQL queries against any Salesforce object. Supports SELECT, WHERE, ORDER BY, LIMIT, and other SOQL features.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ query: {
10
+ type: "string",
11
+ description: "SOQL query string (e.g., 'SELECT Id, Name FROM Account WHERE Industry = \\'Technology\\' LIMIT 10'). Use proper SOQL syntax with single quotes for string literals."
12
+ }
13
+ },
14
+ required: ["query"]
15
+ }
16
+ };
17
+
18
+ export async function executeQuery(client, args) {
19
+ try {
20
+ const { query } = args;
21
+
22
+ // Check if installation has been learned
23
+ const hasDocumentation = await hasInstallationDocumentation();
24
+ if (!hasDocumentation) {
25
+ return {
26
+ content: [{
27
+ type: "text",
28
+ text: `⚠️ **Salesforce-Installation noch nicht gelernt**\n\n` +
29
+ `Bevor du SOQL-Abfragen ausführst, sollte die KI deine Salesforce-Installation kennenlernen.\n\n` +
30
+ `🚀 **Empfehlung:** Führe zuerst das \`salesforce_learn\` Tool aus, um alle verfügbaren Objekte und Felder zu analysieren.\n\n` +
31
+ `Danach kann ich dir bei intelligenten Abfragen helfen und die richtigen Feld- und Objektnamen vorschlagen.\n\n` +
32
+ `**Deine Abfrage wird trotzdem ausgeführt:**`
33
+ }]
34
+ };
35
+ }
36
+
37
+ if (!query || typeof query !== 'string') {
38
+ throw new Error('Query parameter is required and must be a string');
39
+ }
40
+
41
+ // Basic SOQL validation
42
+ if (!query.trim().toUpperCase().startsWith('SELECT')) {
43
+ throw new Error('Query must be a SELECT statement');
44
+ }
45
+
46
+ const result = await client.query(query);
47
+
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text",
52
+ text: `SOQL Query Results:\n\n` +
53
+ `Query: ${query}\n` +
54
+ `Total Records: ${result.totalSize}\n` +
55
+ `Records Returned: ${result.records.length}\n` +
56
+ `More Records Available: ${!result.done}\n\n` +
57
+ `Results:\n${JSON.stringify(result.records, null, 2)}`
58
+ }
59
+ ]
60
+ };
61
+ } catch (error) {
62
+ return {
63
+ content: [
64
+ {
65
+ type: "text",
66
+ text: `❌ Query failed: ${error.message}\n\n` +
67
+ `Query: ${args.query || 'N/A'}\n\n` +
68
+ `Common issues:\n` +
69
+ `- Check field names (case-sensitive)\n` +
70
+ `- Verify object name exists\n` +
71
+ `- Ensure you have read permissions\n` +
72
+ `- Use single quotes for string literals\n\n` +
73
+ `SOQL Syntax Help:\n` +
74
+ `- SELECT Id, Name FROM Account LIMIT 10\n` +
75
+ `- SELECT Id, Name FROM Contact WHERE Email != null\n` +
76
+ `- SELECT Id, Name FROM Opportunity WHERE Amount > 1000`
77
+ }
78
+ ],
79
+ isError: true
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,77 @@
1
+ import { FileStorageManager } from '../auth/file-storage.js';
2
+
3
+ /**
4
+ * Repair credentials that were accidentally set to null
5
+ * This is a one-time fix for the bug where credentials were overwritten with null
6
+ */
7
+ export async function repairCredentials() {
8
+ const fileStorage = new FileStorageManager();
9
+
10
+ try {
11
+ const data = await fileStorage.getAllData();
12
+
13
+ console.error('Current data:', data);
14
+
15
+ if (data.access_token && data.refresh_token && (!data.clientId || !data.clientSecret)) {
16
+ console.error('🔍 Found tokens without credentials - this is the bug!');
17
+
18
+ // We need to ask the user for the credentials
19
+ console.error('❗ We need to restore the missing credentials.');
20
+ console.error('Please provide the following information:');
21
+ console.error('1. Client ID (Consumer Key from your Connected App)');
22
+ console.error('2. Client Secret (Consumer Secret from your Connected App)');
23
+ console.error('');
24
+ console.error('You can find these in your Salesforce Setup > App Manager > Your Connected App > View');
25
+
26
+ // For now, we'll create a temporary fix that preserves the tokens
27
+ // but allows the system to work
28
+ const repairedData = {
29
+ ...data,
30
+ clientId: data.clientId || 'NEEDS_REPAIR',
31
+ clientSecret: data.clientSecret || 'NEEDS_REPAIR',
32
+ instanceUrl: data.instanceUrl || data.instance_url,
33
+ credentialsStoredAt: data.credentialsStoredAt || new Date().toISOString(),
34
+ repaired: true,
35
+ repairNote: 'Credentials were null due to bug - needs manual repair'
36
+ };
37
+
38
+ await fileStorage.storeCredentials({
39
+ clientId: 'NEEDS_REPAIR',
40
+ clientSecret: 'NEEDS_REPAIR',
41
+ instanceUrl: data.instanceUrl || data.instance_url
42
+ });
43
+
44
+ console.error('✅ Temporary repair applied. You can now use the system, but please update credentials.');
45
+
46
+ return {
47
+ success: true,
48
+ message: 'Temporary repair applied',
49
+ nextSteps: [
50
+ 'Get your Client ID and Client Secret from Salesforce Setup',
51
+ 'Run the setup tool to properly configure credentials',
52
+ 'Your existing tokens will be preserved'
53
+ ]
54
+ };
55
+ }
56
+
57
+ console.error('✅ No repair needed - credentials look correct');
58
+ return {
59
+ success: true,
60
+ message: 'No repair needed'
61
+ };
62
+
63
+ } catch (error) {
64
+ console.error('❌ Repair failed:', error.message);
65
+ return {
66
+ success: false,
67
+ error: error.message
68
+ };
69
+ }
70
+ }
71
+
72
+ // If run directly
73
+ if (import.meta.url === `file://${process.argv[1]}`) {
74
+ repairCredentials().then(result => {
75
+ console.error(JSON.stringify(result, null, 2));
76
+ });
77
+ }
@@ -0,0 +1,120 @@
1
+ import { FileStorageManager } from '../auth/file-storage.js';
2
+ import { logger } from '../utils/debug.js';
3
+
4
+ /**
5
+ * Setup tool for collecting Salesforce credentials
6
+ */
7
+ export const setupTool = {
8
+ name: 'salesforce_setup',
9
+ description: 'Configure Salesforce credentials (Client ID, Client Secret, Instance URL)',
10
+ inputSchema: {
11
+ type: 'object',
12
+ properties: {
13
+ clientId: {
14
+ type: 'string',
15
+ description: 'Salesforce Connected App Client ID (Consumer Key)'
16
+ },
17
+ clientSecret: {
18
+ type: 'string',
19
+ description: 'Salesforce Connected App Client Secret (Consumer Secret)'
20
+ },
21
+ instanceUrl: {
22
+ type: 'string',
23
+ description: 'Salesforce Instance URL (e.g., https://mycompany.salesforce.com)'
24
+ }
25
+ },
26
+ required: ['clientId', 'clientSecret', 'instanceUrl']
27
+ }
28
+ };
29
+
30
+ /**
31
+ * Handle Salesforce setup
32
+ * @param {Object} args - Setup arguments
33
+ * @returns {Object} Setup result
34
+ */
35
+ export async function handleSalesforceSetup(args) {
36
+ try {
37
+ logger.log('🔧 Setting up Salesforce credentials...');
38
+
39
+ const { clientId, clientSecret, instanceUrl } = args;
40
+
41
+ // Validate inputs
42
+ if (!clientId || !clientSecret || !instanceUrl) {
43
+ return {
44
+ success: false,
45
+ error: 'Missing required credentials. Please provide clientId, clientSecret, and instanceUrl.'
46
+ };
47
+ }
48
+
49
+ // Validate instance URL format
50
+ if (!instanceUrl.startsWith('https://') || !instanceUrl.includes('.salesforce.com')) {
51
+ return {
52
+ success: false,
53
+ error: 'Invalid instance URL format. Must be https://yourorg.salesforce.com'
54
+ };
55
+ }
56
+
57
+ // Clean up instance URL (remove trailing slash)
58
+ const cleanInstanceUrl = instanceUrl.replace(/\/$/, '');
59
+
60
+ // Store credentials
61
+ const fileStorage = new FileStorageManager();
62
+ await fileStorage.storeCredentials({
63
+ clientId,
64
+ clientSecret,
65
+ instanceUrl: cleanInstanceUrl
66
+ });
67
+
68
+ logger.log('✅ Salesforce credentials configured successfully');
69
+
70
+ return {
71
+ success: true,
72
+ message: 'Salesforce credentials have been configured successfully. You can now use other Salesforce tools.',
73
+ details: {
74
+ instanceUrl: cleanInstanceUrl,
75
+ clientIdPreview: clientId.substring(0, 20) + '...'
76
+ }
77
+ };
78
+
79
+ } catch (error) {
80
+ logger.error('❌ Setup failed:', error);
81
+ return {
82
+ success: false,
83
+ error: `Setup failed: ${error.message}`
84
+ };
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Check if credentials are configured
90
+ * @returns {Object} Configuration status
91
+ */
92
+ export async function checkCredentialsStatus() {
93
+ try {
94
+ const fileStorage = new FileStorageManager();
95
+ const credentials = await fileStorage.getCredentials();
96
+
97
+ if (!credentials) {
98
+ return {
99
+ configured: false,
100
+ message: 'No Salesforce credentials found. Please run the salesforce_setup tool first.'
101
+ };
102
+ }
103
+
104
+ return {
105
+ configured: true,
106
+ message: 'Salesforce credentials are configured.',
107
+ details: {
108
+ instanceUrl: credentials.instanceUrl,
109
+ clientIdPreview: credentials.clientId.substring(0, 20) + '...',
110
+ configuredAt: credentials.credentialsStoredAt
111
+ }
112
+ };
113
+
114
+ } catch (error) {
115
+ return {
116
+ configured: false,
117
+ error: `Error checking credentials: ${error.message}`
118
+ };
119
+ }
120
+ }