zd-agent-cli 0.1.1

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.
@@ -0,0 +1,327 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readTicketByIdApi = readTicketByIdApi;
4
+ exports.findViewByNameApi = findViewByNameApi;
5
+ exports.readQueueByViewIdApi = readQueueByViewIdApi;
6
+ exports.searchTicketsApi = searchTicketsApi;
7
+ exports.readCurrentUserApi = readCurrentUserApi;
8
+ const util_1 = require("./util");
9
+ function normalizeBaseUrl(raw) {
10
+ const text = String(raw || '').trim();
11
+ if (!text) {
12
+ return '';
13
+ }
14
+ try {
15
+ const url = new URL(text);
16
+ return `${url.protocol}//${url.host}`;
17
+ }
18
+ catch (_) {
19
+ return '';
20
+ }
21
+ }
22
+ function inferBaseUrl(page, options = {}) {
23
+ const fromOpt = normalizeBaseUrl(options.baseUrl);
24
+ if (fromOpt) {
25
+ return fromOpt;
26
+ }
27
+ return normalizeBaseUrl(page && typeof page.url === 'function' ? page.url() : '');
28
+ }
29
+ function agentUrl(baseUrl, suffix) {
30
+ const path = String(suffix || '').startsWith('/') ? suffix : `/${String(suffix || '')}`;
31
+ if (!baseUrl) {
32
+ return path;
33
+ }
34
+ return `${baseUrl}${path}`;
35
+ }
36
+ async function apiGet(page, path) {
37
+ try {
38
+ return await page.evaluate(async (endpoint) => {
39
+ try {
40
+ const res = await fetch(endpoint, {
41
+ credentials: 'include',
42
+ headers: {
43
+ Accept: 'application/json'
44
+ }
45
+ });
46
+ const text = await res.text();
47
+ let data = null;
48
+ try {
49
+ data = text ? JSON.parse(text) : null;
50
+ }
51
+ catch (_) {
52
+ data = null;
53
+ }
54
+ return { ok: res.ok, status: res.status, data };
55
+ }
56
+ catch (error) {
57
+ return { ok: false, status: 0, error: String(error && error.message ? error.message : error), data: null };
58
+ }
59
+ }, path);
60
+ }
61
+ catch (error) {
62
+ const message = String(error && error.message ? error.message : error);
63
+ return { ok: false, status: 0, error: message, data: null };
64
+ }
65
+ }
66
+ async function apiGetAllPages(page, initialPath, options = {}) {
67
+ const maxItems = Math.max(1, Number(options.maxItems) || 100);
68
+ const maxPages = Math.max(1, Number(options.maxPages) || 20);
69
+ const itemKey = options.itemKey || 'results';
70
+ let nextPath = initialPath;
71
+ let pageCount = 0;
72
+ const out = [];
73
+ while (nextPath && pageCount < maxPages && out.length < maxItems) {
74
+ const res = await apiGet(page, nextPath);
75
+ if (!res.ok || !res.data) {
76
+ break;
77
+ }
78
+ const rows = Array.isArray(res.data[itemKey]) ? res.data[itemKey] : [];
79
+ const remaining = maxItems - out.length;
80
+ out.push(...rows.slice(0, remaining));
81
+ const nextPage = res.data.next_page;
82
+ if (!nextPage || typeof nextPage !== 'string') {
83
+ break;
84
+ }
85
+ try {
86
+ const url = new URL(nextPage);
87
+ nextPath = `${url.pathname}${url.search}`;
88
+ }
89
+ catch (_) {
90
+ nextPath = null;
91
+ }
92
+ pageCount += 1;
93
+ }
94
+ return out;
95
+ }
96
+ function scoreNameMatch(name, target) {
97
+ const low = (0, util_1.clean)(name).toLowerCase();
98
+ const query = (0, util_1.clean)(target).toLowerCase();
99
+ if (!low || !query) {
100
+ return -1;
101
+ }
102
+ if (low === query)
103
+ return 100;
104
+ if (low.includes(query))
105
+ return 80;
106
+ if (query.includes(low))
107
+ return 60;
108
+ return -1;
109
+ }
110
+ async function fetchUsersMap(page, ids = []) {
111
+ const uniq = [...new Set(ids.map((id) => String(id || '').trim()).filter(Boolean))];
112
+ if (!uniq.length) {
113
+ return {};
114
+ }
115
+ const res = await apiGet(page, `/api/v2/users/show_many.json?ids=${encodeURIComponent(uniq.join(','))}`);
116
+ if (!res.ok || !res.data || !Array.isArray(res.data.users)) {
117
+ return {};
118
+ }
119
+ const map = {};
120
+ for (const user of res.data.users) {
121
+ if (!user || user.id === undefined || user.id === null) {
122
+ continue;
123
+ }
124
+ map[String(user.id)] = (0, util_1.clean)(user.name || user.email || '') || String(user.id);
125
+ }
126
+ return map;
127
+ }
128
+ async function readTicketByIdApi(page, ticketId, options = {}) {
129
+ const id = String(ticketId || '').replace(/\D+/g, '');
130
+ if (!id) {
131
+ return null;
132
+ }
133
+ const baseUrl = inferBaseUrl(page, options);
134
+ const commentsCount = Math.max(1, Number(options.count) || 10);
135
+ const ticketRes = await apiGet(page, `/api/v2/tickets/${id}.json`);
136
+ if (!ticketRes.ok || !ticketRes.data || !ticketRes.data.ticket) {
137
+ return null;
138
+ }
139
+ const commentsRes = await apiGet(page, `/api/v2/tickets/${id}/comments.json?sort_order=desc`);
140
+ const rawComments = commentsRes.ok && commentsRes.data && Array.isArray(commentsRes.data.comments)
141
+ ? commentsRes.data.comments
142
+ : [];
143
+ const ticket = ticketRes.data.ticket;
144
+ const actorIds = [ticket.requester_id, ticket.assignee_id, ...rawComments.map((c) => c && c.author_id)].filter(Boolean);
145
+ const usersById = await fetchUsersMap(page, actorIds);
146
+ const comments = rawComments
147
+ .slice(0, commentsCount)
148
+ .reverse()
149
+ .map((c) => ({
150
+ author: usersById[String(c.author_id)] || (c.author_id ? String(c.author_id) : null),
151
+ time: c.created_at || null,
152
+ text: (0, util_1.clean)(c.plain_body || c.body || '').slice(0, 4000)
153
+ }))
154
+ .filter((c) => c.text);
155
+ return {
156
+ pageUrl: agentUrl(baseUrl, `/agent/tickets/${id}`),
157
+ pageTitle: null,
158
+ ticketId: String(ticket.id),
159
+ subject: (0, util_1.clean)(ticket.subject || '') || null,
160
+ status: (0, util_1.clean)(ticket.status || '') || null,
161
+ priority: (0, util_1.clean)(ticket.priority || '') || null,
162
+ assignee: usersById[String(ticket.assignee_id)] || (ticket.assignee_id ? String(ticket.assignee_id) : null),
163
+ requester: usersById[String(ticket.requester_id)] || (ticket.requester_id ? String(ticket.requester_id) : null),
164
+ tags: Array.isArray(ticket.tags) ? ticket.tags : [],
165
+ comments,
166
+ source: 'api'
167
+ };
168
+ }
169
+ async function findViewByNameApi(page, queueName) {
170
+ const requested = (0, util_1.clean)(queueName);
171
+ if (!requested) {
172
+ return null;
173
+ }
174
+ const baseUrl = inferBaseUrl(page);
175
+ if (/^\d+$/.test(requested)) {
176
+ const id = requested;
177
+ return {
178
+ id,
179
+ score: 100,
180
+ name: requested,
181
+ href: agentUrl(baseUrl, `/agent/filters/${id}`),
182
+ source: 'id-api'
183
+ };
184
+ }
185
+ const responses = [
186
+ await apiGet(page, '/api/v2/views.json?page[size]=100'),
187
+ await apiGet(page, '/api/v2/views/active.json?page[size]=100')
188
+ ];
189
+ const allViews = [];
190
+ for (const res of responses) {
191
+ if (!res.ok || !res.data || !Array.isArray(res.data.views)) {
192
+ continue;
193
+ }
194
+ allViews.push(...res.data.views);
195
+ }
196
+ if (!allViews.length) {
197
+ return null;
198
+ }
199
+ let best = null;
200
+ for (const view of allViews) {
201
+ if (!view || !view.id) {
202
+ continue;
203
+ }
204
+ const name = (0, util_1.clean)(view.title || '');
205
+ if (!name) {
206
+ continue;
207
+ }
208
+ const score = scoreNameMatch(name, requested);
209
+ if (score < 0) {
210
+ continue;
211
+ }
212
+ if (!best || score > best.score) {
213
+ best = {
214
+ id: String(view.id),
215
+ score,
216
+ name,
217
+ href: agentUrl(baseUrl, `/agent/filters/${view.id}`),
218
+ source: 'api'
219
+ };
220
+ }
221
+ }
222
+ return best;
223
+ }
224
+ async function readQueueByViewIdApi(page, viewId, options = {}) {
225
+ const id = String(viewId || '').replace(/\D+/g, '');
226
+ if (!id) {
227
+ return null;
228
+ }
229
+ const baseUrl = inferBaseUrl(page, options);
230
+ const requestedCount = Number(options.count);
231
+ const fetchAll = options.fetchAll === true || !Number.isFinite(requestedCount) || requestedCount <= 0;
232
+ const count = fetchAll ? Number.MAX_SAFE_INTEGER : Math.max(1, Math.floor(requestedCount));
233
+ const viewRes = await apiGet(page, `/api/v2/views/${id}.json`);
234
+ const rawTickets = await apiGetAllPages(page, `/api/v2/views/${id}/tickets.json?per_page=${Math.min(count, 100)}`, {
235
+ maxItems: count,
236
+ maxPages: 500,
237
+ itemKey: 'tickets'
238
+ });
239
+ if (!rawTickets.length) {
240
+ return null;
241
+ }
242
+ const userIds = rawTickets
243
+ .flatMap((t) => {
244
+ const ids = [];
245
+ if (t && t.requester_id !== undefined && t.requester_id !== null) {
246
+ ids.push(String(t.requester_id));
247
+ }
248
+ if (t && t.assignee_id !== undefined && t.assignee_id !== null) {
249
+ ids.push(String(t.assignee_id));
250
+ }
251
+ return ids;
252
+ })
253
+ .filter(Boolean);
254
+ const usersById = await fetchUsersMap(page, userIds);
255
+ const tickets = rawTickets.map((t) => ({
256
+ ticketId: t && t.id !== undefined && t.id !== null ? String(t.id) : null,
257
+ subject: (0, util_1.clean)((t && t.subject) || ''),
258
+ status: (0, util_1.clean)((t && t.status) || ''),
259
+ assigneeId: t && t.assignee_id ? String(t.assignee_id) : null,
260
+ assignee: t && t.assignee_id
261
+ ? usersById[String(t.assignee_id)] || String(t.assignee_id)
262
+ : null,
263
+ requesterId: t && t.requester_id ? String(t.requester_id) : null,
264
+ requester: t && t.requester_id
265
+ ? usersById[String(t.requester_id)] || String(t.requester_id)
266
+ : null,
267
+ url: t && t.id ? agentUrl(baseUrl, `/agent/tickets/${t.id}`) : null
268
+ }));
269
+ const queueName = viewRes.ok && viewRes.data && viewRes.data.view && (0, util_1.clean)(viewRes.data.view.title || '')
270
+ ? (0, util_1.clean)(viewRes.data.view.title || '')
271
+ : null;
272
+ return {
273
+ pageUrl: agentUrl(baseUrl, `/agent/filters/${id}`),
274
+ pageTitle: null,
275
+ queueName,
276
+ source: 'api',
277
+ fullSync: fetchAll,
278
+ resultCount: tickets.length,
279
+ tickets
280
+ };
281
+ }
282
+ async function searchTicketsApi(page, query, options = {}) {
283
+ const q = (0, util_1.clean)(query);
284
+ if (!q) {
285
+ return null;
286
+ }
287
+ const baseUrl = inferBaseUrl(page, options);
288
+ const count = Math.max(1, Number(options.count) || 20);
289
+ const rows = await apiGetAllPages(page, `/api/v2/search.json?query=${encodeURIComponent(`type:ticket ${q}`)}&per_page=${Math.min(count, 100)}`, {
290
+ maxItems: count,
291
+ maxPages: 50,
292
+ itemKey: 'results'
293
+ });
294
+ if (!rows.length) {
295
+ return null;
296
+ }
297
+ const results = rows
298
+ .filter((row) => row && row.result_type === 'ticket')
299
+ .slice(0, count)
300
+ .map((row) => ({
301
+ ticketId: row.id ? String(row.id) : null,
302
+ title: (0, util_1.clean)(row.subject || ''),
303
+ snippet: (0, util_1.clean)(row.description || '').slice(0, 500),
304
+ url: row.id ? agentUrl(baseUrl, `/agent/tickets/${row.id}`) : null
305
+ }));
306
+ return {
307
+ pageUrl: agentUrl(baseUrl, `/agent/search/1?query=${encodeURIComponent(q)}`),
308
+ pageTitle: null,
309
+ query: q,
310
+ source: 'api',
311
+ resultCount: results.length,
312
+ results
313
+ };
314
+ }
315
+ async function readCurrentUserApi(page) {
316
+ const res = await apiGet(page, '/api/v2/users/me.json');
317
+ if (!res.ok || !res.data || !res.data.user) {
318
+ return null;
319
+ }
320
+ const user = res.data.user;
321
+ return {
322
+ id: user.id !== undefined && user.id !== null ? String(user.id) : null,
323
+ name: (0, util_1.clean)(user.name || '') || null,
324
+ email: (0, util_1.clean)(user.email || '') || null,
325
+ role: (0, util_1.clean)(user.role || '') || null
326
+ };
327
+ }
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getZendeskPage = getZendeskPage;
37
+ exports.openTicketById = openTicketById;
38
+ exports.readCurrentTicket = readCurrentTicket;
39
+ exports.openQueueByName = openQueueByName;
40
+ exports.readQueueTickets = readQueueTickets;
41
+ exports.runTicketSearch = runTicketSearch;
42
+ exports.readCurrentUser = readCurrentUser;
43
+ exports.isAuthenticated = isAuthenticated;
44
+ const constants_1 = require("./constants");
45
+ const util_1 = require("./util");
46
+ const dom = __importStar(require("./dom"));
47
+ const api = __importStar(require("./api"));
48
+ function getBaseUrlFromPage(page) {
49
+ try {
50
+ const url = new URL(page.url());
51
+ return `${url.protocol}//${url.host}`;
52
+ }
53
+ catch (_) {
54
+ return '';
55
+ }
56
+ }
57
+ function parseViewIdFromUrl(pageUrl = '') {
58
+ const m = String(pageUrl).match(/\/agent\/filters\/(\d+)/i);
59
+ return m ? m[1] : null;
60
+ }
61
+ function parseTicketIdFromUrl(pageUrl = '') {
62
+ const m = String(pageUrl).match(/\/agent\/tickets\/(\d+)/i);
63
+ return m ? m[1] : null;
64
+ }
65
+ async function getZendeskPage(context, startUrl, background) {
66
+ return dom.getZendeskPage(context, startUrl, background);
67
+ }
68
+ async function openTicketById(page, ticketId, uiWaitMs = constants_1.DEFAULT_UI_WAIT_MS) {
69
+ return dom.openTicketByIdDom(page, ticketId, uiWaitMs);
70
+ }
71
+ async function readCurrentTicket(page, options = {}) {
72
+ const count = Math.max(1, Number(options.count) || 10);
73
+ const ticketId = parseTicketIdFromUrl(page.url());
74
+ const baseUrl = getBaseUrlFromPage(page);
75
+ if (ticketId) {
76
+ const apiResult = await api.readTicketByIdApi(page, ticketId, { count, baseUrl });
77
+ if (apiResult) {
78
+ return {
79
+ ...apiResult,
80
+ pageUrl: page.url(),
81
+ pageTitle: (await page.title().catch(() => null)) || apiResult.pageTitle || null
82
+ };
83
+ }
84
+ }
85
+ return dom.readCurrentTicketDom(page, { count });
86
+ }
87
+ async function openQueueByName(page, queueName, uiWaitMs = constants_1.DEFAULT_UI_WAIT_MS, options = {}) {
88
+ const requested = (0, util_1.clean)(queueName);
89
+ const queuePath = (0, util_1.clean)(options.queuePath || '');
90
+ if (!requested && !queuePath) {
91
+ throw new Error('Queue name or queue path is required.');
92
+ }
93
+ if (queuePath) {
94
+ return dom.openQueueByPathDom(page, queuePath, uiWaitMs);
95
+ }
96
+ await dom.waitForAgentReady(page, uiWaitMs);
97
+ const matchedApi = await api.findViewByNameApi(page, requested);
98
+ if (matchedApi && matchedApi.href) {
99
+ await page.goto(matchedApi.href, { waitUntil: 'domcontentloaded', timeout: 45000 });
100
+ await dom.waitForAgentReady(page, uiWaitMs);
101
+ return matchedApi;
102
+ }
103
+ return dom.openQueueByNameDom(page, requested, uiWaitMs);
104
+ }
105
+ async function readQueueTickets(page, options = {}) {
106
+ const requestedCount = Number(options.count);
107
+ const hasCount = Number.isFinite(requestedCount) && requestedCount > 0;
108
+ const count = hasCount ? Math.max(1, Math.floor(requestedCount)) : Number.MAX_SAFE_INTEGER;
109
+ const fetchAll = options.fetchAll === true || !hasCount;
110
+ const viewId = parseViewIdFromUrl(page.url());
111
+ const baseUrl = getBaseUrlFromPage(page);
112
+ if (viewId) {
113
+ const apiResult = await api.readQueueByViewIdApi(page, viewId, { count, fetchAll, baseUrl });
114
+ if (apiResult) {
115
+ return {
116
+ ...apiResult,
117
+ pageUrl: page.url(),
118
+ pageTitle: (await page.title().catch(() => null)) || apiResult.pageTitle || null
119
+ };
120
+ }
121
+ }
122
+ return dom.readQueueTicketsDom(page, { count });
123
+ }
124
+ async function runTicketSearch(page, query, count = 20, uiWaitMs = constants_1.DEFAULT_UI_WAIT_MS) {
125
+ await dom.waitForAgentReady(page, uiWaitMs);
126
+ const baseUrl = getBaseUrlFromPage(page);
127
+ const apiResult = await api.searchTicketsApi(page, query, { count, baseUrl });
128
+ if (apiResult && apiResult.resultCount > 0) {
129
+ return {
130
+ ...apiResult,
131
+ pageUrl: baseUrl
132
+ ? `${baseUrl}/agent/search/1?query=${encodeURIComponent((0, util_1.clean)(query))}`
133
+ : apiResult.pageUrl,
134
+ pageTitle: (await page.title().catch(() => null)) || apiResult.pageTitle || null
135
+ };
136
+ }
137
+ const domResult = await dom.runTicketSearchDom(page, query, count, uiWaitMs);
138
+ if (domResult && domResult.resultCount > 0) {
139
+ return domResult;
140
+ }
141
+ return apiResult || domResult;
142
+ }
143
+ async function readCurrentUser(page) {
144
+ return api.readCurrentUserApi(page);
145
+ }
146
+ async function isAuthenticated(page) {
147
+ const user = await readCurrentUser(page);
148
+ return Boolean(user && user.id);
149
+ }