snow-flow 1.1.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/.env.example +49 -0
- package/.mcp.json +106 -0
- package/.mcp.json.template +106 -0
- package/LICENSE +21 -0
- package/README.md +236 -0
- package/bin/snow-flow +4 -0
- package/bin/snow-flow.js +3 -0
- package/claude-flow +79 -0
- package/claude-flow.bat +18 -0
- package/claude-flow.config.json +20 -0
- package/claude-flow.ps1 +24 -0
- package/dist/agents/app-creator.agent.d.ts +55 -0
- package/dist/agents/app-creator.agent.d.ts.map +1 -0
- package/dist/agents/app-creator.agent.js +1292 -0
- package/dist/agents/app-creator.agent.js.map +1 -0
- package/dist/agents/base-snow-agent.d.ts +63 -0
- package/dist/agents/base-snow-agent.d.ts.map +1 -0
- package/dist/agents/base-snow-agent.js +296 -0
- package/dist/agents/base-snow-agent.js.map +1 -0
- package/dist/agents/script-generator.agent.d.ts +32 -0
- package/dist/agents/script-generator.agent.d.ts.map +1 -0
- package/dist/agents/script-generator.agent.js +772 -0
- package/dist/agents/script-generator.agent.js.map +1 -0
- package/dist/agents/ui-builder.agent.d.ts +50 -0
- package/dist/agents/ui-builder.agent.d.ts.map +1 -0
- package/dist/agents/ui-builder.agent.js +1539 -0
- package/dist/agents/ui-builder.agent.js.map +1 -0
- package/dist/agents/widget-builder.agent.d.ts +32 -0
- package/dist/agents/widget-builder.agent.d.ts.map +1 -0
- package/dist/agents/widget-builder.agent.js +439 -0
- package/dist/agents/widget-builder.agent.js.map +1 -0
- package/dist/agents/workflow-designer.agent.d.ts +31 -0
- package/dist/agents/workflow-designer.agent.d.ts.map +1 -0
- package/dist/agents/workflow-designer.agent.js +583 -0
- package/dist/agents/workflow-designer.agent.js.map +1 -0
- package/dist/api/error-handling.d.ts +214 -0
- package/dist/api/error-handling.d.ts.map +1 -0
- package/dist/api/error-handling.js +594 -0
- package/dist/api/error-handling.js.map +1 -0
- package/dist/api/flow-designer-api.d.ts +167 -0
- package/dist/api/flow-designer-api.d.ts.map +1 -0
- package/dist/api/flow-designer-api.js +555 -0
- package/dist/api/flow-designer-api.js.map +1 -0
- package/dist/api/natural-language-mapper.d.ts +126 -0
- package/dist/api/natural-language-mapper.d.ts.map +1 -0
- package/dist/api/natural-language-mapper.js +498 -0
- package/dist/api/natural-language-mapper.js.map +1 -0
- package/dist/api/performance-optimizer.d.ts +240 -0
- package/dist/api/performance-optimizer.d.ts.map +1 -0
- package/dist/api/performance-optimizer.js +701 -0
- package/dist/api/performance-optimizer.js.map +1 -0
- package/dist/api/transaction-manager.d.ts +189 -0
- package/dist/api/transaction-manager.d.ts.map +1 -0
- package/dist/api/transaction-manager.js +576 -0
- package/dist/api/transaction-manager.js.map +1 -0
- package/dist/cli/deploy-artifact.d.ts +7 -0
- package/dist/cli/deploy-artifact.d.ts.map +1 -0
- package/dist/cli/deploy-artifact.js +279 -0
- package/dist/cli/deploy-artifact.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2057 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/managers/scope-manager.d.ts +75 -0
- package/dist/managers/scope-manager.d.ts.map +1 -0
- package/dist/managers/scope-manager.js +448 -0
- package/dist/managers/scope-manager.js.map +1 -0
- package/dist/mcp/mcp-wrapper.d.ts +8 -0
- package/dist/mcp/mcp-wrapper.d.ts.map +1 -0
- package/dist/mcp/mcp-wrapper.js +77 -0
- package/dist/mcp/mcp-wrapper.js.map +1 -0
- package/dist/mcp/servicenow-automation-mcp.d.ts +8 -0
- package/dist/mcp/servicenow-automation-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-automation-mcp.js +683 -0
- package/dist/mcp/servicenow-automation-mcp.js.map +1 -0
- package/dist/mcp/servicenow-deployment-mcp.d.ts +7 -0
- package/dist/mcp/servicenow-deployment-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-deployment-mcp.js +972 -0
- package/dist/mcp/servicenow-deployment-mcp.js.map +1 -0
- package/dist/mcp/servicenow-flow-composer-mcp.d.ts +7 -0
- package/dist/mcp/servicenow-flow-composer-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-flow-composer-mcp.js +1121 -0
- package/dist/mcp/servicenow-flow-composer-mcp.js.map +1 -0
- package/dist/mcp/servicenow-graph-memory-mcp.d.ts +7 -0
- package/dist/mcp/servicenow-graph-memory-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-graph-memory-mcp.js +703 -0
- package/dist/mcp/servicenow-graph-memory-mcp.js.map +1 -0
- package/dist/mcp/servicenow-integration-mcp.d.ts +8 -0
- package/dist/mcp/servicenow-integration-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-integration-mcp.js +689 -0
- package/dist/mcp/servicenow-integration-mcp.js.map +1 -0
- package/dist/mcp/servicenow-intelligent-mcp.d.ts +7 -0
- package/dist/mcp/servicenow-intelligent-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-intelligent-mcp.js +1138 -0
- package/dist/mcp/servicenow-intelligent-mcp.js.map +1 -0
- package/dist/mcp/servicenow-mcp-server.d.ts +37 -0
- package/dist/mcp/servicenow-mcp-server.d.ts.map +1 -0
- package/dist/mcp/servicenow-mcp-server.js +648 -0
- package/dist/mcp/servicenow-mcp-server.js.map +1 -0
- package/dist/mcp/servicenow-operations-mcp.d.ts +15 -0
- package/dist/mcp/servicenow-operations-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-operations-mcp.js +1287 -0
- package/dist/mcp/servicenow-operations-mcp.js.map +1 -0
- package/dist/mcp/servicenow-platform-development-mcp.d.ts +8 -0
- package/dist/mcp/servicenow-platform-development-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-platform-development-mcp.js +557 -0
- package/dist/mcp/servicenow-platform-development-mcp.js.map +1 -0
- package/dist/mcp/servicenow-progressive-indexer.d.ts +79 -0
- package/dist/mcp/servicenow-progressive-indexer.d.ts.map +1 -0
- package/dist/mcp/servicenow-progressive-indexer.js +320 -0
- package/dist/mcp/servicenow-progressive-indexer.js.map +1 -0
- package/dist/mcp/servicenow-reporting-analytics-mcp.d.ts +8 -0
- package/dist/mcp/servicenow-reporting-analytics-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-reporting-analytics-mcp.js +888 -0
- package/dist/mcp/servicenow-reporting-analytics-mcp.js.map +1 -0
- package/dist/mcp/servicenow-security-compliance-mcp.d.ts +8 -0
- package/dist/mcp/servicenow-security-compliance-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-security-compliance-mcp.js +812 -0
- package/dist/mcp/servicenow-security-compliance-mcp.js.map +1 -0
- package/dist/mcp/servicenow-update-set-mcp.d.ts +7 -0
- package/dist/mcp/servicenow-update-set-mcp.d.ts.map +1 -0
- package/dist/mcp/servicenow-update-set-mcp.js +599 -0
- package/dist/mcp/servicenow-update-set-mcp.js.map +1 -0
- package/dist/mcp/start-all-mcp-servers.d.ts +6 -0
- package/dist/mcp/start-all-mcp-servers.d.ts.map +1 -0
- package/dist/mcp/start-all-mcp-servers.js +65 -0
- package/dist/mcp/start-all-mcp-servers.js.map +1 -0
- package/dist/mcp/start-servicenow-mcp.d.ts +7 -0
- package/dist/mcp/start-servicenow-mcp.d.ts.map +1 -0
- package/dist/mcp/start-servicenow-mcp.js +64 -0
- package/dist/mcp/start-servicenow-mcp.js.map +1 -0
- package/dist/mcp/test-simple-mcp.d.ts +6 -0
- package/dist/mcp/test-simple-mcp.d.ts.map +1 -0
- package/dist/mcp/test-simple-mcp.js +64 -0
- package/dist/mcp/test-simple-mcp.js.map +1 -0
- package/dist/orchestrator/flow-composer.d.ts +269 -0
- package/dist/orchestrator/flow-composer.d.ts.map +1 -0
- package/dist/orchestrator/flow-composer.js +2221 -0
- package/dist/orchestrator/flow-composer.js.map +1 -0
- package/dist/orchestrator/flow-pattern-templates.d.ts +260 -0
- package/dist/orchestrator/flow-pattern-templates.d.ts.map +1 -0
- package/dist/orchestrator/flow-pattern-templates.js +906 -0
- package/dist/orchestrator/flow-pattern-templates.js.map +1 -0
- package/dist/orchestrator/flow-subflow-decision-engine.d.ts +202 -0
- package/dist/orchestrator/flow-subflow-decision-engine.d.ts.map +1 -0
- package/dist/orchestrator/flow-subflow-decision-engine.js +769 -0
- package/dist/orchestrator/flow-subflow-decision-engine.js.map +1 -0
- package/dist/orchestrator/flow-validation-engine.d.ts +145 -0
- package/dist/orchestrator/flow-validation-engine.d.ts.map +1 -0
- package/dist/orchestrator/flow-validation-engine.js +663 -0
- package/dist/orchestrator/flow-validation-engine.js.map +1 -0
- package/dist/orchestrator/snow-orchestrator.d.ts +49 -0
- package/dist/orchestrator/snow-orchestrator.d.ts.map +1 -0
- package/dist/orchestrator/snow-orchestrator.js +235 -0
- package/dist/orchestrator/snow-orchestrator.js.map +1 -0
- package/dist/orchestrator/subflow-creation-handler.d.ts +230 -0
- package/dist/orchestrator/subflow-creation-handler.d.ts.map +1 -0
- package/dist/orchestrator/subflow-creation-handler.js +791 -0
- package/dist/orchestrator/subflow-creation-handler.js.map +1 -0
- package/dist/strategies/global-scope-strategy.d.ts +123 -0
- package/dist/strategies/global-scope-strategy.d.ts.map +1 -0
- package/dist/strategies/global-scope-strategy.js +585 -0
- package/dist/strategies/global-scope-strategy.js.map +1 -0
- package/dist/types/index.d.ts +138 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/servicenow.types.d.ts +260 -0
- package/dist/types/servicenow.types.d.ts.map +1 -0
- package/dist/types/servicenow.types.js +3 -0
- package/dist/types/servicenow.types.js.map +1 -0
- package/dist/types/snow-flow.types.d.ts +114 -0
- package/dist/types/snow-flow.types.d.ts.map +1 -0
- package/dist/types/snow-flow.types.js +3 -0
- package/dist/types/snow-flow.types.js.map +1 -0
- package/dist/utils/action-type-cache.d.ts +66 -0
- package/dist/utils/action-type-cache.d.ts.map +1 -0
- package/dist/utils/action-type-cache.js +237 -0
- package/dist/utils/action-type-cache.js.map +1 -0
- package/dist/utils/agent-detector.d.ts +35 -0
- package/dist/utils/agent-detector.d.ts.map +1 -0
- package/dist/utils/agent-detector.js +353 -0
- package/dist/utils/agent-detector.js.map +1 -0
- package/dist/utils/flow-api-discovery.d.ts +151 -0
- package/dist/utils/flow-api-discovery.d.ts.map +1 -0
- package/dist/utils/flow-api-discovery.js +440 -0
- package/dist/utils/flow-api-discovery.js.map +1 -0
- package/dist/utils/flow-xml-generator.d.ts +41 -0
- package/dist/utils/flow-xml-generator.d.ts.map +1 -0
- package/dist/utils/flow-xml-generator.js +258 -0
- package/dist/utils/flow-xml-generator.js.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +51 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/mcp-auth-middleware.d.ts +58 -0
- package/dist/utils/mcp-auth-middleware.d.ts.map +1 -0
- package/dist/utils/mcp-auth-middleware.js +227 -0
- package/dist/utils/mcp-auth-middleware.js.map +1 -0
- package/dist/utils/mcp-config-manager.d.ts +126 -0
- package/dist/utils/mcp-config-manager.d.ts.map +1 -0
- package/dist/utils/mcp-config-manager.js +209 -0
- package/dist/utils/mcp-config-manager.js.map +1 -0
- package/dist/utils/mcp-server-manager.d.ts +91 -0
- package/dist/utils/mcp-server-manager.d.ts.map +1 -0
- package/dist/utils/mcp-server-manager.js +338 -0
- package/dist/utils/mcp-server-manager.js.map +1 -0
- package/dist/utils/scope-utils.d.ts +72 -0
- package/dist/utils/scope-utils.d.ts.map +1 -0
- package/dist/utils/scope-utils.js +386 -0
- package/dist/utils/scope-utils.js.map +1 -0
- package/dist/utils/servicenow-client.d.ts +233 -0
- package/dist/utils/servicenow-client.d.ts.map +1 -0
- package/dist/utils/servicenow-client.js +1465 -0
- package/dist/utils/servicenow-client.js.map +1 -0
- package/dist/utils/snow-oauth-old.d.ts +65 -0
- package/dist/utils/snow-oauth-old.d.ts.map +1 -0
- package/dist/utils/snow-oauth-old.js +345 -0
- package/dist/utils/snow-oauth-old.js.map +1 -0
- package/dist/utils/snow-oauth.d.ts +85 -0
- package/dist/utils/snow-oauth.d.ts.map +1 -0
- package/dist/utils/snow-oauth.js +512 -0
- package/dist/utils/snow-oauth.js.map +1 -0
- package/dist/utils/template-engine.d.ts +76 -0
- package/dist/utils/template-engine.d.ts.map +1 -0
- package/dist/utils/template-engine.js +391 -0
- package/dist/utils/template-engine.js.map +1 -0
- package/dist/version.d.ts +35 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +72 -0
- package/dist/version.js.map +1 -0
- package/mcp-wrapper.sh +12 -0
- package/package.json +77 -0
- package/reports/swarm-auto-centralized-1752649029776.json +13 -0
- package/scripts/postinstall.js +30 -0
- package/scripts/setup-mcp.js +89 -0
|
@@ -0,0 +1,1465 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* ServiceNow API Client
|
|
5
|
+
* Handles all ServiceNow API operations with OAuth authentication
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.ServiceNowClient = void 0;
|
|
12
|
+
const axios_1 = __importDefault(require("axios"));
|
|
13
|
+
const snow_oauth_1 = require("./snow-oauth");
|
|
14
|
+
const action_type_cache_1 = require("./action-type-cache");
|
|
15
|
+
class ServiceNowClient {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.credentials = null;
|
|
18
|
+
this.oauth = new snow_oauth_1.ServiceNowOAuth();
|
|
19
|
+
this.client = axios_1.default.create({
|
|
20
|
+
timeout: 30000,
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
'Accept': 'application/json'
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
this.actionTypeCache = new action_type_cache_1.ActionTypeCache(this);
|
|
27
|
+
// Add request interceptor for authentication
|
|
28
|
+
this.client.interceptors.request.use(async (config) => {
|
|
29
|
+
await this.ensureAuthenticated();
|
|
30
|
+
if (this.credentials?.accessToken) {
|
|
31
|
+
config.headers['Authorization'] = `Bearer ${this.credentials.accessToken}`;
|
|
32
|
+
}
|
|
33
|
+
return config;
|
|
34
|
+
});
|
|
35
|
+
// Add response interceptor for error handling
|
|
36
|
+
this.client.interceptors.response.use((response) => response, async (error) => {
|
|
37
|
+
if (error.response?.status === 401) {
|
|
38
|
+
console.log('🔄 Access token expired, refreshing...');
|
|
39
|
+
const refreshResult = await this.oauth.refreshAccessToken();
|
|
40
|
+
if (refreshResult.success && refreshResult.accessToken) {
|
|
41
|
+
// Update the authorization header with new token
|
|
42
|
+
error.config.headers['Authorization'] = `Bearer ${refreshResult.accessToken}`;
|
|
43
|
+
// Retry the original request
|
|
44
|
+
return this.client.request(error.config);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return Promise.reject(error);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Ensure we have valid authentication
|
|
52
|
+
*/
|
|
53
|
+
async ensureAuthenticated() {
|
|
54
|
+
console.log('🔐 Checking authentication...');
|
|
55
|
+
if (!this.credentials) {
|
|
56
|
+
console.log('🔍 Loading credentials...');
|
|
57
|
+
this.credentials = await this.oauth.loadCredentials();
|
|
58
|
+
}
|
|
59
|
+
if (!this.credentials) {
|
|
60
|
+
console.error('❌ No credentials found');
|
|
61
|
+
throw new Error('No ServiceNow credentials found. Please run "snow-flow auth login" first.');
|
|
62
|
+
}
|
|
63
|
+
console.log('✅ Credentials loaded, checking if authenticated...');
|
|
64
|
+
const isAuth = await this.oauth.isAuthenticated();
|
|
65
|
+
if (!isAuth) {
|
|
66
|
+
console.error('❌ Authentication expired');
|
|
67
|
+
throw new Error('ServiceNow authentication expired. Please run "snow-flow auth login" again.');
|
|
68
|
+
}
|
|
69
|
+
console.log('✅ Authentication successful');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get base URL for ServiceNow instance
|
|
73
|
+
*/
|
|
74
|
+
getBaseUrl() {
|
|
75
|
+
if (!this.credentials) {
|
|
76
|
+
throw new Error('No credentials available');
|
|
77
|
+
}
|
|
78
|
+
return `https://${this.credentials.instance}`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Sanitize a flow name for use as internal_name
|
|
82
|
+
*/
|
|
83
|
+
sanitizeInternalName(name) {
|
|
84
|
+
return name
|
|
85
|
+
.toLowerCase()
|
|
86
|
+
.replace(/[^a-z0-9_\s]/g, '') // Remove special characters except underscores and spaces
|
|
87
|
+
.replace(/\s+/g, '_') // Replace spaces with underscores
|
|
88
|
+
.replace(/_+/g, '_') // Replace multiple underscores with single
|
|
89
|
+
.replace(/^_|_$/g, '') // Remove leading/trailing underscores
|
|
90
|
+
.substring(0, 80); // Limit length to 80 characters
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Test connection to ServiceNow
|
|
94
|
+
*/
|
|
95
|
+
async testConnection() {
|
|
96
|
+
try {
|
|
97
|
+
// Ensure we have credentials first
|
|
98
|
+
await this.ensureAuthenticated();
|
|
99
|
+
// Use the /api/now/v2/table/sys_user?sysparm_limit=1 endpoint to test
|
|
100
|
+
// This is a more reliable endpoint that should work on all instances
|
|
101
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/v2/table/sys_user?sysparm_limit=1&sysparm_query=user_name=admin`);
|
|
102
|
+
// If we can query users, we're connected
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
data: {
|
|
106
|
+
name: 'ServiceNow Instance',
|
|
107
|
+
user_name: 'Connected',
|
|
108
|
+
email: `${this.credentials?.instance}`,
|
|
109
|
+
message: 'Connection successful'
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
// Try a simpler endpoint if the first one fails
|
|
115
|
+
try {
|
|
116
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_properties?sysparm_limit=1`);
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
data: {
|
|
120
|
+
name: 'ServiceNow Instance',
|
|
121
|
+
user_name: 'Connected',
|
|
122
|
+
email: `${this.credentials?.instance}`,
|
|
123
|
+
message: 'Connection successful (limited access)'
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (secondError) {
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
error: error instanceof Error ? error.message : String(error)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Create a new ServiceNow widget
|
|
137
|
+
*/
|
|
138
|
+
async createWidget(widget) {
|
|
139
|
+
try {
|
|
140
|
+
console.log('🎨 Creating ServiceNow widget...');
|
|
141
|
+
console.log(`📋 Widget Name: ${widget.name}`);
|
|
142
|
+
// Ensure we have credentials before making the API call
|
|
143
|
+
await this.ensureAuthenticated();
|
|
144
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sp_widget`, {
|
|
145
|
+
name: widget.name,
|
|
146
|
+
id: widget.id,
|
|
147
|
+
title: widget.title,
|
|
148
|
+
description: widget.description,
|
|
149
|
+
template: widget.template,
|
|
150
|
+
css: widget.css,
|
|
151
|
+
client_script: widget.client_script,
|
|
152
|
+
server_script: widget.server_script,
|
|
153
|
+
option_schema: widget.option_schema || '[]',
|
|
154
|
+
demo_data: widget.demo_data || '{}',
|
|
155
|
+
has_preview: widget.has_preview || false,
|
|
156
|
+
category: widget.category || 'custom'
|
|
157
|
+
});
|
|
158
|
+
console.log('✅ Widget created successfully!');
|
|
159
|
+
console.log(`🆔 Widget ID: ${response.data.result.sys_id}`);
|
|
160
|
+
return {
|
|
161
|
+
success: true,
|
|
162
|
+
data: response.data.result
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.error('❌ Failed to create widget:', error);
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
error: error instanceof Error ? error.message : String(error)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Update an existing ServiceNow widget
|
|
175
|
+
*/
|
|
176
|
+
async updateWidget(sysId, widget) {
|
|
177
|
+
try {
|
|
178
|
+
console.log(`🔄 Updating widget ${sysId}...`);
|
|
179
|
+
// Ensure we have credentials before making the API call
|
|
180
|
+
await this.ensureAuthenticated();
|
|
181
|
+
const response = await this.client.patch(`${this.getBaseUrl()}/api/now/table/sp_widget/${sysId}`, widget);
|
|
182
|
+
console.log('✅ Widget updated successfully!');
|
|
183
|
+
return {
|
|
184
|
+
success: true,
|
|
185
|
+
data: response.data.result
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
console.error('❌ Failed to update widget:', error);
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
error: error instanceof Error ? error.message : String(error)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get widget by ID
|
|
198
|
+
*/
|
|
199
|
+
async getWidget(widgetId) {
|
|
200
|
+
try {
|
|
201
|
+
// Ensure we have credentials before making the API call
|
|
202
|
+
await this.ensureAuthenticated();
|
|
203
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sp_widget?sysparm_query=id=${widgetId}`);
|
|
204
|
+
if (response.data.result.length === 0) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: 'Widget not found'
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
success: true,
|
|
212
|
+
data: response.data.result[0]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
error: error instanceof Error ? error.message : String(error)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Create a new ServiceNow workflow
|
|
224
|
+
*/
|
|
225
|
+
async createWorkflow(workflow) {
|
|
226
|
+
try {
|
|
227
|
+
console.log('🔄 Creating ServiceNow workflow...');
|
|
228
|
+
console.log(`📋 Workflow Name: ${workflow.name}`);
|
|
229
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/wf_workflow`, {
|
|
230
|
+
name: workflow.name,
|
|
231
|
+
description: workflow.description,
|
|
232
|
+
active: workflow.active,
|
|
233
|
+
workflow_version: workflow.workflow_version,
|
|
234
|
+
table: workflow.table || '',
|
|
235
|
+
condition: workflow.condition || ''
|
|
236
|
+
});
|
|
237
|
+
console.log('✅ Workflow created successfully!');
|
|
238
|
+
console.log(`🆔 Workflow ID: ${response.data.result.sys_id}`);
|
|
239
|
+
return {
|
|
240
|
+
success: true,
|
|
241
|
+
data: response.data.result
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
console.error('❌ Failed to create workflow:', error);
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
error: error instanceof Error ? error.message : String(error)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Create a new ServiceNow application
|
|
254
|
+
*/
|
|
255
|
+
async createApplication(application) {
|
|
256
|
+
try {
|
|
257
|
+
console.log('🏗️ Creating ServiceNow application...');
|
|
258
|
+
console.log(`📋 Application Name: ${application.name}`);
|
|
259
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_app`, {
|
|
260
|
+
name: application.name,
|
|
261
|
+
scope: application.scope,
|
|
262
|
+
version: application.version,
|
|
263
|
+
short_description: application.short_description,
|
|
264
|
+
description: application.description,
|
|
265
|
+
vendor: application.vendor,
|
|
266
|
+
vendor_prefix: application.vendor_prefix,
|
|
267
|
+
template: application.template || '',
|
|
268
|
+
logo: application.logo || '',
|
|
269
|
+
active: application.active
|
|
270
|
+
});
|
|
271
|
+
console.log('✅ Application created successfully!');
|
|
272
|
+
console.log(`🆔 Application ID: ${response.data.result.sys_id}`);
|
|
273
|
+
return {
|
|
274
|
+
success: true,
|
|
275
|
+
data: response.data.result
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.error('❌ Failed to create application:', error);
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
error: error instanceof Error ? error.message : String(error)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Execute a ServiceNow script
|
|
288
|
+
*/
|
|
289
|
+
async executeScript(script) {
|
|
290
|
+
try {
|
|
291
|
+
console.log('⚡ Executing ServiceNow script...');
|
|
292
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_script_execution`, {
|
|
293
|
+
script: script,
|
|
294
|
+
type: 'server'
|
|
295
|
+
});
|
|
296
|
+
console.log('✅ Script executed successfully!');
|
|
297
|
+
return {
|
|
298
|
+
success: true,
|
|
299
|
+
data: response.data.result
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error('❌ Failed to execute script:', error);
|
|
304
|
+
return {
|
|
305
|
+
success: false,
|
|
306
|
+
error: error instanceof Error ? error.message : String(error)
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Get all widgets
|
|
312
|
+
*/
|
|
313
|
+
async getWidgets() {
|
|
314
|
+
try {
|
|
315
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sp_widget?sysparm_limit=100`);
|
|
316
|
+
return {
|
|
317
|
+
success: true,
|
|
318
|
+
result: response.data.result
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
return {
|
|
323
|
+
success: false,
|
|
324
|
+
error: error instanceof Error ? error.message : String(error)
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get all workflows
|
|
330
|
+
*/
|
|
331
|
+
async getWorkflows() {
|
|
332
|
+
try {
|
|
333
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/wf_workflow?sysparm_limit=100`);
|
|
334
|
+
return {
|
|
335
|
+
success: true,
|
|
336
|
+
result: response.data.result
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
return {
|
|
341
|
+
success: false,
|
|
342
|
+
error: error instanceof Error ? error.message : String(error)
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get all applications
|
|
348
|
+
*/
|
|
349
|
+
async getApplications() {
|
|
350
|
+
try {
|
|
351
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_app?sysparm_limit=100`);
|
|
352
|
+
return {
|
|
353
|
+
success: true,
|
|
354
|
+
result: response.data.result
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
return {
|
|
359
|
+
success: false,
|
|
360
|
+
error: error instanceof Error ? error.message : String(error)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Get default flow structure from ServiceNow
|
|
366
|
+
*/
|
|
367
|
+
async getFlowDefaults() {
|
|
368
|
+
try {
|
|
369
|
+
// Try to get an existing flow to see the structure
|
|
370
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_hub_flow?sysparm_limit=1&sysparm_fields=sys_class_name,type,status,access,source_ui,sys_domain,sys_domain_path`);
|
|
371
|
+
if (response.data.result && response.data.result.length > 0) {
|
|
372
|
+
const sample = response.data.result[0];
|
|
373
|
+
return {
|
|
374
|
+
sys_class_name: sample.sys_class_name || 'sys_hub_flow',
|
|
375
|
+
type: sample.type || 'flow',
|
|
376
|
+
status: sample.status || 'published',
|
|
377
|
+
access: sample.access || 'public',
|
|
378
|
+
source_ui: sample.source_ui || 'flow_designer',
|
|
379
|
+
sys_domain: sample.sys_domain || 'global',
|
|
380
|
+
sys_domain_path: sample.sys_domain_path || '/'
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
console.log('Could not fetch flow defaults, using minimal defaults');
|
|
386
|
+
}
|
|
387
|
+
// Return minimal defaults if we can't get from ServiceNow
|
|
388
|
+
return {
|
|
389
|
+
sys_class_name: 'sys_hub_flow',
|
|
390
|
+
type: 'flow',
|
|
391
|
+
status: 'published',
|
|
392
|
+
access: 'public'
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Get instance info
|
|
397
|
+
*/
|
|
398
|
+
async getInstanceInfo() {
|
|
399
|
+
try {
|
|
400
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_properties?sysparm_query=name=instance.name`);
|
|
401
|
+
return {
|
|
402
|
+
success: true,
|
|
403
|
+
data: response.data.result[0]
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
return {
|
|
408
|
+
success: false,
|
|
409
|
+
error: error instanceof Error ? error.message : String(error)
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Get a specific record by sys_id
|
|
415
|
+
*/
|
|
416
|
+
async getRecord(table, sys_id) {
|
|
417
|
+
try {
|
|
418
|
+
// Ensure we have credentials before making the API call
|
|
419
|
+
await this.ensureAuthenticated();
|
|
420
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}/${sys_id}`);
|
|
421
|
+
return response.data.result;
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
console.error(`Failed to get record from ${table}:`, error);
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get multiple records from a table
|
|
430
|
+
*/
|
|
431
|
+
async getRecords(table, params) {
|
|
432
|
+
try {
|
|
433
|
+
// Ensure we have credentials before making the API call
|
|
434
|
+
await this.ensureAuthenticated();
|
|
435
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, { params });
|
|
436
|
+
return {
|
|
437
|
+
success: true,
|
|
438
|
+
data: response.data.result
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
console.error(`Failed to get records from ${table}:`, error);
|
|
443
|
+
return {
|
|
444
|
+
success: false,
|
|
445
|
+
error: error instanceof Error ? error.message : String(error)
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Search records in a table using encoded query
|
|
451
|
+
*/
|
|
452
|
+
async searchRecords(table, query, limit = 10) {
|
|
453
|
+
try {
|
|
454
|
+
await this.ensureAuthenticated();
|
|
455
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
|
|
456
|
+
params: {
|
|
457
|
+
sysparm_query: query,
|
|
458
|
+
sysparm_limit: limit
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
return {
|
|
462
|
+
success: true,
|
|
463
|
+
data: {
|
|
464
|
+
result: response.data.result || []
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
console.error(`Failed to search records in ${table}:`, error);
|
|
470
|
+
return {
|
|
471
|
+
success: false,
|
|
472
|
+
error: error instanceof Error ? error.message : String(error)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Create a record in any ServiceNow table
|
|
478
|
+
*/
|
|
479
|
+
async createRecord(table, data) {
|
|
480
|
+
try {
|
|
481
|
+
await this.ensureAuthenticated();
|
|
482
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/${table}`, data);
|
|
483
|
+
return {
|
|
484
|
+
success: true,
|
|
485
|
+
data: response.data.result
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
console.error(`Failed to create record in ${table}:`, error);
|
|
490
|
+
return {
|
|
491
|
+
success: false,
|
|
492
|
+
error: error instanceof Error ? error.message : String(error)
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Search for available flow actions in ServiceNow
|
|
498
|
+
*/
|
|
499
|
+
async searchFlowActions(searchTerm) {
|
|
500
|
+
try {
|
|
501
|
+
console.log(`🔍 Searching for flow actions: ${searchTerm}`);
|
|
502
|
+
// Search in sys_hub_action_type_base for available action types
|
|
503
|
+
const results = await this.searchRecords('sys_hub_action_type_base', `nameLIKE${searchTerm}^ORlabelLIKE${searchTerm}^ORdescriptionLIKE${searchTerm}`, 20);
|
|
504
|
+
// Also search in sys_hub_action_instance for existing actions
|
|
505
|
+
const instanceResults = await this.searchRecords('sys_hub_action_instance', `action_nameLIKE${searchTerm}^ORdescriptionLIKE${searchTerm}`, 10);
|
|
506
|
+
const actionTypes = results.success ? results.data.result : [];
|
|
507
|
+
const actionInstances = instanceResults.success ? instanceResults.data.result : [];
|
|
508
|
+
console.log(`✅ Found ${actionTypes.length} action types and ${actionInstances.length} action instances`);
|
|
509
|
+
return {
|
|
510
|
+
actionTypes,
|
|
511
|
+
actionInstances
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
console.error('Failed to search flow actions:', error);
|
|
516
|
+
return { actionTypes: [], actionInstances: [] };
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Get flow action details
|
|
521
|
+
*/
|
|
522
|
+
async getFlowActionDetails(actionTypeId) {
|
|
523
|
+
try {
|
|
524
|
+
const actionType = await this.getRecord('sys_hub_action_type_base', actionTypeId);
|
|
525
|
+
// Get input/output variables for this action type
|
|
526
|
+
const inputs = await this.searchRecords('sys_hub_action_input', `action_type=${actionTypeId}`, 50);
|
|
527
|
+
const outputs = await this.searchRecords('sys_hub_action_output', `action_type=${actionTypeId}`, 50);
|
|
528
|
+
return {
|
|
529
|
+
actionType,
|
|
530
|
+
inputs: inputs.success ? inputs.data.result : [],
|
|
531
|
+
outputs: outputs.success ? outputs.data.result : []
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
console.error('Failed to get flow action details:', error);
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Create a simple Flow Designer flow
|
|
541
|
+
* Focusing on basic flow creation with simple actions
|
|
542
|
+
*/
|
|
543
|
+
async createFlow(flow) {
|
|
544
|
+
try {
|
|
545
|
+
console.log('🔄 Creating Flow Designer flow...');
|
|
546
|
+
console.log(`📋 Flow: ${flow.name}`);
|
|
547
|
+
// First, ensure we have an Update Set
|
|
548
|
+
const updateSetResult = await this.ensureUpdateSet();
|
|
549
|
+
// Get dynamic defaults from ServiceNow
|
|
550
|
+
const flowDefaults = await this.getFlowDefaults();
|
|
551
|
+
// Build flow structure with dynamic defaults
|
|
552
|
+
const flowData = {
|
|
553
|
+
name: flow.name,
|
|
554
|
+
description: flow.description || flow.name,
|
|
555
|
+
active: flow.active !== false,
|
|
556
|
+
internal_name: this.sanitizeInternalName(flow.name),
|
|
557
|
+
category: flow.category || 'custom',
|
|
558
|
+
run_as: 'user_who_triggers',
|
|
559
|
+
// Merge with dynamic defaults from ServiceNow
|
|
560
|
+
...flowDefaults,
|
|
561
|
+
// Override with any specific values from the flow parameter
|
|
562
|
+
...flow.overrides
|
|
563
|
+
};
|
|
564
|
+
// Create the main flow record
|
|
565
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_hub_flow`, flowData);
|
|
566
|
+
if (!response.data || !response.data.result) {
|
|
567
|
+
throw new Error('No response data from flow creation');
|
|
568
|
+
}
|
|
569
|
+
const flowId = response.data.result.sys_id;
|
|
570
|
+
console.log('✅ Flow created successfully!');
|
|
571
|
+
console.log(`🆔 Flow sys_id: ${flowId}`);
|
|
572
|
+
// Create trigger first
|
|
573
|
+
let triggerInstanceId = null;
|
|
574
|
+
let triggerLogicId = null;
|
|
575
|
+
if (flow.trigger_type) {
|
|
576
|
+
console.log('📋 Creating flow trigger...');
|
|
577
|
+
try {
|
|
578
|
+
const triggerResult = await this.createFlowTrigger(flowId, {
|
|
579
|
+
type: flow.trigger_type,
|
|
580
|
+
table: flow.table || 'incident',
|
|
581
|
+
condition: flow.trigger_condition || flow.condition || ''
|
|
582
|
+
});
|
|
583
|
+
triggerInstanceId = triggerResult.sys_id;
|
|
584
|
+
// Create trigger logic entry
|
|
585
|
+
console.log('📋 Creating trigger logic...');
|
|
586
|
+
const triggerLogic = await this.createFlowLogic(flowId, {
|
|
587
|
+
name: 'Trigger',
|
|
588
|
+
type: 'trigger',
|
|
589
|
+
order: 0,
|
|
590
|
+
instance: triggerInstanceId
|
|
591
|
+
});
|
|
592
|
+
triggerLogicId = triggerLogic.sys_id;
|
|
593
|
+
}
|
|
594
|
+
catch (triggerError) {
|
|
595
|
+
console.error('Failed to create trigger:', triggerError);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// Create flow actions with logic entries
|
|
599
|
+
const actionLogicIds = [];
|
|
600
|
+
if (flow.actions && Array.isArray(flow.actions)) {
|
|
601
|
+
console.log(`📋 Creating ${flow.actions.length} flow actions...`);
|
|
602
|
+
for (let i = 0; i < flow.actions.length; i++) {
|
|
603
|
+
const action = flow.actions[i];
|
|
604
|
+
try {
|
|
605
|
+
const actionResult = await this.createFlowActionInstance(flowId, action, (i + 1) * 100);
|
|
606
|
+
// Create action logic entry
|
|
607
|
+
const actionLogic = await this.createFlowLogic(flowId, {
|
|
608
|
+
name: action.name,
|
|
609
|
+
type: 'action',
|
|
610
|
+
order: (i + 1) * 100,
|
|
611
|
+
instance: actionResult.sys_id
|
|
612
|
+
});
|
|
613
|
+
actionLogicIds.push(actionLogic.sys_id);
|
|
614
|
+
}
|
|
615
|
+
catch (actionError) {
|
|
616
|
+
console.error(`Failed to create action ${action.name}:`, actionError);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Create connections between trigger and actions
|
|
621
|
+
if (triggerLogicId && actionLogicIds.length > 0) {
|
|
622
|
+
console.log('📋 Creating flow connections...');
|
|
623
|
+
try {
|
|
624
|
+
// Connect trigger to first action
|
|
625
|
+
await this.createFlowConnection(flowId, triggerLogicId, actionLogicIds[0]);
|
|
626
|
+
// Connect actions in sequence
|
|
627
|
+
for (let i = 0; i < actionLogicIds.length - 1; i++) {
|
|
628
|
+
await this.createFlowConnection(flowId, actionLogicIds[i], actionLogicIds[i + 1]);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch (connError) {
|
|
632
|
+
console.error('Failed to create connections:', connError);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
success: true,
|
|
637
|
+
data: response.data.result
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
console.error('❌ Failed to create flow:', error);
|
|
642
|
+
return {
|
|
643
|
+
success: false,
|
|
644
|
+
error: error instanceof Error ? error.message : String(error)
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Create a flow action using discovered ServiceNow action types
|
|
650
|
+
*/
|
|
651
|
+
async createFlowAction(flowId, action, order) {
|
|
652
|
+
try {
|
|
653
|
+
console.log(`Creating flow action: ${action.type} - ${action.name}`);
|
|
654
|
+
// If we have a discovered action type, use it
|
|
655
|
+
let actionTypeId = action.action_type_id;
|
|
656
|
+
// If no specific action type provided, search for one
|
|
657
|
+
if (!actionTypeId) {
|
|
658
|
+
const searchResults = await this.searchFlowActions(action.type);
|
|
659
|
+
if (searchResults.actionTypes && searchResults.actionTypes.length > 0) {
|
|
660
|
+
actionTypeId = searchResults.actionTypes[0].sys_id;
|
|
661
|
+
console.log(`📋 Using discovered action type: ${searchResults.actionTypes[0].name}`);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
// Fallback to common action types
|
|
665
|
+
const fallbackMap = {
|
|
666
|
+
'notification': 'com.glideapp.servicenow_common.send_email',
|
|
667
|
+
'field_update': 'com.glideapp.servicenow_common.update_record',
|
|
668
|
+
'create_task': 'com.glideapp.servicenow_common.create_record',
|
|
669
|
+
'log': 'com.glideapp.servicenow_common.log_message',
|
|
670
|
+
'wait': 'com.glideapp.servicenow_common.timer',
|
|
671
|
+
'approval': 'com.glideapp.servicenow_common.approval'
|
|
672
|
+
};
|
|
673
|
+
actionTypeId = fallbackMap[action.type] || 'com.glideapp.servicenow_common.script';
|
|
674
|
+
console.log(`📋 Using fallback action type: ${actionTypeId}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// Get action details to understand inputs/outputs
|
|
678
|
+
const actionDetails = await this.getFlowActionDetails(actionTypeId);
|
|
679
|
+
// Build inputs based on action details
|
|
680
|
+
const inputs = this.buildActionInputs(action, actionDetails);
|
|
681
|
+
const actionData = {
|
|
682
|
+
flow: flowId,
|
|
683
|
+
action_name: action.name,
|
|
684
|
+
action_type: actionTypeId,
|
|
685
|
+
order: order * 100,
|
|
686
|
+
active: true,
|
|
687
|
+
// Use proper ServiceNow action configuration
|
|
688
|
+
inputs: JSON.stringify(inputs),
|
|
689
|
+
configuration: this.buildActionConfiguration(action)
|
|
690
|
+
};
|
|
691
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_hub_action_instance`, actionData);
|
|
692
|
+
console.log(`✅ Flow action created: ${action.name}`);
|
|
693
|
+
return response.data.result;
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
console.error('Failed to create flow action:', error);
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Build action inputs based on action details
|
|
702
|
+
*/
|
|
703
|
+
buildActionInputs(action, actionDetails) {
|
|
704
|
+
const inputs = {};
|
|
705
|
+
if (!actionDetails || !actionDetails.inputs) {
|
|
706
|
+
return inputs;
|
|
707
|
+
}
|
|
708
|
+
// Map action properties to ServiceNow input variables
|
|
709
|
+
for (const input of actionDetails.inputs) {
|
|
710
|
+
const inputName = input.name;
|
|
711
|
+
switch (action.type) {
|
|
712
|
+
case 'notification':
|
|
713
|
+
if (inputName === 'to' || inputName === 'recipient') {
|
|
714
|
+
inputs[inputName] = action.to || '${trigger.record.assigned_to}';
|
|
715
|
+
}
|
|
716
|
+
else if (inputName === 'subject') {
|
|
717
|
+
inputs[inputName] = action.subject || 'Flow Notification';
|
|
718
|
+
}
|
|
719
|
+
else if (inputName === 'message' || inputName === 'body') {
|
|
720
|
+
inputs[inputName] = action.message || 'Flow action executed';
|
|
721
|
+
}
|
|
722
|
+
break;
|
|
723
|
+
case 'field_update':
|
|
724
|
+
if (inputName === 'record' || inputName === 'table') {
|
|
725
|
+
inputs[inputName] = '${trigger.record}';
|
|
726
|
+
}
|
|
727
|
+
else if (inputName === 'field') {
|
|
728
|
+
inputs[inputName] = action.field;
|
|
729
|
+
}
|
|
730
|
+
else if (inputName === 'value') {
|
|
731
|
+
inputs[inputName] = action.value;
|
|
732
|
+
}
|
|
733
|
+
break;
|
|
734
|
+
case 'create_task':
|
|
735
|
+
if (inputName === 'table') {
|
|
736
|
+
inputs[inputName] = action.target_table || 'task';
|
|
737
|
+
}
|
|
738
|
+
else if (inputName === 'fields') {
|
|
739
|
+
inputs[inputName] = action.fields || {};
|
|
740
|
+
}
|
|
741
|
+
break;
|
|
742
|
+
case 'log':
|
|
743
|
+
if (inputName === 'message') {
|
|
744
|
+
inputs[inputName] = action.message || 'Flow action executed';
|
|
745
|
+
}
|
|
746
|
+
else if (inputName === 'level') {
|
|
747
|
+
inputs[inputName] = action.level || 'info';
|
|
748
|
+
}
|
|
749
|
+
break;
|
|
750
|
+
case 'wait':
|
|
751
|
+
if (inputName === 'duration') {
|
|
752
|
+
inputs[inputName] = action.duration || 300;
|
|
753
|
+
}
|
|
754
|
+
break;
|
|
755
|
+
case 'approval':
|
|
756
|
+
if (inputName === 'approvers') {
|
|
757
|
+
inputs[inputName] = action.approvers || '${trigger.record.assigned_to.manager}';
|
|
758
|
+
}
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return inputs;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Build simple action configuration
|
|
766
|
+
*/
|
|
767
|
+
buildActionConfiguration(action) {
|
|
768
|
+
const config = {
|
|
769
|
+
name: action.name,
|
|
770
|
+
type: action.type
|
|
771
|
+
};
|
|
772
|
+
switch (action.type) {
|
|
773
|
+
case 'notification':
|
|
774
|
+
config.to = action.to || 'assigned_to';
|
|
775
|
+
config.subject = action.subject || 'Flow Notification';
|
|
776
|
+
config.body = action.message || 'A flow action has been triggered.';
|
|
777
|
+
break;
|
|
778
|
+
case 'field_update':
|
|
779
|
+
config.field = action.field;
|
|
780
|
+
config.value = action.value;
|
|
781
|
+
break;
|
|
782
|
+
case 'create_task':
|
|
783
|
+
config.table = action.target_table || 'task';
|
|
784
|
+
config.fields = action.fields || {};
|
|
785
|
+
break;
|
|
786
|
+
case 'log':
|
|
787
|
+
config.message = action.message || 'Flow action executed';
|
|
788
|
+
config.level = action.level || 'info';
|
|
789
|
+
break;
|
|
790
|
+
case 'wait':
|
|
791
|
+
config.duration = action.duration || 300; // 5 minutes default
|
|
792
|
+
break;
|
|
793
|
+
case 'approval':
|
|
794
|
+
config.approvers = action.approvers || 'assigned_to.manager';
|
|
795
|
+
break;
|
|
796
|
+
default:
|
|
797
|
+
config.script = action.script || '// Custom action';
|
|
798
|
+
}
|
|
799
|
+
return JSON.stringify(config);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Create a flow trigger instance
|
|
803
|
+
*/
|
|
804
|
+
async createFlowTrigger(flowId, trigger) {
|
|
805
|
+
try {
|
|
806
|
+
// Get trigger type from cache
|
|
807
|
+
let triggerType = await this.actionTypeCache.getTriggerType(trigger.type);
|
|
808
|
+
if (!triggerType) {
|
|
809
|
+
console.warn(`Trigger type '${trigger.type}' not found in cache, using default`);
|
|
810
|
+
// Fallback to 'Created' trigger if not found
|
|
811
|
+
triggerType = await this.actionTypeCache.getTriggerType('Created');
|
|
812
|
+
if (!triggerType) {
|
|
813
|
+
throw new Error('Could not find any trigger types');
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const triggerTypeId = triggerType.sys_id;
|
|
817
|
+
const triggerData = {
|
|
818
|
+
flow: flowId,
|
|
819
|
+
trigger_type: triggerTypeId,
|
|
820
|
+
table_name: trigger.table || 'incident',
|
|
821
|
+
condition: trigger.condition || '',
|
|
822
|
+
order: 0,
|
|
823
|
+
active: true
|
|
824
|
+
};
|
|
825
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_hub_trigger_instance`, triggerData);
|
|
826
|
+
console.log('✅ Trigger created successfully');
|
|
827
|
+
return response.data.result;
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
console.error('Failed to create flow trigger:', error);
|
|
831
|
+
throw error;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Create a flow action instance using simplified approach
|
|
836
|
+
*/
|
|
837
|
+
async createFlowActionInstance(flowId, action, order) {
|
|
838
|
+
try {
|
|
839
|
+
console.log(`Creating action instance: ${action.name}`);
|
|
840
|
+
// Map action types to search terms
|
|
841
|
+
const actionSearchMap = {
|
|
842
|
+
'notification': 'Send Email',
|
|
843
|
+
'email': 'Send Email',
|
|
844
|
+
'field_update': 'Update Record',
|
|
845
|
+
'create_task': 'Create Record',
|
|
846
|
+
'create_record': 'Create Record',
|
|
847
|
+
'wait': 'Wait',
|
|
848
|
+
'approval': 'Approval',
|
|
849
|
+
'log': 'Log'
|
|
850
|
+
};
|
|
851
|
+
const searchTerm = actionSearchMap[action.type] || action.type;
|
|
852
|
+
let actionType = await this.actionTypeCache.getActionType(searchTerm);
|
|
853
|
+
if (!actionType) {
|
|
854
|
+
console.warn(`Action type '${searchTerm}' not found in cache, using Script action`);
|
|
855
|
+
// Fallback to Script action if not found
|
|
856
|
+
actionType = await this.actionTypeCache.getActionType('Script');
|
|
857
|
+
if (!actionType) {
|
|
858
|
+
throw new Error('Could not find any action types');
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const actionTypeId = actionType.sys_id;
|
|
862
|
+
// Build inputs based on action type
|
|
863
|
+
let inputs = {};
|
|
864
|
+
switch (action.type) {
|
|
865
|
+
case 'notification':
|
|
866
|
+
case 'email':
|
|
867
|
+
inputs = {
|
|
868
|
+
email_to: action.to || '${trigger.assigned_to.email}',
|
|
869
|
+
email_subject: action.subject || 'Notification',
|
|
870
|
+
email_body: action.message || 'Notification from flow'
|
|
871
|
+
};
|
|
872
|
+
break;
|
|
873
|
+
case 'field_update':
|
|
874
|
+
inputs = {
|
|
875
|
+
table: action.table || 'current',
|
|
876
|
+
field: action.field || 'state',
|
|
877
|
+
value: action.value || ''
|
|
878
|
+
};
|
|
879
|
+
break;
|
|
880
|
+
case 'create_task':
|
|
881
|
+
case 'create_record':
|
|
882
|
+
inputs = {
|
|
883
|
+
table: action.target_table || 'task',
|
|
884
|
+
field_values: JSON.stringify({
|
|
885
|
+
short_description: action.short_description || 'Task from flow',
|
|
886
|
+
assigned_to: action.assigned_to || '${trigger.assigned_to}'
|
|
887
|
+
})
|
|
888
|
+
};
|
|
889
|
+
break;
|
|
890
|
+
case 'wait':
|
|
891
|
+
inputs = {
|
|
892
|
+
duration: action.duration || 300,
|
|
893
|
+
unit: 'seconds'
|
|
894
|
+
};
|
|
895
|
+
break;
|
|
896
|
+
case 'approval':
|
|
897
|
+
inputs = {
|
|
898
|
+
approvers: action.approvers || '${trigger.assigned_to.manager}',
|
|
899
|
+
approval_field: 'approval'
|
|
900
|
+
};
|
|
901
|
+
break;
|
|
902
|
+
default:
|
|
903
|
+
inputs = action.inputs || {};
|
|
904
|
+
}
|
|
905
|
+
const actionData = {
|
|
906
|
+
flow: flowId,
|
|
907
|
+
action_name: action.name,
|
|
908
|
+
action_type: actionTypeId,
|
|
909
|
+
order: order,
|
|
910
|
+
active: true,
|
|
911
|
+
inputs: JSON.stringify(inputs)
|
|
912
|
+
};
|
|
913
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_hub_action_instance`, actionData);
|
|
914
|
+
console.log(`✅ Action created: ${action.name}`);
|
|
915
|
+
return response.data.result;
|
|
916
|
+
}
|
|
917
|
+
catch (error) {
|
|
918
|
+
console.error(`Failed to create action instance ${action.name}:`, error);
|
|
919
|
+
throw error;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Create a flow operation (activity) for a Flow Designer flow
|
|
924
|
+
*/
|
|
925
|
+
async createFlowOperation(flowId, operation) {
|
|
926
|
+
try {
|
|
927
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_hub_flow_operation`, {
|
|
928
|
+
flow: flowId,
|
|
929
|
+
name: operation.name,
|
|
930
|
+
type: operation.type || 'core',
|
|
931
|
+
action_type: operation.action_type || 'script',
|
|
932
|
+
order: operation.order || 100,
|
|
933
|
+
// Operation-specific configuration
|
|
934
|
+
configuration: JSON.stringify({
|
|
935
|
+
script: operation.script,
|
|
936
|
+
inputs: operation.inputs || {},
|
|
937
|
+
outputs: operation.outputs || {},
|
|
938
|
+
artifact_reference: operation.artifact_reference
|
|
939
|
+
})
|
|
940
|
+
});
|
|
941
|
+
return response.data.result;
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
console.error('Failed to create flow operation:', error);
|
|
945
|
+
throw error;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Create a Script Include
|
|
950
|
+
*/
|
|
951
|
+
async createScriptInclude(scriptInclude) {
|
|
952
|
+
try {
|
|
953
|
+
console.log('📝 Creating Script Include...');
|
|
954
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_script_include`, {
|
|
955
|
+
name: scriptInclude.name,
|
|
956
|
+
api_name: scriptInclude.api_name,
|
|
957
|
+
description: scriptInclude.description,
|
|
958
|
+
script: scriptInclude.script,
|
|
959
|
+
active: scriptInclude.active,
|
|
960
|
+
access: scriptInclude.access || 'public'
|
|
961
|
+
});
|
|
962
|
+
console.log('✅ Script Include created successfully!');
|
|
963
|
+
return {
|
|
964
|
+
success: true,
|
|
965
|
+
data: response.data.result
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
console.error('❌ Failed to create script include:', error);
|
|
970
|
+
return {
|
|
971
|
+
success: false,
|
|
972
|
+
error: error instanceof Error ? error.message : String(error)
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Create a Business Rule
|
|
978
|
+
*/
|
|
979
|
+
async createBusinessRule(businessRule) {
|
|
980
|
+
try {
|
|
981
|
+
console.log('📋 Creating Business Rule...');
|
|
982
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_script`, {
|
|
983
|
+
name: businessRule.name,
|
|
984
|
+
collection: businessRule.table,
|
|
985
|
+
when: businessRule.when,
|
|
986
|
+
condition: businessRule.condition,
|
|
987
|
+
script: businessRule.script,
|
|
988
|
+
description: businessRule.description,
|
|
989
|
+
active: businessRule.active,
|
|
990
|
+
order: businessRule.order || 100
|
|
991
|
+
});
|
|
992
|
+
console.log('✅ Business Rule created successfully!');
|
|
993
|
+
return {
|
|
994
|
+
success: true,
|
|
995
|
+
data: response.data.result
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
catch (error) {
|
|
999
|
+
console.error('❌ Failed to create business rule:', error);
|
|
1000
|
+
return {
|
|
1001
|
+
success: false,
|
|
1002
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Create a Table
|
|
1008
|
+
*/
|
|
1009
|
+
async createTable(table) {
|
|
1010
|
+
try {
|
|
1011
|
+
console.log('🗄️ Creating Table...');
|
|
1012
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_db_object`, {
|
|
1013
|
+
name: table.name,
|
|
1014
|
+
label: table.label,
|
|
1015
|
+
extends_table: table.extends_table || 'sys_metadata',
|
|
1016
|
+
is_extendable: table.is_extendable !== false,
|
|
1017
|
+
access: table.access || 'public',
|
|
1018
|
+
create_access_controls: table.create_access_controls !== false
|
|
1019
|
+
});
|
|
1020
|
+
console.log('✅ Table created successfully!');
|
|
1021
|
+
return {
|
|
1022
|
+
success: true,
|
|
1023
|
+
data: response.data.result
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
catch (error) {
|
|
1027
|
+
console.error('❌ Failed to create table:', error);
|
|
1028
|
+
return {
|
|
1029
|
+
success: false,
|
|
1030
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Create a Table Field
|
|
1036
|
+
*/
|
|
1037
|
+
async createTableField(field) {
|
|
1038
|
+
try {
|
|
1039
|
+
console.log('📊 Creating Table Field...');
|
|
1040
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_dictionary`, {
|
|
1041
|
+
name: `${field.table}.${field.element}`,
|
|
1042
|
+
element: field.element,
|
|
1043
|
+
column_label: field.column_label,
|
|
1044
|
+
internal_type: field.internal_type || 'string',
|
|
1045
|
+
max_length: field.max_length || 255,
|
|
1046
|
+
active: true
|
|
1047
|
+
});
|
|
1048
|
+
console.log('✅ Table Field created successfully!');
|
|
1049
|
+
return {
|
|
1050
|
+
success: true,
|
|
1051
|
+
data: response.data.result
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
catch (error) {
|
|
1055
|
+
console.error('❌ Failed to create table field:', error);
|
|
1056
|
+
return {
|
|
1057
|
+
success: false,
|
|
1058
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Create a new Update Set
|
|
1064
|
+
*/
|
|
1065
|
+
async createUpdateSet(updateSet) {
|
|
1066
|
+
try {
|
|
1067
|
+
console.log('📦 Creating Update Set...');
|
|
1068
|
+
// Ensure we have credentials before making the API call
|
|
1069
|
+
await this.ensureAuthenticated();
|
|
1070
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_update_set`, {
|
|
1071
|
+
name: updateSet.name,
|
|
1072
|
+
description: updateSet.description,
|
|
1073
|
+
release_date: updateSet.release_date,
|
|
1074
|
+
state: updateSet.state || 'in_progress',
|
|
1075
|
+
application: updateSet.application || 'global'
|
|
1076
|
+
});
|
|
1077
|
+
console.log('✅ Update Set created successfully!');
|
|
1078
|
+
return {
|
|
1079
|
+
success: true,
|
|
1080
|
+
data: response.data.result
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
catch (error) {
|
|
1084
|
+
console.error('❌ Failed to create Update Set:', error);
|
|
1085
|
+
return {
|
|
1086
|
+
success: false,
|
|
1087
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Set current Update Set for the session
|
|
1093
|
+
*/
|
|
1094
|
+
async setCurrentUpdateSet(updateSetId) {
|
|
1095
|
+
try {
|
|
1096
|
+
console.log('🔄 Setting current Update Set...');
|
|
1097
|
+
// Ensure we have credentials before making the API call
|
|
1098
|
+
await this.ensureAuthenticated();
|
|
1099
|
+
// Use the sys_user_preference table to set the current update set
|
|
1100
|
+
// First, check if a preference already exists
|
|
1101
|
+
const existingPref = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_user_preference`, {
|
|
1102
|
+
params: {
|
|
1103
|
+
sysparm_query: 'name=sys_update_set^user=javascript:gs.getUserID()',
|
|
1104
|
+
sysparm_limit: 1
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
let response;
|
|
1108
|
+
if (existingPref.data.result && existingPref.data.result.length > 0) {
|
|
1109
|
+
// Update existing preference
|
|
1110
|
+
response = await this.client.patch(`${this.getBaseUrl()}/api/now/table/sys_user_preference/${existingPref.data.result[0].sys_id}`, {
|
|
1111
|
+
value: updateSetId
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
// Create new preference
|
|
1116
|
+
response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_user_preference`, {
|
|
1117
|
+
name: 'sys_update_set',
|
|
1118
|
+
value: updateSetId,
|
|
1119
|
+
user: 'javascript:gs.getUserID()'
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
console.log('✅ Current Update Set changed successfully!');
|
|
1123
|
+
return {
|
|
1124
|
+
success: true,
|
|
1125
|
+
data: response.data.result
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
catch (error) {
|
|
1129
|
+
console.error('❌ Failed to set current Update Set:', error);
|
|
1130
|
+
return {
|
|
1131
|
+
success: false,
|
|
1132
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Get current Update Set
|
|
1138
|
+
*/
|
|
1139
|
+
async getCurrentUpdateSet() {
|
|
1140
|
+
try {
|
|
1141
|
+
console.log('📋 Getting current Update Set...');
|
|
1142
|
+
// Ensure we have credentials before making the API call
|
|
1143
|
+
await this.ensureAuthenticated();
|
|
1144
|
+
// Try to get the current update set using sys_user_preference table
|
|
1145
|
+
// This is more reliable than the UI API
|
|
1146
|
+
try {
|
|
1147
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_user_preference`, {
|
|
1148
|
+
params: {
|
|
1149
|
+
sysparm_query: 'name=sys_update_set^user=javascript:gs.getUserID()',
|
|
1150
|
+
sysparm_limit: 1
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
if (response.data.result && response.data.result.length > 0) {
|
|
1154
|
+
const updateSetId = response.data.result[0].value;
|
|
1155
|
+
// Get Update Set details
|
|
1156
|
+
const updateSetResponse = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_update_set/${updateSetId}`);
|
|
1157
|
+
return {
|
|
1158
|
+
success: true,
|
|
1159
|
+
data: updateSetResponse.data.result
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
catch (prefError) {
|
|
1164
|
+
console.log('⚠️ User preference lookup failed, trying fallback...');
|
|
1165
|
+
}
|
|
1166
|
+
// Fallback: Get the most recent in-progress update set for the current user
|
|
1167
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_update_set`, {
|
|
1168
|
+
params: {
|
|
1169
|
+
sysparm_query: 'state=in_progress^sys_created_by=javascript:gs.getUserName()',
|
|
1170
|
+
sysparm_orderby: 'sys_created_on',
|
|
1171
|
+
sysparm_limit: 1
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
if (response.data.result && response.data.result.length > 0) {
|
|
1175
|
+
return {
|
|
1176
|
+
success: true,
|
|
1177
|
+
data: response.data.result[0]
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
return {
|
|
1181
|
+
success: false,
|
|
1182
|
+
error: 'No current Update Set found'
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
catch (error) {
|
|
1186
|
+
console.error('❌ Failed to get current Update Set:', error);
|
|
1187
|
+
return {
|
|
1188
|
+
success: false,
|
|
1189
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Get Update Set by ID
|
|
1195
|
+
*/
|
|
1196
|
+
async getUpdateSet(updateSetId) {
|
|
1197
|
+
try {
|
|
1198
|
+
console.log(`📋 Getting Update Set ${updateSetId}...`);
|
|
1199
|
+
// Ensure we have credentials before making the API call
|
|
1200
|
+
await this.ensureAuthenticated();
|
|
1201
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_update_set/${updateSetId}`);
|
|
1202
|
+
return {
|
|
1203
|
+
success: true,
|
|
1204
|
+
data: response.data.result
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
catch (error) {
|
|
1208
|
+
console.error('❌ Failed to get Update Set:', error);
|
|
1209
|
+
return {
|
|
1210
|
+
success: false,
|
|
1211
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* List Update Sets
|
|
1217
|
+
*/
|
|
1218
|
+
async listUpdateSets(options) {
|
|
1219
|
+
try {
|
|
1220
|
+
console.log('📋 Listing Update Sets...');
|
|
1221
|
+
// Ensure we have credentials before making the API call
|
|
1222
|
+
await this.ensureAuthenticated();
|
|
1223
|
+
let query = 'sys_created_by=javascript:gs.getUserName()';
|
|
1224
|
+
if (options.state) {
|
|
1225
|
+
query += `^state=${options.state}`;
|
|
1226
|
+
}
|
|
1227
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_update_set`, {
|
|
1228
|
+
params: {
|
|
1229
|
+
sysparm_query: query,
|
|
1230
|
+
sysparm_limit: options.limit || 10,
|
|
1231
|
+
sysparm_orderby: 'sys_created_on^DESC'
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
return {
|
|
1235
|
+
success: true,
|
|
1236
|
+
data: response.data.result
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
catch (error) {
|
|
1240
|
+
console.error('❌ Failed to list Update Sets:', error);
|
|
1241
|
+
return {
|
|
1242
|
+
success: false,
|
|
1243
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Complete an Update Set
|
|
1249
|
+
*/
|
|
1250
|
+
async completeUpdateSet(updateSetId, notes) {
|
|
1251
|
+
try {
|
|
1252
|
+
console.log('✅ Completing Update Set...');
|
|
1253
|
+
// Ensure we have credentials before making the API call
|
|
1254
|
+
await this.ensureAuthenticated();
|
|
1255
|
+
const response = await this.client.patch(`${this.getBaseUrl()}/api/now/table/sys_update_set/${updateSetId}`, {
|
|
1256
|
+
state: 'complete',
|
|
1257
|
+
description: notes ? `${notes}\n\nCompleted: ${new Date().toISOString()}` : undefined
|
|
1258
|
+
});
|
|
1259
|
+
console.log('✅ Update Set completed successfully!');
|
|
1260
|
+
return {
|
|
1261
|
+
success: true,
|
|
1262
|
+
data: response.data.result
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
catch (error) {
|
|
1266
|
+
console.error('❌ Failed to complete Update Set:', error);
|
|
1267
|
+
return {
|
|
1268
|
+
success: false,
|
|
1269
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Preview Update Set changes
|
|
1275
|
+
*/
|
|
1276
|
+
async previewUpdateSet(updateSetId) {
|
|
1277
|
+
try {
|
|
1278
|
+
console.log('🔍 Previewing Update Set changes...');
|
|
1279
|
+
// Get Update Set details
|
|
1280
|
+
const updateSetResponse = await this.getUpdateSet(updateSetId);
|
|
1281
|
+
if (!updateSetResponse.success) {
|
|
1282
|
+
return updateSetResponse;
|
|
1283
|
+
}
|
|
1284
|
+
// Get Update Set changes
|
|
1285
|
+
const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_update_xml`, {
|
|
1286
|
+
params: {
|
|
1287
|
+
sysparm_query: `update_set=${updateSetId}`,
|
|
1288
|
+
sysparm_limit: 1000
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
return {
|
|
1292
|
+
success: true,
|
|
1293
|
+
data: {
|
|
1294
|
+
...updateSetResponse.data,
|
|
1295
|
+
changes: response.data.result
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
catch (error) {
|
|
1300
|
+
console.error('❌ Failed to preview Update Set:', error);
|
|
1301
|
+
return {
|
|
1302
|
+
success: false,
|
|
1303
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Export Update Set as XML
|
|
1309
|
+
*/
|
|
1310
|
+
/**
|
|
1311
|
+
* Ensure we have an active Update Set for tracking changes
|
|
1312
|
+
*/
|
|
1313
|
+
async ensureUpdateSet() {
|
|
1314
|
+
try {
|
|
1315
|
+
// Check if we have a current Update Set
|
|
1316
|
+
const currentUpdateSet = await this.getCurrentUpdateSet();
|
|
1317
|
+
if (currentUpdateSet.success && currentUpdateSet.data) {
|
|
1318
|
+
console.log(`📦 Using existing Update Set: ${currentUpdateSet.data.name}`);
|
|
1319
|
+
return currentUpdateSet;
|
|
1320
|
+
}
|
|
1321
|
+
// Create a new Update Set if none exists
|
|
1322
|
+
const updateSetName = `Snow-Flow Changes ${new Date().toISOString().split('T')[0]}`;
|
|
1323
|
+
console.log(`📦 Creating new Update Set: ${updateSetName}`);
|
|
1324
|
+
const newUpdateSet = await this.createUpdateSet({
|
|
1325
|
+
name: updateSetName,
|
|
1326
|
+
description: 'Automated changes from Snow-Flow MCP',
|
|
1327
|
+
state: 'in_progress'
|
|
1328
|
+
});
|
|
1329
|
+
if (newUpdateSet.success && newUpdateSet.data) {
|
|
1330
|
+
// Set it as current
|
|
1331
|
+
await this.setCurrentUpdateSet(newUpdateSet.data.sys_id);
|
|
1332
|
+
return newUpdateSet;
|
|
1333
|
+
}
|
|
1334
|
+
// If Update Set creation fails, continue without it
|
|
1335
|
+
console.warn('⚠️ Could not create Update Set, changes will not be tracked');
|
|
1336
|
+
return { success: false, error: 'Update Set creation failed' };
|
|
1337
|
+
}
|
|
1338
|
+
catch (error) {
|
|
1339
|
+
console.warn('⚠️ Update Set management failed:', error);
|
|
1340
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
async exportUpdateSet(updateSetId) {
|
|
1344
|
+
try {
|
|
1345
|
+
console.log('📤 Exporting Update Set...');
|
|
1346
|
+
// Get Update Set details first
|
|
1347
|
+
const updateSetResponse = await this.getUpdateSet(updateSetId);
|
|
1348
|
+
if (!updateSetResponse.success) {
|
|
1349
|
+
return updateSetResponse;
|
|
1350
|
+
}
|
|
1351
|
+
// Use the unload processor to export
|
|
1352
|
+
const response = await this.client.get(`${this.getBaseUrl()}/unload.do`, {
|
|
1353
|
+
params: {
|
|
1354
|
+
sysparm_sys_id: updateSetId,
|
|
1355
|
+
sysparm_table: 'sys_update_set',
|
|
1356
|
+
sysparm_unload_format: 'xml'
|
|
1357
|
+
},
|
|
1358
|
+
responseType: 'text'
|
|
1359
|
+
});
|
|
1360
|
+
return {
|
|
1361
|
+
success: true,
|
|
1362
|
+
data: {
|
|
1363
|
+
name: updateSetResponse.data.name,
|
|
1364
|
+
xml: response.data,
|
|
1365
|
+
change_count: updateSetResponse.data.update_count || 0
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
catch (error) {
|
|
1370
|
+
console.error('❌ Failed to export Update Set:', error);
|
|
1371
|
+
return {
|
|
1372
|
+
success: false,
|
|
1373
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Debug flow structure - check what's missing for Flow Designer
|
|
1379
|
+
*/
|
|
1380
|
+
async debugFlow(flowId) {
|
|
1381
|
+
try {
|
|
1382
|
+
console.log('🔍 Debugging flow structure...');
|
|
1383
|
+
// Get the flow record
|
|
1384
|
+
const flowResponse = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_hub_flow/${flowId}`);
|
|
1385
|
+
if (!flowResponse.data.result) {
|
|
1386
|
+
return { success: false, error: 'Flow not found' };
|
|
1387
|
+
}
|
|
1388
|
+
const flow = flowResponse.data.result;
|
|
1389
|
+
// Check for sys_hub_flow_logic entries
|
|
1390
|
+
const logicResponse = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_hub_flow_logic?sysparm_query=flow=${flowId}`);
|
|
1391
|
+
// Check for trigger instances
|
|
1392
|
+
const triggerResponse = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_hub_trigger_instance?sysparm_query=flow=${flowId}`);
|
|
1393
|
+
// Check for action instances
|
|
1394
|
+
const actionResponse = await this.client.get(`${this.getBaseUrl()}/api/now/table/sys_hub_action_instance?sysparm_query=flow=${flowId}`);
|
|
1395
|
+
return {
|
|
1396
|
+
success: true,
|
|
1397
|
+
data: {
|
|
1398
|
+
flow: {
|
|
1399
|
+
sys_id: flow.sys_id,
|
|
1400
|
+
name: flow.name,
|
|
1401
|
+
internal_name: flow.internal_name,
|
|
1402
|
+
status: flow.status,
|
|
1403
|
+
type: flow.type,
|
|
1404
|
+
sys_class_name: flow.sys_class_name,
|
|
1405
|
+
missing_fields: []
|
|
1406
|
+
},
|
|
1407
|
+
logic_count: logicResponse.data.result.length,
|
|
1408
|
+
trigger_count: triggerResponse.data.result.length,
|
|
1409
|
+
action_count: actionResponse.data.result.length,
|
|
1410
|
+
logic_entries: logicResponse.data.result,
|
|
1411
|
+
triggers: triggerResponse.data.result,
|
|
1412
|
+
actions: actionResponse.data.result
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
return {
|
|
1418
|
+
success: false,
|
|
1419
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Create a flow logic entry (visual representation in Flow Designer)
|
|
1425
|
+
*/
|
|
1426
|
+
async createFlowLogic(flowId, logic) {
|
|
1427
|
+
try {
|
|
1428
|
+
const logicData = {
|
|
1429
|
+
flow: flowId,
|
|
1430
|
+
name: logic.name,
|
|
1431
|
+
type: logic.type,
|
|
1432
|
+
order: logic.order,
|
|
1433
|
+
active: true,
|
|
1434
|
+
instance: logic.instance
|
|
1435
|
+
};
|
|
1436
|
+
const response = await this.client.post(`${this.getBaseUrl()}/api/now/table/sys_hub_flow_logic`, logicData);
|
|
1437
|
+
return response.data.result;
|
|
1438
|
+
}
|
|
1439
|
+
catch (error) {
|
|
1440
|
+
console.error('Failed to create flow logic:', error);
|
|
1441
|
+
throw error;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Create a connection between flow logic elements
|
|
1446
|
+
*/
|
|
1447
|
+
async createFlowConnection(flowId, fromId, toId) {
|
|
1448
|
+
try {
|
|
1449
|
+
// In ServiceNow, connections are stored as part of the flow logic
|
|
1450
|
+
// We need to update the 'from' element to point to the 'to' element
|
|
1451
|
+
const connectionData = {
|
|
1452
|
+
next: toId
|
|
1453
|
+
};
|
|
1454
|
+
const response = await this.client.patch(`${this.getBaseUrl()}/api/now/table/sys_hub_flow_logic/${fromId}`, connectionData);
|
|
1455
|
+
return response.data.result;
|
|
1456
|
+
}
|
|
1457
|
+
catch (error) {
|
|
1458
|
+
console.error('Failed to create flow connection:', error);
|
|
1459
|
+
// Don't throw - connections might work differently in some versions
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
exports.ServiceNowClient = ServiceNowClient;
|
|
1465
|
+
//# sourceMappingURL=servicenow-client.js.map
|