nowaikit-utils 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/LICENSE +21 -0
- package/README.md +152 -0
- package/ai-window.html +598 -0
- package/background/service-worker.js +398 -0
- package/cli.mjs +65 -0
- package/content/ai-sidebar.js +1198 -0
- package/content/code-templates.js +843 -0
- package/content/content.js +2527 -0
- package/content/integration-bridge.js +627 -0
- package/content/main-panel.js +592 -0
- package/content/styles.css +1609 -0
- package/icons/README.txt +1 -0
- package/icons/icon-128.png +0 -0
- package/icons/icon-16.png +0 -0
- package/icons/icon-48.png +0 -0
- package/icons/icon.svg +16 -0
- package/manifest.json +63 -0
- package/options/options.html +434 -0
- package/package.json +49 -0
- package/popup/popup.html +663 -0
- package/popup/popup.js +414 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NowAIKit Utils — Background Service Worker
|
|
3
|
+
*
|
|
4
|
+
* Handles context menus, badge updates, cookie proxy, and AI streaming.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/** Validate a URL belongs to a ServiceNow domain */
|
|
10
|
+
function isServiceNowUrl(url) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = new URL(url);
|
|
13
|
+
return parsed.hostname.endsWith('.service-now.com') || parsed.hostname.endsWith('.servicenow.com');
|
|
14
|
+
} catch (e) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ─── Context Menus ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
chrome.runtime.onInstalled.addListener(() => {
|
|
22
|
+
chrome.contextMenus.create({
|
|
23
|
+
id: 'nowaikit-copy-sysid',
|
|
24
|
+
title: 'Copy sys_id',
|
|
25
|
+
contexts: ['page'],
|
|
26
|
+
documentUrlPatterns: ['https://*.service-now.com/*', 'https://*.servicenow.com/*'],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
chrome.contextMenus.create({
|
|
30
|
+
id: 'nowaikit-copy-record-url',
|
|
31
|
+
title: 'Copy Record URL',
|
|
32
|
+
contexts: ['page'],
|
|
33
|
+
documentUrlPatterns: ['https://*.service-now.com/*', 'https://*.servicenow.com/*'],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
chrome.contextMenus.create({
|
|
37
|
+
id: 'nowaikit-copy-table-name',
|
|
38
|
+
title: 'Copy Table Name',
|
|
39
|
+
contexts: ['page'],
|
|
40
|
+
documentUrlPatterns: ['https://*.service-now.com/*', 'https://*.servicenow.com/*'],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
chrome.contextMenus.create({
|
|
44
|
+
id: 'nowaikit-open-list',
|
|
45
|
+
title: 'Open Table List View',
|
|
46
|
+
contexts: ['page'],
|
|
47
|
+
documentUrlPatterns: ['https://*.service-now.com/*', 'https://*.servicenow.com/*'],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
chrome.contextMenus.create({
|
|
51
|
+
id: 'nowaikit-open-schema',
|
|
52
|
+
title: 'View Table Schema',
|
|
53
|
+
contexts: ['page'],
|
|
54
|
+
documentUrlPatterns: ['https://*.service-now.com/*', 'https://*.servicenow.com/*'],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
chrome.contextMenus.create({
|
|
58
|
+
id: 'nowaikit-separator',
|
|
59
|
+
type: 'separator',
|
|
60
|
+
contexts: ['page'],
|
|
61
|
+
documentUrlPatterns: ['https://*.service-now.com/*', 'https://*.servicenow.com/*'],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
chrome.contextMenus.create({
|
|
65
|
+
id: 'nowaikit-xml-view',
|
|
66
|
+
title: 'View as XML',
|
|
67
|
+
contexts: ['page'],
|
|
68
|
+
documentUrlPatterns: ['https://*.service-now.com/*', 'https://*.servicenow.com/*'],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
chrome.contextMenus.create({
|
|
72
|
+
id: 'nowaikit-json-view',
|
|
73
|
+
title: 'View as JSON (REST)',
|
|
74
|
+
contexts: ['page'],
|
|
75
|
+
documentUrlPatterns: ['https://*.service-now.com/*', 'https://*.servicenow.com/*'],
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
|
80
|
+
if (!tab?.id) return;
|
|
81
|
+
|
|
82
|
+
chrome.tabs.sendMessage(tab.id, {
|
|
83
|
+
action: info.menuItemId,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ─── Badge Updates ──────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
90
|
+
if (changeInfo.status !== 'complete') return;
|
|
91
|
+
if (!tab.url) return;
|
|
92
|
+
|
|
93
|
+
if (isServiceNowUrl(tab.url)) {
|
|
94
|
+
chrome.action.setBadgeText({ text: 'ON', tabId });
|
|
95
|
+
chrome.action.setBadgeBackgroundColor({ color: '#00D4AA', tabId });
|
|
96
|
+
} else {
|
|
97
|
+
chrome.action.setBadgeText({ text: '', tabId });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ─── Message Handler ────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
104
|
+
// Security: only accept messages from our own extension
|
|
105
|
+
if (sender.id !== chrome.runtime.id) return;
|
|
106
|
+
|
|
107
|
+
if (message.action === 'getSettings') {
|
|
108
|
+
chrome.storage.sync.get({
|
|
109
|
+
showTechnicalNames: false,
|
|
110
|
+
showUpdateSetBanner: false,
|
|
111
|
+
showFieldTypes: false,
|
|
112
|
+
enableNodeSwitcher: false,
|
|
113
|
+
enableQuickNav: false,
|
|
114
|
+
enableScriptHighlight: false,
|
|
115
|
+
enableFieldCopy: false,
|
|
116
|
+
enableAISidebar: false,
|
|
117
|
+
darkOverlay: false,
|
|
118
|
+
}, (settings) => {
|
|
119
|
+
sendResponse(settings);
|
|
120
|
+
});
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (message.action === 'openUrl') {
|
|
125
|
+
// Validate URL is a ServiceNow domain
|
|
126
|
+
if (isServiceNowUrl(message.url)) {
|
|
127
|
+
chrome.tabs.create({ url: message.url });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─── Cookie Handlers (for Node Switcher) ───────────────────────────────
|
|
132
|
+
// Content scripts can't set httpOnly cookies — must go through background
|
|
133
|
+
|
|
134
|
+
if (message.action === 'setCookie') {
|
|
135
|
+
// Security: only allow cookie ops on ServiceNow domains
|
|
136
|
+
if (!isServiceNowUrl(message.url)) {
|
|
137
|
+
sendResponse({ success: false, error: 'Invalid domain' });
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
const opts = {
|
|
141
|
+
url: message.url,
|
|
142
|
+
name: message.name,
|
|
143
|
+
value: message.value,
|
|
144
|
+
path: message.path || '/',
|
|
145
|
+
};
|
|
146
|
+
if (message.domain) opts.domain = message.domain;
|
|
147
|
+
if (message.httpOnly !== undefined) opts.httpOnly = message.httpOnly;
|
|
148
|
+
if (message.secure !== undefined) opts.secure = message.secure;
|
|
149
|
+
if (message.sameSite) opts.sameSite = message.sameSite;
|
|
150
|
+
if (message.expirationDate) {
|
|
151
|
+
opts.expirationDate = message.expirationDate;
|
|
152
|
+
} else {
|
|
153
|
+
opts.expirationDate = Math.floor(Date.now() / 1000) + 86400 * 365;
|
|
154
|
+
}
|
|
155
|
+
chrome.cookies.set(opts, (cookie) => {
|
|
156
|
+
sendResponse({ success: !!cookie, cookie: cookie });
|
|
157
|
+
});
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (message.action === 'getCookie') {
|
|
162
|
+
if (!isServiceNowUrl(message.url)) {
|
|
163
|
+
sendResponse({ cookie: null });
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
chrome.cookies.get({ url: message.url, name: message.name }, (cookie) => {
|
|
167
|
+
sendResponse({ cookie: cookie });
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (message.action === 'getAllCookies') {
|
|
173
|
+
if (!isServiceNowUrl(message.url)) {
|
|
174
|
+
sendResponse({ cookies: [] });
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
chrome.cookies.getAll({ url: message.url }, (cookies) => {
|
|
178
|
+
sendResponse({ cookies: cookies || [] });
|
|
179
|
+
});
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (message.action === 'removeCookie') {
|
|
184
|
+
if (!isServiceNowUrl(message.url)) {
|
|
185
|
+
sendResponse({ success: false });
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
chrome.cookies.remove({ url: message.url, name: message.name }, (details) => {
|
|
189
|
+
sendResponse({ success: !!details });
|
|
190
|
+
});
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ─── AI Chat: LLM API Proxy with Streaming ─────────────────────────────────
|
|
196
|
+
|
|
197
|
+
chrome.runtime.onConnect.addListener((port) => {
|
|
198
|
+
if (port.name !== 'nowaikit-ai-stream') return;
|
|
199
|
+
|
|
200
|
+
let aborted = false;
|
|
201
|
+
let activeReader = null;
|
|
202
|
+
|
|
203
|
+
// Handle port disconnect — abort streaming
|
|
204
|
+
port.onDisconnect.addListener(() => {
|
|
205
|
+
aborted = true;
|
|
206
|
+
if (activeReader) {
|
|
207
|
+
try { activeReader.cancel(); } catch (e) { /* ignore */ }
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
port.onMessage.addListener(async (msg) => {
|
|
212
|
+
if (msg.action !== 'nowaikit-ai-chat') return;
|
|
213
|
+
|
|
214
|
+
const { provider, apiKey, model, messages, ollamaUrl } = msg;
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
let url, headers, body;
|
|
218
|
+
|
|
219
|
+
switch (provider) {
|
|
220
|
+
case 'anthropic':
|
|
221
|
+
url = 'https://api.anthropic.com/v1/messages';
|
|
222
|
+
headers = {
|
|
223
|
+
'Content-Type': 'application/json',
|
|
224
|
+
'x-api-key': apiKey,
|
|
225
|
+
'anthropic-version': '2023-06-01',
|
|
226
|
+
'anthropic-dangerous-direct-browser-access': 'true',
|
|
227
|
+
};
|
|
228
|
+
const systemMsg = messages.find(m => m.role === 'system');
|
|
229
|
+
const nonSystemMsgs = messages.filter(m => m.role !== 'system');
|
|
230
|
+
body = JSON.stringify({
|
|
231
|
+
model: model,
|
|
232
|
+
max_tokens: 4096,
|
|
233
|
+
system: systemMsg ? systemMsg.content : '',
|
|
234
|
+
messages: nonSystemMsgs,
|
|
235
|
+
stream: true,
|
|
236
|
+
});
|
|
237
|
+
break;
|
|
238
|
+
|
|
239
|
+
case 'google':
|
|
240
|
+
url = 'https://generativelanguage.googleapis.com/v1beta/models/' + encodeURIComponent(model) + ':streamGenerateContent?alt=sse';
|
|
241
|
+
headers = {
|
|
242
|
+
'Content-Type': 'application/json',
|
|
243
|
+
'x-goog-api-key': apiKey,
|
|
244
|
+
};
|
|
245
|
+
const googleContents = messages
|
|
246
|
+
.filter(m => m.role !== 'system')
|
|
247
|
+
.map(m => ({
|
|
248
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
249
|
+
parts: [{ text: m.content }],
|
|
250
|
+
}));
|
|
251
|
+
const googleSystem = messages.find(m => m.role === 'system');
|
|
252
|
+
body = JSON.stringify({
|
|
253
|
+
contents: googleContents,
|
|
254
|
+
systemInstruction: googleSystem ? { parts: [{ text: googleSystem.content }] } : undefined,
|
|
255
|
+
});
|
|
256
|
+
break;
|
|
257
|
+
|
|
258
|
+
case 'ollama':
|
|
259
|
+
url = (ollamaUrl || 'http://localhost:11434') + '/api/chat';
|
|
260
|
+
headers = { 'Content-Type': 'application/json' };
|
|
261
|
+
body = JSON.stringify({
|
|
262
|
+
model: model,
|
|
263
|
+
messages: messages,
|
|
264
|
+
stream: true,
|
|
265
|
+
});
|
|
266
|
+
break;
|
|
267
|
+
|
|
268
|
+
case 'openrouter':
|
|
269
|
+
url = 'https://openrouter.ai/api/v1/chat/completions';
|
|
270
|
+
headers = {
|
|
271
|
+
'Content-Type': 'application/json',
|
|
272
|
+
'Authorization': 'Bearer ' + apiKey,
|
|
273
|
+
'HTTP-Referer': 'https://nowaikit.com',
|
|
274
|
+
'X-Title': 'NowAIKit Utils',
|
|
275
|
+
};
|
|
276
|
+
body = JSON.stringify({
|
|
277
|
+
model: model,
|
|
278
|
+
messages: messages,
|
|
279
|
+
stream: true,
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
|
|
283
|
+
case 'openai':
|
|
284
|
+
default:
|
|
285
|
+
url = 'https://api.openai.com/v1/chat/completions';
|
|
286
|
+
headers = {
|
|
287
|
+
'Content-Type': 'application/json',
|
|
288
|
+
'Authorization': 'Bearer ' + apiKey,
|
|
289
|
+
};
|
|
290
|
+
body = JSON.stringify({
|
|
291
|
+
model: model,
|
|
292
|
+
messages: messages,
|
|
293
|
+
stream: true,
|
|
294
|
+
});
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const response = await fetch(url, {
|
|
299
|
+
method: 'POST',
|
|
300
|
+
headers: headers,
|
|
301
|
+
body: body,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (!response.ok) {
|
|
305
|
+
const errText = await response.text();
|
|
306
|
+
let errMsg = 'API error (' + response.status + ')';
|
|
307
|
+
try {
|
|
308
|
+
const errJson = JSON.parse(errText);
|
|
309
|
+
errMsg = errJson.error?.message || errJson.message || errMsg;
|
|
310
|
+
} catch (e) {
|
|
311
|
+
errMsg = errText.substring(0, 200) || errMsg;
|
|
312
|
+
}
|
|
313
|
+
if (!aborted) {
|
|
314
|
+
try { port.postMessage({ type: 'error', content: errMsg }); } catch (e) { /* port closed */ }
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Stream the response
|
|
320
|
+
const reader = response.body.getReader();
|
|
321
|
+
activeReader = reader;
|
|
322
|
+
const decoder = new TextDecoder();
|
|
323
|
+
let buffer = '';
|
|
324
|
+
|
|
325
|
+
while (!aborted) {
|
|
326
|
+
const { done, value } = await reader.read();
|
|
327
|
+
if (done) break;
|
|
328
|
+
|
|
329
|
+
buffer += decoder.decode(value, { stream: true });
|
|
330
|
+
const lines = buffer.split('\n');
|
|
331
|
+
buffer = lines.pop() || '';
|
|
332
|
+
|
|
333
|
+
for (const line of lines) {
|
|
334
|
+
if (aborted) break;
|
|
335
|
+
const trimmed = line.trim();
|
|
336
|
+
if (!trimmed || trimmed === 'data: [DONE]') continue;
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
let json;
|
|
340
|
+
let token = '';
|
|
341
|
+
|
|
342
|
+
if (provider === 'ollama') {
|
|
343
|
+
json = JSON.parse(trimmed);
|
|
344
|
+
if (json.message?.content) {
|
|
345
|
+
token = json.message.content;
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
if (!trimmed.startsWith('data: ')) continue;
|
|
349
|
+
json = JSON.parse(trimmed.slice(6));
|
|
350
|
+
|
|
351
|
+
if (provider === 'anthropic') {
|
|
352
|
+
if (json.type === 'content_block_delta' && json.delta?.text) {
|
|
353
|
+
token = json.delta.text;
|
|
354
|
+
}
|
|
355
|
+
} else if (provider === 'google') {
|
|
356
|
+
if (json.candidates?.[0]?.content?.parts?.[0]?.text) {
|
|
357
|
+
token = json.candidates[0].content.parts[0].text;
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
if (json.choices?.[0]?.delta?.content) {
|
|
361
|
+
token = json.choices[0].delta.content;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (token && !aborted) {
|
|
367
|
+
try { port.postMessage({ type: 'token', content: token }); } catch (e) { aborted = true; break; }
|
|
368
|
+
}
|
|
369
|
+
} catch (e) {
|
|
370
|
+
// Skip malformed JSON lines
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Handle remaining buffer for Ollama
|
|
376
|
+
if (!aborted && provider === 'ollama' && buffer.trim()) {
|
|
377
|
+
try {
|
|
378
|
+
const json = JSON.parse(buffer.trim());
|
|
379
|
+
if (json.message?.content) {
|
|
380
|
+
try { port.postMessage({ type: 'token', content: json.message.content }); } catch (e) { /* port closed */ }
|
|
381
|
+
}
|
|
382
|
+
} catch (e) {
|
|
383
|
+
// Ignore
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
activeReader = null;
|
|
388
|
+
if (!aborted) {
|
|
389
|
+
try { port.postMessage({ type: 'done' }); } catch (e) { /* port closed */ }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
} catch (err) {
|
|
393
|
+
if (!aborted) {
|
|
394
|
+
try { port.postMessage({ type: 'error', content: err.message || 'Failed to connect to AI provider' }); } catch (e) { /* port closed */ }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
});
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, resolve } from 'path';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf8'));
|
|
9
|
+
|
|
10
|
+
const TEAL = '\x1b[36m';
|
|
11
|
+
const BOLD = '\x1b[1m';
|
|
12
|
+
const DIM = '\x1b[2m';
|
|
13
|
+
const RESET = '\x1b[0m';
|
|
14
|
+
const GREEN = '\x1b[32m';
|
|
15
|
+
const YELLOW = '\x1b[33m';
|
|
16
|
+
|
|
17
|
+
console.log(`
|
|
18
|
+
${TEAL}${BOLD} _ _ _ ___ _ ___ _
|
|
19
|
+
| \\ | | _____ __/ \\ |_ _| |/ (_) |_
|
|
20
|
+
| \\| |/ _ \\ \\ /\\ / / _ \\ | || ' /| | __|
|
|
21
|
+
| |\\ | (_) \\ V V / ___ \\ | || . \\| | |_
|
|
22
|
+
|_| \\_|\\___/ \\_/\\_/_/ \\_\\___|_|\\_\\_|\\__|
|
|
23
|
+
${DIM}Utils${RESET}
|
|
24
|
+
${TEAL}${BOLD} The AI-Powered ServiceNow Browser Extension${RESET}
|
|
25
|
+
${DIM} v${pkg.version}${RESET}
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
console.log(`${BOLD}Features:${RESET}
|
|
29
|
+
${GREEN}+${RESET} AI Assistant with 5 providers (OpenAI, Anthropic, Google, OpenRouter, Ollama)
|
|
30
|
+
${GREEN}+${RESET} 22 code templates (GlideRecord, GlideQuery, Business Rules, REST, etc.)
|
|
31
|
+
${GREEN}+${RESET} 12 slash commands for instant navigation
|
|
32
|
+
${GREEN}+${RESET} 10 keyboard shortcuts for zero-mouse workflow
|
|
33
|
+
${GREEN}+${RESET} Node switcher with cluster-aware discovery
|
|
34
|
+
${GREEN}+${RESET} Two-way script sync with VS Code (NowAIKit Builder)
|
|
35
|
+
${GREEN}+${RESET} Multi-instance management with color-coded environments
|
|
36
|
+
${GREEN}+${RESET} Technical name resolver, field copy, syntax highlighting
|
|
37
|
+
${GREEN}+${RESET} AES-256-GCM encrypted API key storage
|
|
38
|
+
${GREEN}+${RESET} Zero telemetry, domain-scoped, CSP enforced
|
|
39
|
+
`);
|
|
40
|
+
|
|
41
|
+
console.log(`${BOLD}Installation:${RESET}
|
|
42
|
+
|
|
43
|
+
${YELLOW}Option 1: Chrome Web Store${RESET}
|
|
44
|
+
Visit: https://chrome.google.com/webstore/detail/nowaikit-utils
|
|
45
|
+
|
|
46
|
+
${YELLOW}Option 2: Load Unpacked (Developer Mode)${RESET}
|
|
47
|
+
1. Extension files are installed at:
|
|
48
|
+
${TEAL}${resolve(__dirname)}${RESET}
|
|
49
|
+
2. Open Chrome and navigate to: ${TEAL}chrome://extensions${RESET}
|
|
50
|
+
3. Enable ${BOLD}Developer mode${RESET} (toggle in top-right)
|
|
51
|
+
4. Click ${BOLD}Load unpacked${RESET}
|
|
52
|
+
5. Select the directory above
|
|
53
|
+
`);
|
|
54
|
+
|
|
55
|
+
console.log(`${BOLD}Part of the NowAIKit Ecosystem:${RESET}
|
|
56
|
+
${DIM}NowAIKit MCP${RESET} — 400+ tools, connect any AI to ServiceNow
|
|
57
|
+
${DIM}NowAIKit Builder${RESET} — VS Code extension with 10 Copilot agents
|
|
58
|
+
${TEAL}NowAIKit Utils${RESET} — Chrome extension (you are here)
|
|
59
|
+
${DIM}NowAIKit Apex${RESET} — CLI with 26 scan/review/build capabilities
|
|
60
|
+
`);
|
|
61
|
+
|
|
62
|
+
console.log(`${DIM}GitHub: https://github.com/aartiq/nowaikit-utils-browser${RESET}`);
|
|
63
|
+
console.log(`${DIM}Website: https://nowaikit.com/products/utils${RESET}`);
|
|
64
|
+
console.log(`${DIM}npm: https://www.npmjs.com/package/nowaikit-utils${RESET}`);
|
|
65
|
+
console.log();
|