snow-flow 1.3.18 → 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/dist/cli.js
CHANGED
|
@@ -65,8 +65,16 @@ program
|
|
|
65
65
|
// Helper function to deploy XML to ServiceNow
|
|
66
66
|
async function deployXMLToServiceNow(xmlFile, options = {}) {
|
|
67
67
|
const oauth = new snow_oauth_js_1.ServiceNowOAuth();
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
let tokens;
|
|
69
|
+
try {
|
|
70
|
+
tokens = await oauth.getStoredTokens();
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
cliLogger.error('❌ Failed to get authentication tokens:', error);
|
|
74
|
+
cliLogger.error('Please run: snow-flow auth login');
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (!tokens || !tokens.accessToken) {
|
|
70
78
|
cliLogger.error('❌ Not authenticated. Please run: snow-flow auth login');
|
|
71
79
|
return false;
|
|
72
80
|
}
|
|
@@ -166,11 +174,43 @@ async function deployXMLToServiceNow(xmlFile, options = {}) {
|
|
|
166
174
|
return true;
|
|
167
175
|
}
|
|
168
176
|
catch (error) {
|
|
169
|
-
cliLogger.error('\n❌ Deployment failed:'
|
|
177
|
+
cliLogger.error('\n❌ Deployment failed:');
|
|
178
|
+
// Detailed error handling for 400 errors
|
|
179
|
+
if (error.response?.status === 400) {
|
|
180
|
+
cliLogger.error(` Status: ${error.response.status} Bad Request`);
|
|
181
|
+
if (error.response.data) {
|
|
182
|
+
cliLogger.error(` Message: ${error.response.data.error?.message || error.response.data.message || 'Unknown error'}`);
|
|
183
|
+
if (error.response.data.error?.detail) {
|
|
184
|
+
cliLogger.error(` Detail: ${error.response.data.error.detail}`);
|
|
185
|
+
}
|
|
186
|
+
if (error.response.data.error?.fields) {
|
|
187
|
+
cliLogger.error(' Missing or invalid fields:');
|
|
188
|
+
Object.entries(error.response.data.error.fields).forEach(([field, msg]) => {
|
|
189
|
+
cliLogger.error(` - ${field}: ${msg}`);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (error.response?.status === 401) {
|
|
195
|
+
cliLogger.error(' Status: 401 Unauthorized - Authentication failed');
|
|
196
|
+
cliLogger.error(' Your OAuth token may be expired. Please run: snow-flow auth login');
|
|
197
|
+
}
|
|
198
|
+
else if (error.response?.status === 403) {
|
|
199
|
+
cliLogger.error(' Status: 403 Forbidden - Insufficient permissions');
|
|
200
|
+
cliLogger.error(' You need admin or update_set_admin role to import update sets');
|
|
201
|
+
}
|
|
202
|
+
else if (error.response) {
|
|
203
|
+
cliLogger.error(` Status: ${error.response.status}`);
|
|
204
|
+
cliLogger.error(` Message: ${error.response.data?.error?.message || error.response.statusText}`);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
cliLogger.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
208
|
+
}
|
|
170
209
|
cliLogger.info('\n💡 Troubleshooting tips:');
|
|
171
210
|
cliLogger.info(' 1. Check your authentication: snow-flow auth status');
|
|
172
211
|
cliLogger.info(' 2. Verify XML file format is correct');
|
|
173
212
|
cliLogger.info(' 3. Ensure you have required permissions in ServiceNow');
|
|
213
|
+
cliLogger.info(' 4. Check ServiceNow system logs for more details');
|
|
174
214
|
return false;
|
|
175
215
|
}
|
|
176
216
|
}
|
|
@@ -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();
|
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",
|