snow-flow 1.3.17 → 1.3.19
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/CLAUDE.md +56 -68
- package/README.md +28 -25
- package/dist/agents/index.js +1 -4
- package/dist/agents/queen-agent.js +3 -1
- package/dist/cli.js +93 -33
- package/dist/mcp/servicenow-deployment-mcp-refactored.js +3 -0
- package/dist/mcp/servicenow-deployment-mcp.js +135 -0
- package/dist/mcp/servicenow-flow-composer-mcp.js +208 -40
- package/dist/mcp/servicenow-platform-development-mcp.js +27 -2
- package/dist/mcp/servicenow-update-set-mcp-refactored.js +4 -0
- package/dist/mcp/servicenow-update-set-mcp.js +4 -0
- package/dist/mcp/servicenow-xml-flow-mcp.js +120 -7
- package/dist/types/index.js +0 -1
- package/dist/utils/mcp-server-manager.js +23 -4
- package/dist/utils/servicenow-client.js +17 -1
- package/dist/utils/snow-oauth.js +12 -5
- package/dist/utils/unified-auth-store.js +193 -0
- package/dist/utils/xml-first-flow-generator.js +23 -32
- package/package.json +1 -1
|
@@ -41,6 +41,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
41
41
|
exports.ServiceNowXMLFlowMCP = void 0;
|
|
42
42
|
const base_mcp_server_1 = require("./base-mcp-server");
|
|
43
43
|
const improved_flow_xml_generator_1 = __importStar(require("../utils/improved-flow-xml-generator"));
|
|
44
|
+
const xml_first_flow_generator_1 = require("../utils/xml-first-flow-generator"); // Keep for backward compatibility
|
|
44
45
|
const natural_language_mapper_1 = require("../api/natural-language-mapper");
|
|
45
46
|
const servicenow_id_generator_js_1 = require("../utils/servicenow-id-generator.js");
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
@@ -118,7 +119,7 @@ class ServiceNowXMLFlowMCP extends base_mcp_server_1.BaseMCPServer {
|
|
|
118
119
|
// Generate flow XML from natural language
|
|
119
120
|
this.registerTool({
|
|
120
121
|
name: 'snow_xml_flow_from_instruction',
|
|
121
|
-
description: '
|
|
122
|
+
description: '⚠️ DEPRECATED - Use snow_create_flow instead. This tool is kept for backwards compatibility only.',
|
|
122
123
|
inputSchema: {
|
|
123
124
|
type: 'object',
|
|
124
125
|
properties: {
|
|
@@ -130,6 +131,11 @@ class ServiceNowXMLFlowMCP extends base_mcp_server_1.BaseMCPServer {
|
|
|
130
131
|
type: 'boolean',
|
|
131
132
|
default: true,
|
|
132
133
|
description: 'Save XML to file'
|
|
134
|
+
},
|
|
135
|
+
auto_deploy: {
|
|
136
|
+
type: 'boolean',
|
|
137
|
+
default: true,
|
|
138
|
+
description: 'Automatically deploy XML to ServiceNow after generation (RECOMMENDED)'
|
|
133
139
|
}
|
|
134
140
|
},
|
|
135
141
|
required: ['instruction']
|
|
@@ -219,7 +225,7 @@ class ServiceNowXMLFlowMCP extends base_mcp_server_1.BaseMCPServer {
|
|
|
219
225
|
*/
|
|
220
226
|
async generateFlowFromInstruction(args) {
|
|
221
227
|
try {
|
|
222
|
-
const { instruction } = args;
|
|
228
|
+
const { instruction, auto_deploy = true } = args;
|
|
223
229
|
// Parse natural language to flow components
|
|
224
230
|
const flowRequirements = await this.nlMapper.parseFlowRequirements(instruction);
|
|
225
231
|
// Convert to IMPROVED flow definition
|
|
@@ -235,21 +241,128 @@ class ServiceNowXMLFlowMCP extends base_mcp_server_1.BaseMCPServer {
|
|
|
235
241
|
tags: ['auto-generated'],
|
|
236
242
|
activities: this.convertToImprovedActivities(flowRequirements)
|
|
237
243
|
};
|
|
238
|
-
// Use
|
|
239
|
-
const result = (0,
|
|
244
|
+
// Use PRODUCTION-READY generator with proper Update Set structure
|
|
245
|
+
const result = (0, xml_first_flow_generator_1.generateProductionFlowXML)(flowDef);
|
|
246
|
+
let deploymentResult = null;
|
|
247
|
+
// 🚀 AUTO-DEPLOYMENT: Deploy immediately if requested
|
|
248
|
+
if (auto_deploy) {
|
|
249
|
+
try {
|
|
250
|
+
this.logger.info('🚀 AUTO-DEPLOYING XML to ServiceNow...');
|
|
251
|
+
// Check authentication
|
|
252
|
+
const isAuth = await this.oauth.isAuthenticated();
|
|
253
|
+
if (!isAuth) {
|
|
254
|
+
throw new Error('Not authenticated with ServiceNow. Please run: snow-flow auth login');
|
|
255
|
+
}
|
|
256
|
+
// Initialize ServiceNow client
|
|
257
|
+
const client = new ServiceNowClient();
|
|
258
|
+
// Read the XML file
|
|
259
|
+
const fs = require('fs').promises;
|
|
260
|
+
const xmlContent = await fs.readFile(result.filePath, 'utf-8');
|
|
261
|
+
// Import XML as remote update set
|
|
262
|
+
const importResponse = await client.makeRequest({
|
|
263
|
+
method: 'POST',
|
|
264
|
+
url: '/api/now/table/sys_remote_update_set',
|
|
265
|
+
headers: {
|
|
266
|
+
'Content-Type': 'application/xml',
|
|
267
|
+
'Accept': 'application/json'
|
|
268
|
+
},
|
|
269
|
+
data: xmlContent
|
|
270
|
+
});
|
|
271
|
+
if (!importResponse.result || !importResponse.result.sys_id) {
|
|
272
|
+
throw new Error('Failed to import XML update set');
|
|
273
|
+
}
|
|
274
|
+
const remoteUpdateSetId = importResponse.result.sys_id;
|
|
275
|
+
this.logger.info(`✅ XML imported successfully (sys_id: ${remoteUpdateSetId})`);
|
|
276
|
+
// Load the update set
|
|
277
|
+
await client.makeRequest({
|
|
278
|
+
method: 'PUT',
|
|
279
|
+
url: `/api/now/table/sys_remote_update_set/${remoteUpdateSetId}`,
|
|
280
|
+
data: {
|
|
281
|
+
state: 'loaded'
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// Find the loaded update set
|
|
285
|
+
const loadedResponse = await client.makeRequest({
|
|
286
|
+
method: 'GET',
|
|
287
|
+
url: '/api/now/table/sys_update_set',
|
|
288
|
+
params: {
|
|
289
|
+
sysparm_query: `remote_sys_id=${remoteUpdateSetId}`,
|
|
290
|
+
sysparm_limit: 1
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
if (!loadedResponse.result || loadedResponse.result.length === 0) {
|
|
294
|
+
throw new Error('Failed to find loaded update set');
|
|
295
|
+
}
|
|
296
|
+
const updateSetId = loadedResponse.result[0].sys_id;
|
|
297
|
+
const updateSetName = loadedResponse.result[0].name;
|
|
298
|
+
// Preview the update set
|
|
299
|
+
await client.makeRequest({
|
|
300
|
+
method: 'POST',
|
|
301
|
+
url: `/api/now/table/sys_update_set/${updateSetId}/preview`
|
|
302
|
+
});
|
|
303
|
+
// Check for preview problems
|
|
304
|
+
const previewProblems = await client.makeRequest({
|
|
305
|
+
method: 'GET',
|
|
306
|
+
url: '/api/now/table/sys_update_preview_problem',
|
|
307
|
+
params: {
|
|
308
|
+
sysparm_query: `update_set=${updateSetId}`,
|
|
309
|
+
sysparm_limit: 100
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
if (previewProblems.result && previewProblems.result.length > 0) {
|
|
313
|
+
const problemsList = previewProblems.result.map((p) => `- ${p.type}: ${p.description}`).join('\n');
|
|
314
|
+
throw new Error(`Preview found problems:\n${problemsList}\n\nPlease review and resolve in ServiceNow UI`);
|
|
315
|
+
}
|
|
316
|
+
// Commit the update set
|
|
317
|
+
await client.makeRequest({
|
|
318
|
+
method: 'POST',
|
|
319
|
+
url: `/api/now/table/sys_update_set/${updateSetId}/commit`
|
|
320
|
+
});
|
|
321
|
+
deploymentResult = {
|
|
322
|
+
success: true,
|
|
323
|
+
message: '✅ XML automatically deployed to ServiceNow!',
|
|
324
|
+
update_set_id: updateSetId,
|
|
325
|
+
update_set_name: updateSetName,
|
|
326
|
+
steps_completed: [
|
|
327
|
+
'✅ XML imported as remote update set',
|
|
328
|
+
'✅ Update set loaded successfully',
|
|
329
|
+
'✅ Preview completed with no problems',
|
|
330
|
+
'✅ Update set committed successfully'
|
|
331
|
+
]
|
|
332
|
+
};
|
|
333
|
+
this.logger.info('✅ Auto-deployment successful', deploymentResult);
|
|
334
|
+
}
|
|
335
|
+
catch (deployError) {
|
|
336
|
+
this.logger.warn('⚠️ Auto-deployment failed, providing manual instructions', deployError);
|
|
337
|
+
deploymentResult = {
|
|
338
|
+
success: false,
|
|
339
|
+
error: deployError instanceof Error ? deployError.message : String(deployError),
|
|
340
|
+
manual_command: `snow-flow deploy-xml ${result.filePath}`,
|
|
341
|
+
troubleshooting: [
|
|
342
|
+
'1. Check ServiceNow authentication: snow-flow auth status',
|
|
343
|
+
'2. Verify admin permissions in ServiceNow',
|
|
344
|
+
'3. Review XML file for format issues',
|
|
345
|
+
'4. Try manual deployment command above'
|
|
346
|
+
]
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
240
350
|
return {
|
|
241
351
|
success: true,
|
|
242
352
|
xml: args.save_to_file === false ? result.xml : undefined,
|
|
243
353
|
file_path: result.filePath,
|
|
244
354
|
flow_definition: flowDef,
|
|
245
|
-
|
|
355
|
+
deployment: deploymentResult,
|
|
356
|
+
message: `✅ Generated IMPROVED flow XML from instruction: ${instruction}${deploymentResult?.success ? ' + DEPLOYED!' : ''}`,
|
|
246
357
|
improvements: [
|
|
247
358
|
'✅ Production-ready Flow Designer format',
|
|
248
359
|
'✅ Complete XML structure with all required fields',
|
|
249
360
|
'✅ Base64+gzip encoded action values',
|
|
250
|
-
'✅ Proper v2 table usage'
|
|
361
|
+
'✅ Proper v2 table usage',
|
|
362
|
+
...(deploymentResult?.success ? ['✅ Automatically deployed to ServiceNow!'] : [])
|
|
251
363
|
],
|
|
252
|
-
import_instructions: result.instructions
|
|
364
|
+
import_instructions: deploymentResult?.success ? 'Flow is already deployed and ready to use!' : result.instructions,
|
|
365
|
+
auto_deploy_command: deploymentResult?.manual_command || `snow-flow deploy-xml ${result.filePath}`
|
|
253
366
|
};
|
|
254
367
|
}
|
|
255
368
|
catch (error) {
|
package/dist/types/index.js
CHANGED
|
@@ -17,4 +17,3 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
// Re-export coordination framework types
|
|
19
19
|
__exportStar(require("./snow-flow.types"), exports);
|
|
20
|
-
__exportStar(require("../coordination/types"), exports);
|
|
@@ -12,6 +12,7 @@ const fs_1 = require("fs");
|
|
|
12
12
|
const path_1 = require("path");
|
|
13
13
|
const events_1 = require("events");
|
|
14
14
|
const os_1 = __importDefault(require("os"));
|
|
15
|
+
const unified_auth_store_js_1 = require("./unified-auth-store.js");
|
|
15
16
|
class MCPServerManager extends events_1.EventEmitter {
|
|
16
17
|
constructor(configPath) {
|
|
17
18
|
super();
|
|
@@ -172,15 +173,33 @@ class MCPServerManager extends events_1.EventEmitter {
|
|
|
172
173
|
}
|
|
173
174
|
// Check if script exists
|
|
174
175
|
await fs_1.promises.access(scriptPath);
|
|
175
|
-
//
|
|
176
|
+
// Bridge OAuth tokens to MCP servers
|
|
177
|
+
await unified_auth_store_js_1.unifiedAuthStore.bridgeToMCP();
|
|
178
|
+
// Get current tokens for the MCP server
|
|
179
|
+
const tokens = await unified_auth_store_js_1.unifiedAuthStore.getTokens();
|
|
180
|
+
const authEnv = {};
|
|
181
|
+
if (tokens) {
|
|
182
|
+
authEnv.SNOW_OAUTH_TOKENS = JSON.stringify(tokens);
|
|
183
|
+
authEnv.SNOW_INSTANCE = tokens.instance;
|
|
184
|
+
authEnv.SNOW_CLIENT_ID = tokens.clientId;
|
|
185
|
+
authEnv.SNOW_CLIENT_SECRET = tokens.clientSecret;
|
|
186
|
+
if (tokens.accessToken) {
|
|
187
|
+
authEnv.SNOW_ACCESS_TOKEN = tokens.accessToken;
|
|
188
|
+
}
|
|
189
|
+
if (tokens.refreshToken) {
|
|
190
|
+
authEnv.SNOW_REFRESH_TOKEN = tokens.refreshToken;
|
|
191
|
+
}
|
|
192
|
+
if (tokens.expiresAt) {
|
|
193
|
+
authEnv.SNOW_TOKEN_EXPIRES_AT = tokens.expiresAt;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Start the process with OAuth tokens
|
|
176
197
|
const childProcess = (0, child_process_1.spawn)('node', [scriptPath], {
|
|
177
198
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
178
199
|
detached: true,
|
|
179
200
|
env: {
|
|
180
201
|
...process.env,
|
|
181
|
-
|
|
182
|
-
SNOW_CLIENT_ID: process.env.SNOW_CLIENT_ID,
|
|
183
|
-
SNOW_CLIENT_SECRET: process.env.SNOW_CLIENT_SECRET
|
|
202
|
+
...authEnv
|
|
184
203
|
}
|
|
185
204
|
});
|
|
186
205
|
// Set up logging
|
|
@@ -15,6 +15,7 @@ const action_type_cache_1 = require("./action-type-cache");
|
|
|
15
15
|
const snow_flow_config_js_1 = require("../config/snow-flow-config.js");
|
|
16
16
|
const widget_template_generator_js_1 = require("./widget-template-generator.js");
|
|
17
17
|
const logger_1 = require("./logger");
|
|
18
|
+
const unified_auth_store_js_1 = require("./unified-auth-store.js");
|
|
18
19
|
const flow_structure_builder_1 = require("./flow-structure-builder");
|
|
19
20
|
class ServiceNowClient {
|
|
20
21
|
constructor() {
|
|
@@ -115,7 +116,22 @@ class ServiceNowClient {
|
|
|
115
116
|
*/
|
|
116
117
|
async ensureAuthenticated() {
|
|
117
118
|
if (!this.credentials) {
|
|
118
|
-
|
|
119
|
+
// Try unified auth store first (most reliable)
|
|
120
|
+
const tokens = await unified_auth_store_js_1.unifiedAuthStore.getTokens();
|
|
121
|
+
if (tokens) {
|
|
122
|
+
this.credentials = {
|
|
123
|
+
instance: tokens.instance,
|
|
124
|
+
clientId: tokens.clientId,
|
|
125
|
+
clientSecret: tokens.clientSecret,
|
|
126
|
+
accessToken: tokens.accessToken,
|
|
127
|
+
refreshToken: tokens.refreshToken,
|
|
128
|
+
expiresAt: tokens.expiresAt
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Fallback to OAuth loadCredentials
|
|
133
|
+
this.credentials = await this.oauth.loadCredentials();
|
|
134
|
+
}
|
|
119
135
|
}
|
|
120
136
|
if (!this.credentials) {
|
|
121
137
|
console.error('❌ No ServiceNow credentials found');
|
package/dist/utils/snow-oauth.js
CHANGED
|
@@ -18,6 +18,7 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
18
18
|
const net_1 = __importDefault(require("net"));
|
|
19
19
|
const crypto_1 = __importDefault(require("crypto"));
|
|
20
20
|
const snow_flow_config_js_1 = require("../config/snow-flow-config.js");
|
|
21
|
+
const unified_auth_store_js_1 = require("./unified-auth-store.js");
|
|
21
22
|
class ServiceNowOAuth {
|
|
22
23
|
constructor() {
|
|
23
24
|
// Store tokens in user's home directory
|
|
@@ -348,15 +349,16 @@ class ServiceNowOAuth {
|
|
|
348
349
|
*/
|
|
349
350
|
async saveTokens(tokenData) {
|
|
350
351
|
try {
|
|
351
|
-
const configDir = process.env.SNOW_FLOW_HOME || (0, path_1.join)(os_1.default.homedir(), '.snow-flow');
|
|
352
|
-
await fs_1.promises.mkdir(configDir, { recursive: true });
|
|
353
352
|
const expiresAt = new Date();
|
|
354
353
|
expiresAt.setSeconds(expiresAt.getSeconds() + tokenData.expiresIn);
|
|
355
354
|
const authData = {
|
|
356
355
|
...tokenData,
|
|
357
356
|
expiresAt: expiresAt.toISOString()
|
|
358
357
|
};
|
|
359
|
-
|
|
358
|
+
// Use unified auth store
|
|
359
|
+
await unified_auth_store_js_1.unifiedAuthStore.saveTokens(authData);
|
|
360
|
+
// Bridge to MCP servers immediately
|
|
361
|
+
await unified_auth_store_js_1.unifiedAuthStore.bridgeToMCP();
|
|
360
362
|
}
|
|
361
363
|
catch (error) {
|
|
362
364
|
console.error('Failed to save tokens:', error);
|
|
@@ -368,8 +370,7 @@ class ServiceNowOAuth {
|
|
|
368
370
|
*/
|
|
369
371
|
async loadTokens() {
|
|
370
372
|
try {
|
|
371
|
-
|
|
372
|
-
return JSON.parse(data);
|
|
373
|
+
return await unified_auth_store_js_1.unifiedAuthStore.getTokens();
|
|
373
374
|
}
|
|
374
375
|
catch (error) {
|
|
375
376
|
return null;
|
|
@@ -495,6 +496,12 @@ class ServiceNowOAuth {
|
|
|
495
496
|
console.log('No active session to logout from');
|
|
496
497
|
}
|
|
497
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Get stored OAuth tokens for use in other contexts (MCP servers)
|
|
501
|
+
*/
|
|
502
|
+
async getStoredTokens() {
|
|
503
|
+
return await this.loadTokens();
|
|
504
|
+
}
|
|
498
505
|
/**
|
|
499
506
|
* Load credentials (including tokens) with .env fallback
|
|
500
507
|
*/
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unified Authentication Store for ServiceNow
|
|
4
|
+
*
|
|
5
|
+
* Provides shared token storage accessible from both CLI and MCP contexts.
|
|
6
|
+
* Solves the token isolation problem between different execution contexts.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.unifiedAuthStore = exports.UnifiedAuthStore = void 0;
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const path_1 = require("path");
|
|
15
|
+
const os_1 = __importDefault(require("os"));
|
|
16
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
17
|
+
// Load environment variables
|
|
18
|
+
dotenv_1.default.config();
|
|
19
|
+
class UnifiedAuthStore {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.memoryStore = null;
|
|
22
|
+
// Use consistent path across all contexts
|
|
23
|
+
const configDir = process.env.SNOW_FLOW_HOME || (0, path_1.join)(os_1.default.homedir(), '.snow-flow');
|
|
24
|
+
this.tokenPath = (0, path_1.join)(configDir, 'auth.json');
|
|
25
|
+
// Also check environment for shared tokens (from MCP bridge)
|
|
26
|
+
if (process.env.SNOW_OAUTH_TOKENS) {
|
|
27
|
+
try {
|
|
28
|
+
this.memoryStore = JSON.parse(process.env.SNOW_OAUTH_TOKENS);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.error('Failed to parse SNOW_OAUTH_TOKENS from environment');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
static getInstance() {
|
|
36
|
+
if (!UnifiedAuthStore.instance) {
|
|
37
|
+
UnifiedAuthStore.instance = new UnifiedAuthStore();
|
|
38
|
+
}
|
|
39
|
+
return UnifiedAuthStore.instance;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get tokens from file or memory
|
|
43
|
+
*/
|
|
44
|
+
async getTokens() {
|
|
45
|
+
try {
|
|
46
|
+
// First check memory store (fastest)
|
|
47
|
+
if (this.memoryStore) {
|
|
48
|
+
return this.memoryStore;
|
|
49
|
+
}
|
|
50
|
+
// Then check file system
|
|
51
|
+
const data = await fs_1.promises.readFile(this.tokenPath, 'utf8');
|
|
52
|
+
const tokens = JSON.parse(data);
|
|
53
|
+
// Cache in memory for performance
|
|
54
|
+
this.memoryStore = tokens;
|
|
55
|
+
return tokens;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
// Fallback to environment variables
|
|
59
|
+
return this.getTokensFromEnv();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Save tokens to file and memory
|
|
64
|
+
*/
|
|
65
|
+
async saveTokens(tokens) {
|
|
66
|
+
try {
|
|
67
|
+
const configDir = (0, path_1.join)(os_1.default.homedir(), '.snow-flow');
|
|
68
|
+
await fs_1.promises.mkdir(configDir, { recursive: true });
|
|
69
|
+
await fs_1.promises.writeFile(this.tokenPath, JSON.stringify(tokens, null, 2));
|
|
70
|
+
// Update memory store
|
|
71
|
+
this.memoryStore = tokens;
|
|
72
|
+
// Update environment for child processes
|
|
73
|
+
process.env.SNOW_OAUTH_TOKENS = JSON.stringify(tokens);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('Failed to save tokens:', error);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get tokens from environment variables (fallback)
|
|
82
|
+
*/
|
|
83
|
+
getTokensFromEnv() {
|
|
84
|
+
const instance = process.env.SNOW_INSTANCE;
|
|
85
|
+
const clientId = process.env.SNOW_CLIENT_ID;
|
|
86
|
+
const clientSecret = process.env.SNOW_CLIENT_SECRET;
|
|
87
|
+
if (!instance || !clientId || !clientSecret) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
instance: instance.replace(/\/$/, ''),
|
|
92
|
+
clientId,
|
|
93
|
+
clientSecret,
|
|
94
|
+
accessToken: process.env.SNOW_ACCESS_TOKEN,
|
|
95
|
+
refreshToken: process.env.SNOW_REFRESH_TOKEN,
|
|
96
|
+
expiresAt: process.env.SNOW_TOKEN_EXPIRES_AT
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if tokens are valid and not expired
|
|
101
|
+
*/
|
|
102
|
+
async isAuthenticated() {
|
|
103
|
+
try {
|
|
104
|
+
const tokens = await this.getTokens();
|
|
105
|
+
if (!tokens || !tokens.accessToken) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
// Check expiration
|
|
109
|
+
if (tokens.expiresAt) {
|
|
110
|
+
const expiresAt = new Date(tokens.expiresAt);
|
|
111
|
+
const now = new Date();
|
|
112
|
+
return now < expiresAt;
|
|
113
|
+
}
|
|
114
|
+
// If no expiration, assume valid
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Clear all stored tokens
|
|
123
|
+
*/
|
|
124
|
+
async clearTokens() {
|
|
125
|
+
try {
|
|
126
|
+
await fs_1.promises.unlink(this.tokenPath);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// Ignore if file doesn't exist
|
|
130
|
+
}
|
|
131
|
+
// Clear memory and environment
|
|
132
|
+
this.memoryStore = null;
|
|
133
|
+
delete process.env.SNOW_OAUTH_TOKENS;
|
|
134
|
+
delete process.env.SNOW_ACCESS_TOKEN;
|
|
135
|
+
delete process.env.SNOW_REFRESH_TOKEN;
|
|
136
|
+
delete process.env.SNOW_TOKEN_EXPIRES_AT;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get ServiceNow instance URL
|
|
140
|
+
*/
|
|
141
|
+
async getInstanceUrl() {
|
|
142
|
+
const tokens = await this.getTokens();
|
|
143
|
+
if (!tokens) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
let instance = tokens.instance;
|
|
147
|
+
if (!instance.startsWith('http')) {
|
|
148
|
+
instance = `https://${instance}`;
|
|
149
|
+
}
|
|
150
|
+
if (!instance.endsWith('.service-now.com')) {
|
|
151
|
+
instance = `${instance}.service-now.com`;
|
|
152
|
+
}
|
|
153
|
+
return instance;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get headers for API requests
|
|
157
|
+
*/
|
|
158
|
+
async getAuthHeaders() {
|
|
159
|
+
const tokens = await this.getTokens();
|
|
160
|
+
if (!tokens || !tokens.accessToken) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
'Authorization': `Bearer ${tokens.accessToken}`,
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
'Accept': 'application/json'
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Bridge tokens to MCP servers via environment
|
|
171
|
+
*/
|
|
172
|
+
async bridgeToMCP() {
|
|
173
|
+
const tokens = await this.getTokens();
|
|
174
|
+
if (tokens) {
|
|
175
|
+
process.env.SNOW_OAUTH_TOKENS = JSON.stringify(tokens);
|
|
176
|
+
process.env.SNOW_INSTANCE = tokens.instance;
|
|
177
|
+
process.env.SNOW_CLIENT_ID = tokens.clientId;
|
|
178
|
+
process.env.SNOW_CLIENT_SECRET = tokens.clientSecret;
|
|
179
|
+
if (tokens.accessToken) {
|
|
180
|
+
process.env.SNOW_ACCESS_TOKEN = tokens.accessToken;
|
|
181
|
+
}
|
|
182
|
+
if (tokens.refreshToken) {
|
|
183
|
+
process.env.SNOW_REFRESH_TOKEN = tokens.refreshToken;
|
|
184
|
+
}
|
|
185
|
+
if (tokens.expiresAt) {
|
|
186
|
+
process.env.SNOW_TOKEN_EXPIRES_AT = tokens.expiresAt;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
exports.UnifiedAuthStore = UnifiedAuthStore;
|
|
192
|
+
// Export singleton instance
|
|
193
|
+
exports.unifiedAuthStore = UnifiedAuthStore.getInstance();
|
|
@@ -425,47 +425,38 @@ class XMLFirstFlowGenerator {
|
|
|
425
425
|
}
|
|
426
426
|
exports.XMLFirstFlowGenerator = XMLFirstFlowGenerator;
|
|
427
427
|
/**
|
|
428
|
-
* Generate PRODUCTION-READY flow XML
|
|
428
|
+
* Generate PRODUCTION-READY flow XML with BOTH formats
|
|
429
429
|
*/
|
|
430
430
|
function generateProductionFlowXML(flowDef) {
|
|
431
431
|
const generator = new XMLFirstFlowGenerator(flowDef.name.replace(/[^a-zA-Z0-9]+/g, '_') + '_Import');
|
|
432
|
+
// Generate Update Set XML (better for automatic deployment)
|
|
432
433
|
const xml = generator.generateFlowUpdateSetXML(flowDef);
|
|
433
434
|
const filePath = generator.saveToFile(xml, flowDef.name.toLowerCase().replace(/[^a-z0-9]+/g, '_') + '_flow.xml');
|
|
434
435
|
const instructions = `
|
|
435
|
-
=== ServiceNow Flow
|
|
436
|
+
=== ServiceNow Flow Deployment Options ===
|
|
436
437
|
|
|
437
|
-
|
|
438
|
+
🚀 OPTION 1: Automatic Deployment (RECOMMENDED)
|
|
439
|
+
Use the snow-flow deploy-xml command for complete automation:
|
|
440
|
+
|
|
441
|
+
snow-flow deploy-xml ${filePath}
|
|
442
|
+
|
|
443
|
+
This automatically:
|
|
444
|
+
✅ Imports the Update Set
|
|
445
|
+
✅ Previews for conflicts
|
|
446
|
+
✅ Commits if clean
|
|
447
|
+
✅ Reports any issues
|
|
438
448
|
|
|
439
|
-
2
|
|
440
|
-
|
|
449
|
+
🔧 OPTION 2: Manual Import (if automatic fails)
|
|
450
|
+
|
|
451
|
+
1. Log into ServiceNow as admin
|
|
452
|
+
2. Navigate to: System Update Sets > Local Update Sets
|
|
453
|
+
3. Click "Import Update Set from XML"
|
|
454
|
+
4. Choose file: ${filePath}
|
|
455
|
+
5. Click "Upload" and follow prompts
|
|
456
|
+
6. Preview and commit the Update Set
|
|
457
|
+
7. Find your flow in Flow Designer
|
|
441
458
|
|
|
442
|
-
|
|
443
|
-
(NOT the "Import from XML" in the context menu!)
|
|
444
|
-
|
|
445
|
-
4. Choose file: ${filePath}
|
|
446
|
-
|
|
447
|
-
5. Click "Upload"
|
|
448
|
-
|
|
449
|
-
6. Find your imported Update Set in the list
|
|
450
|
-
|
|
451
|
-
7. Click on the Update Set name to open it
|
|
452
|
-
|
|
453
|
-
8. Click "Preview Update Set"
|
|
454
|
-
- Review any errors or warnings
|
|
455
|
-
- Resolve any missing dependencies
|
|
456
|
-
|
|
457
|
-
9. Once preview is clean, click "Commit Update Set"
|
|
458
|
-
|
|
459
|
-
10. Navigate to Flow Designer:
|
|
460
|
-
- All > Flow Designer > Designer
|
|
461
|
-
- Your flow "${flowDef.name}" should appear in the list
|
|
462
|
-
|
|
463
|
-
11. Open the flow to verify all components are present
|
|
464
|
-
|
|
465
|
-
TROUBLESHOOTING:
|
|
466
|
-
- If flow appears empty: Check that all sys_update_xml records were imported
|
|
467
|
-
- If import fails: Verify you're using "Import Update Set from XML" link
|
|
468
|
-
- For dependency errors: Import required plugins/applications first
|
|
459
|
+
The flow "${flowDef.name}" will appear in Flow Designer > Designer
|
|
469
460
|
`.trim();
|
|
470
461
|
return { xml, filePath, instructions };
|
|
471
462
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-flow",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.19",
|
|
4
4
|
"description": "ServiceNow Queen Agent - Hive-Mind Intelligence for ServiceNow Development inspired by claude-flow. Transform complex workflows into elegant one-command orchestration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|