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.
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/cli.js +85 -0
- package/dist/cmds/auth-check.js +63 -0
- package/dist/cmds/auth-login.js +38 -0
- package/dist/cmds/doctor.js +76 -0
- package/dist/cmds/queue-list.js +32 -0
- package/dist/cmds/queue-read.js +44 -0
- package/dist/cmds/search-tickets.js +23 -0
- package/dist/cmds/ticket-read.js +52 -0
- package/dist/core/api.js +327 -0
- package/dist/core/automation.js +149 -0
- package/dist/core/browser-cdp.js +285 -0
- package/dist/core/config.js +287 -0
- package/dist/core/constants.js +13 -0
- package/dist/core/dom.js +360 -0
- package/dist/core/facade.js +2 -0
- package/dist/core/index.js +58 -0
- package/dist/core/runtime.js +316 -0
- package/dist/core/storage.js +114 -0
- package/dist/core/util.js +42 -0
- package/dist/types.js +2 -0
- package/package.json +57 -0
- package/zendesk.config.example.json +28 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveGlobalOpts = resolveGlobalOpts;
|
|
7
|
+
exports.emitResult = emitResult;
|
|
8
|
+
exports.withZendeskBrowser = withZendeskBrowser;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const playwright_1 = require("playwright");
|
|
12
|
+
const browser_cdp_1 = require("./browser-cdp");
|
|
13
|
+
const constants_1 = require("./constants");
|
|
14
|
+
const config_1 = require("./config");
|
|
15
|
+
const storage_1 = require("./storage");
|
|
16
|
+
const automation_1 = require("./automation");
|
|
17
|
+
function resolveGlobalOpts(program) {
|
|
18
|
+
const opts = program.opts();
|
|
19
|
+
const env = process.env;
|
|
20
|
+
const loadedConfig = (0, config_1.loadResolvedConfig)({
|
|
21
|
+
cwd: process.cwd(),
|
|
22
|
+
configPath: opts.config || ''
|
|
23
|
+
});
|
|
24
|
+
const cfg = loadedConfig.config || {};
|
|
25
|
+
const queueCfg = loadedConfig.queueConfig || { defaultQueue: '', queues: {} };
|
|
26
|
+
const configValidation = (0, config_1.validateConfigContract)(cfg, queueCfg);
|
|
27
|
+
const cdpUrl = (0, config_1.pickString)({
|
|
28
|
+
cliValue: opts.cdpUrl,
|
|
29
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'cdpUrl'),
|
|
30
|
+
envValue: env.ZENDESK_CDP_URL,
|
|
31
|
+
configValue: cfg.cdpUrl,
|
|
32
|
+
fallback: constants_1.DEFAULT_CDP_URL
|
|
33
|
+
});
|
|
34
|
+
let domain = (0, config_1.pickString)({
|
|
35
|
+
cliValue: opts.domain,
|
|
36
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'domain'),
|
|
37
|
+
envValue: env.ZENDESK_DOMAIN,
|
|
38
|
+
configValue: cfg.domain,
|
|
39
|
+
fallback: constants_1.DEFAULT_DOMAIN
|
|
40
|
+
}).replace(/^https?:\/\//i, '').replace(/\/+$/, '');
|
|
41
|
+
if (domain.includes('/')) {
|
|
42
|
+
domain = domain.split('/')[0];
|
|
43
|
+
}
|
|
44
|
+
const startPath = (0, config_1.normalizeAgentPath)((0, config_1.pickString)({
|
|
45
|
+
cliValue: opts.startPath,
|
|
46
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'startPath'),
|
|
47
|
+
envValue: env.ZENDESK_START_PATH,
|
|
48
|
+
configValue: cfg.startPath,
|
|
49
|
+
fallback: constants_1.DEFAULT_START_PATH
|
|
50
|
+
}), constants_1.DEFAULT_START_PATH);
|
|
51
|
+
if (startPath && !/^\/agent\//i.test(startPath)) {
|
|
52
|
+
throw new Error(`Invalid startPath "${startPath}". startPath must begin with "/agent/".`);
|
|
53
|
+
}
|
|
54
|
+
const startUrl = domain ? `https://${domain}${startPath}` : '';
|
|
55
|
+
const uiWaitMs = (0, config_1.pickNumber)({
|
|
56
|
+
cliValue: opts.uiWaitMs,
|
|
57
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'uiWaitMs'),
|
|
58
|
+
envValue: env.ZENDESK_UI_WAIT_MS,
|
|
59
|
+
configValue: cfg.uiWaitMs,
|
|
60
|
+
fallback: constants_1.DEFAULT_UI_WAIT_MS
|
|
61
|
+
});
|
|
62
|
+
const storeRootRaw = (0, config_1.pickString)({
|
|
63
|
+
cliValue: opts.storeRoot,
|
|
64
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'storeRoot'),
|
|
65
|
+
envValue: env.ZENDESK_STORE_ROOT,
|
|
66
|
+
configValue: cfg.storeRoot,
|
|
67
|
+
fallback: constants_1.DEFAULT_STORE_ROOT
|
|
68
|
+
});
|
|
69
|
+
const profileDirRaw = (0, config_1.pickString)({
|
|
70
|
+
cliValue: opts.profileDir,
|
|
71
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'profileDir'),
|
|
72
|
+
envValue: env.ZENDESK_PROFILE_DIR,
|
|
73
|
+
configValue: cfg.profileDir,
|
|
74
|
+
fallback: constants_1.DEFAULT_PROFILE_DIR
|
|
75
|
+
});
|
|
76
|
+
const defaultQueue = (0, config_1.pickString)({
|
|
77
|
+
cliValue: '',
|
|
78
|
+
cliSource: '',
|
|
79
|
+
envValue: env.ZENDESK_DEFAULT_QUEUE,
|
|
80
|
+
configValue: queueCfg.defaultQueue,
|
|
81
|
+
fallback: ''
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
cdpUrl,
|
|
85
|
+
domain,
|
|
86
|
+
startPath,
|
|
87
|
+
profileDir: (0, config_1.toAbsPath)(profileDirRaw, loadedConfig.repoRoot),
|
|
88
|
+
startUrl,
|
|
89
|
+
noLaunch: (0, config_1.pickBool)({
|
|
90
|
+
cliValue: opts.noLaunch,
|
|
91
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'noLaunch'),
|
|
92
|
+
envValue: env.ZENDESK_NO_LAUNCH,
|
|
93
|
+
configValue: cfg.noLaunch,
|
|
94
|
+
fallback: false
|
|
95
|
+
}),
|
|
96
|
+
allowSharedCdp: (0, config_1.pickBool)({
|
|
97
|
+
cliValue: opts.allowSharedCdp,
|
|
98
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'allowSharedCdp'),
|
|
99
|
+
envValue: env.ZENDESK_ALLOW_SHARED_CDP,
|
|
100
|
+
configValue: cfg.allowSharedCdp,
|
|
101
|
+
fallback: false
|
|
102
|
+
}),
|
|
103
|
+
autoPort: !(0, config_1.pickBool)({
|
|
104
|
+
cliValue: opts.autoPort === false,
|
|
105
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'autoPort'),
|
|
106
|
+
envValue: env.ZENDESK_NO_AUTO_PORT,
|
|
107
|
+
configValue: cfg.noAutoPort,
|
|
108
|
+
fallback: false
|
|
109
|
+
}),
|
|
110
|
+
cdpPortSpan: Math.max(0, Math.floor((0, config_1.pickNumber)({
|
|
111
|
+
cliValue: opts.cdpPortSpan,
|
|
112
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'cdpPortSpan'),
|
|
113
|
+
envValue: env.ZENDESK_CDP_PORT_SPAN,
|
|
114
|
+
configValue: cfg.cdpPortSpan,
|
|
115
|
+
fallback: 10
|
|
116
|
+
}))),
|
|
117
|
+
json: (0, config_1.pickBool)({
|
|
118
|
+
cliValue: opts.json,
|
|
119
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'json'),
|
|
120
|
+
envValue: env.ZENDESK_JSON,
|
|
121
|
+
configValue: cfg.json,
|
|
122
|
+
fallback: false
|
|
123
|
+
}),
|
|
124
|
+
out: opts.out || '',
|
|
125
|
+
uiWaitMs,
|
|
126
|
+
background: !(0, config_1.pickBool)({
|
|
127
|
+
cliValue: opts.foreground,
|
|
128
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'foreground'),
|
|
129
|
+
envValue: env.ZENDESK_FOREGROUND,
|
|
130
|
+
configValue: cfg.foreground,
|
|
131
|
+
fallback: false
|
|
132
|
+
}),
|
|
133
|
+
storeRoot: (0, config_1.toAbsPath)(storeRootRaw, loadedConfig.repoRoot),
|
|
134
|
+
store: !(0, config_1.pickBool)({
|
|
135
|
+
cliValue: opts.store === false,
|
|
136
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'store'),
|
|
137
|
+
envValue: env.ZENDESK_NO_STORE,
|
|
138
|
+
configValue: cfg.noStore,
|
|
139
|
+
fallback: false
|
|
140
|
+
}),
|
|
141
|
+
cache: !(0, config_1.pickBool)({
|
|
142
|
+
cliValue: opts.cache === false,
|
|
143
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'cache'),
|
|
144
|
+
envValue: env.ZENDESK_NO_CACHE,
|
|
145
|
+
configValue: cfg.noCache,
|
|
146
|
+
fallback: false
|
|
147
|
+
}),
|
|
148
|
+
cacheOnly: (0, config_1.pickBool)({
|
|
149
|
+
cliValue: opts.cacheOnly,
|
|
150
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'cacheOnly'),
|
|
151
|
+
envValue: env.ZENDESK_CACHE_ONLY,
|
|
152
|
+
configValue: cfg.cacheOnly,
|
|
153
|
+
fallback: false
|
|
154
|
+
}),
|
|
155
|
+
cacheTtl: Math.max(0, Math.floor((0, config_1.pickNumber)({
|
|
156
|
+
cliValue: opts.cacheTtl,
|
|
157
|
+
cliSource: (0, config_1.safeOptionSource)(program, 'cacheTtl'),
|
|
158
|
+
envValue: env.ZENDESK_CACHE_TTL,
|
|
159
|
+
configValue: cfg.cacheTtl,
|
|
160
|
+
fallback: 120
|
|
161
|
+
}))),
|
|
162
|
+
defaultQueue,
|
|
163
|
+
queueAliases: queueCfg.queues,
|
|
164
|
+
configPath: loadedConfig.configPath,
|
|
165
|
+
repoRoot: loadedConfig.repoRoot,
|
|
166
|
+
configValidation
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function emitResult(program, result) {
|
|
170
|
+
const globalOpts = resolveGlobalOpts(program);
|
|
171
|
+
const persisted = result && result.cacheHit ? null : (0, storage_1.persistOutput)(result, globalOpts);
|
|
172
|
+
const output = persisted ? { ...result, persisted } : result;
|
|
173
|
+
if (globalOpts.out) {
|
|
174
|
+
const outPath = path_1.default.resolve(globalOpts.out);
|
|
175
|
+
fs_1.default.mkdirSync(path_1.default.dirname(outPath), { recursive: true });
|
|
176
|
+
fs_1.default.writeFileSync(outPath, `${JSON.stringify(output, null, 2)}\n`, 'utf8');
|
|
177
|
+
}
|
|
178
|
+
if (globalOpts.json) {
|
|
179
|
+
console.log(JSON.stringify(output, null, 2));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (output.command === 'read-ticket') {
|
|
183
|
+
console.log(`Ticket: ${output.ticketId || 'unknown'}`);
|
|
184
|
+
console.log(`Subject: ${output.subject || 'unknown'}`);
|
|
185
|
+
console.log(`Status: ${output.status || 'unknown'}`);
|
|
186
|
+
console.log(`Priority: ${output.priority || 'unknown'}`);
|
|
187
|
+
console.log(`Assignee: ${output.assignee || 'unknown'}`);
|
|
188
|
+
console.log(`Requester: ${output.requester || 'unknown'}`);
|
|
189
|
+
console.log(`URL: ${output.pageUrl}`);
|
|
190
|
+
if (output.cacheHit) {
|
|
191
|
+
console.log(`Cache: hit (${output.cacheAgeSeconds}s old)`);
|
|
192
|
+
}
|
|
193
|
+
if (output.persisted) {
|
|
194
|
+
console.log(`Stored: ${output.persisted.latestPath}`);
|
|
195
|
+
}
|
|
196
|
+
console.log('');
|
|
197
|
+
for (let i = 0; i < output.comments.length; i += 1) {
|
|
198
|
+
const row = output.comments[i];
|
|
199
|
+
console.log(`${i + 1}. [${row.author || 'Unknown'} @ ${row.time || 'time-unknown'}] ${row.text}`);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (output.command === 'read-queue') {
|
|
204
|
+
console.log(`Queue: ${output.queueName}`);
|
|
205
|
+
console.log(`URL: ${output.pageUrl}`);
|
|
206
|
+
console.log(`Tickets: ${output.resultCount}`);
|
|
207
|
+
if (output.persisted) {
|
|
208
|
+
console.log(`Stored: ${output.persisted.latestPath}`);
|
|
209
|
+
}
|
|
210
|
+
console.log('');
|
|
211
|
+
for (let i = 0; i < output.tickets.length; i += 1) {
|
|
212
|
+
const row = output.tickets[i];
|
|
213
|
+
console.log(`${i + 1}. #${row.ticketId || '?'} ${row.subject || '(no subject)'} [${row.status || 'unknown'}]`);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (output.command === 'search-tickets') {
|
|
218
|
+
console.log(`Query: ${output.query}`);
|
|
219
|
+
console.log(`URL: ${output.pageUrl}`);
|
|
220
|
+
console.log(`Hits: ${output.resultCount}`);
|
|
221
|
+
if (output.persisted) {
|
|
222
|
+
console.log(`Stored: ${output.persisted.latestPath}`);
|
|
223
|
+
}
|
|
224
|
+
console.log('');
|
|
225
|
+
for (let i = 0; i < output.results.length; i += 1) {
|
|
226
|
+
const row = output.results[i];
|
|
227
|
+
console.log(`${i + 1}. #${row.ticketId || '?'} ${row.title || '(no title)'}`);
|
|
228
|
+
if (row.snippet) {
|
|
229
|
+
console.log(` ${row.snippet}`);
|
|
230
|
+
}
|
|
231
|
+
if (row.url) {
|
|
232
|
+
console.log(` ${row.url}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (output.command === 'list-queues') {
|
|
238
|
+
console.log(`Domain: ${output.domain || 'unknown'}`);
|
|
239
|
+
console.log(`Default queue: ${output.defaultQueue || 'none'}`);
|
|
240
|
+
console.log(`Configured queues: ${output.count || 0}`);
|
|
241
|
+
console.log('');
|
|
242
|
+
for (let i = 0; i < output.queues.length; i += 1) {
|
|
243
|
+
const row = output.queues[i];
|
|
244
|
+
const marker = row.isDefault ? ' (default)' : '';
|
|
245
|
+
const team = row.team ? ` [${row.team}]` : '';
|
|
246
|
+
console.log(`${i + 1}. ${row.alias}${marker}${team}`);
|
|
247
|
+
console.log(` ${row.path || '(no path configured)'}`);
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (output.command === 'auth-check') {
|
|
252
|
+
console.log(`CDP: ${output.cdp.reachable ? 'reachable' : 'unreachable'}`);
|
|
253
|
+
console.log(`Config: ${output.config.ok ? 'valid' : 'invalid'}`);
|
|
254
|
+
console.log(`Auth: ${output.auth.authenticated ? 'authenticated' : 'not authenticated'}`);
|
|
255
|
+
if (output.auth.user) {
|
|
256
|
+
console.log(`User: ${output.auth.user.name || output.auth.user.email || output.auth.user.id}`);
|
|
257
|
+
}
|
|
258
|
+
if (output.persisted) {
|
|
259
|
+
console.log(`Stored: ${output.persisted.latestPath}`);
|
|
260
|
+
}
|
|
261
|
+
if (!output.config.ok && Array.isArray(output.config.issues) && output.config.issues.length) {
|
|
262
|
+
console.log('');
|
|
263
|
+
for (const issue of output.config.issues) {
|
|
264
|
+
console.log(`- ${issue}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (output.command === 'auth-login') {
|
|
270
|
+
console.log(output.authenticated ? 'Zendesk login confirmed.' : 'Zendesk login not confirmed.');
|
|
271
|
+
console.log(`URL: ${output.pageUrl || output.startUrl || 'unknown'}`);
|
|
272
|
+
if (output.user) {
|
|
273
|
+
console.log(`User: ${output.user.name || output.user.email || output.user.id}`);
|
|
274
|
+
}
|
|
275
|
+
if (output.persisted) {
|
|
276
|
+
console.log(`Stored: ${output.persisted.latestPath}`);
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (output.command === 'doctor') {
|
|
281
|
+
console.log(`Status: ${output.ok ? 'ok' : 'needs attention'}`);
|
|
282
|
+
for (const check of output.checks || []) {
|
|
283
|
+
console.log(`- ${check.name}: ${check.ok ? 'ok' : 'fail'}${check.detail ? ` (${check.detail})` : ''}`);
|
|
284
|
+
}
|
|
285
|
+
if (output.persisted) {
|
|
286
|
+
console.log(`Stored: ${output.persisted.latestPath}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function withZendeskBrowser(program, handler) {
|
|
291
|
+
const globalOpts = resolveGlobalOpts(program);
|
|
292
|
+
if (!globalOpts.domain) {
|
|
293
|
+
throw new Error('Zendesk domain is required. Set `domain` in zendesk config, `ZENDESK_DOMAIN`, or pass `--domain`.');
|
|
294
|
+
}
|
|
295
|
+
const cdp = await (0, browser_cdp_1.ensureCdp)({
|
|
296
|
+
cdpUrl: globalOpts.cdpUrl,
|
|
297
|
+
profileDir: globalOpts.profileDir,
|
|
298
|
+
noLaunch: globalOpts.noLaunch,
|
|
299
|
+
allowSharedCdp: globalOpts.allowSharedCdp,
|
|
300
|
+
autoPort: globalOpts.autoPort,
|
|
301
|
+
cdpPortSpan: globalOpts.cdpPortSpan
|
|
302
|
+
});
|
|
303
|
+
const browser = await playwright_1.chromium.connectOverCDP(cdp.wsEndpoint);
|
|
304
|
+
try {
|
|
305
|
+
const context = browser.contexts()[0];
|
|
306
|
+
if (!context) {
|
|
307
|
+
throw new Error('No browser context available from CDP connection.');
|
|
308
|
+
}
|
|
309
|
+
const page = await (0, automation_1.getZendeskPage)(context, globalOpts.startUrl, globalOpts.background);
|
|
310
|
+
await (0, browser_cdp_1.prepareInteractionContext)(page, globalOpts.uiWaitMs);
|
|
311
|
+
return await handler({ page, globalOpts, cdp });
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
await browser.close();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.persistOutput = persistOutput;
|
|
7
|
+
exports.readCachedTicket = readCachedTicket;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const util_1 = require("./util");
|
|
11
|
+
function nowParts(now = new Date()) {
|
|
12
|
+
const yyyy = String(now.getFullYear());
|
|
13
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
14
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
15
|
+
const hh = String(now.getHours()).padStart(2, '0');
|
|
16
|
+
const mi = String(now.getMinutes()).padStart(2, '0');
|
|
17
|
+
const ss = String(now.getSeconds()).padStart(2, '0');
|
|
18
|
+
return { yyyy, mm, dd, hh, mi, ss };
|
|
19
|
+
}
|
|
20
|
+
function persistOutput(result, globalOpts) {
|
|
21
|
+
if (!globalOpts.store || !result || !result.command) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const now = new Date();
|
|
25
|
+
const { yyyy, mm, dd, hh, mi, ss } = nowParts(now);
|
|
26
|
+
const root = globalOpts.storeRoot;
|
|
27
|
+
if (result.command === 'read-ticket') {
|
|
28
|
+
const ticketId = result.ticketId || (0, util_1.parseTicketIdFromUrl)(result.pageUrl || '') || 'unknown';
|
|
29
|
+
const ticketRoot = path_1.default.join(root, 'tickets', String(ticketId));
|
|
30
|
+
const snapshotPath = path_1.default.join(ticketRoot, 'snapshots', yyyy, mm, dd, `${hh}${mi}${ss}.json`);
|
|
31
|
+
const latestPath = path_1.default.join(ticketRoot, 'latest.json');
|
|
32
|
+
const record = {
|
|
33
|
+
capturedAt: now.toISOString(),
|
|
34
|
+
...result
|
|
35
|
+
};
|
|
36
|
+
(0, util_1.writeJson)(snapshotPath, record);
|
|
37
|
+
(0, util_1.writeJson)(latestPath, record);
|
|
38
|
+
return {
|
|
39
|
+
entity: 'ticket',
|
|
40
|
+
ticketId: String(ticketId),
|
|
41
|
+
ticketRoot,
|
|
42
|
+
latestPath,
|
|
43
|
+
snapshotPath
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (result.command === 'read-queue') {
|
|
47
|
+
const slug = (0, util_1.slugify)(result.queueName || 'queue');
|
|
48
|
+
const queueRoot = path_1.default.join(root, 'queues', slug);
|
|
49
|
+
const snapshotPath = path_1.default.join(queueRoot, 'snapshots', yyyy, mm, dd, `${hh}${mi}${ss}.json`);
|
|
50
|
+
const latestPath = path_1.default.join(queueRoot, 'latest.json');
|
|
51
|
+
const record = {
|
|
52
|
+
capturedAt: now.toISOString(),
|
|
53
|
+
...result
|
|
54
|
+
};
|
|
55
|
+
(0, util_1.writeJson)(snapshotPath, record);
|
|
56
|
+
(0, util_1.writeJson)(latestPath, record);
|
|
57
|
+
return {
|
|
58
|
+
entity: 'queue',
|
|
59
|
+
queue: slug,
|
|
60
|
+
queueRoot,
|
|
61
|
+
latestPath,
|
|
62
|
+
snapshotPath
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (result.command === 'search-tickets') {
|
|
66
|
+
const slug = (0, util_1.slugify)(result.query || 'query');
|
|
67
|
+
const searchRoot = path_1.default.join(root, 'searches', slug);
|
|
68
|
+
const snapshotPath = path_1.default.join(searchRoot, yyyy, mm, dd, `${hh}${mi}${ss}.json`);
|
|
69
|
+
const latestPath = path_1.default.join(searchRoot, 'latest.json');
|
|
70
|
+
const record = {
|
|
71
|
+
capturedAt: now.toISOString(),
|
|
72
|
+
...result
|
|
73
|
+
};
|
|
74
|
+
(0, util_1.writeJson)(snapshotPath, record);
|
|
75
|
+
(0, util_1.writeJson)(latestPath, record);
|
|
76
|
+
return {
|
|
77
|
+
entity: 'search',
|
|
78
|
+
query: slug,
|
|
79
|
+
searchRoot,
|
|
80
|
+
latestPath,
|
|
81
|
+
snapshotPath
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function readCachedTicket({ storeRoot, ticketId, ttlSeconds = 120 }) {
|
|
87
|
+
const id = String(ticketId || '').replace(/\D+/g, '');
|
|
88
|
+
if (!storeRoot || !id) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const latestPath = path_1.default.join(storeRoot, 'tickets', id, 'latest.json');
|
|
92
|
+
if (!fs_1.default.existsSync(latestPath)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const payload = (0, util_1.readJson)(latestPath, null);
|
|
96
|
+
if (!payload || payload.command !== 'read-ticket') {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const capturedAtMs = Date.parse(payload.capturedAt || '');
|
|
100
|
+
if (!Number.isFinite(capturedAtMs)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const ageSeconds = Math.max(0, Math.floor((Date.now() - capturedAtMs) / 1000));
|
|
104
|
+
const ttl = Math.max(0, Number(ttlSeconds) || 0);
|
|
105
|
+
if (ttl > 0 && ageSeconds > ttl) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...payload,
|
|
110
|
+
cacheHit: true,
|
|
111
|
+
cacheAgeSeconds: ageSeconds,
|
|
112
|
+
cachePath: latestPath
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sleep = sleep;
|
|
7
|
+
exports.clean = clean;
|
|
8
|
+
exports.slugify = slugify;
|
|
9
|
+
exports.readJson = readJson;
|
|
10
|
+
exports.writeJson = writeJson;
|
|
11
|
+
exports.parseTicketIdFromUrl = parseTicketIdFromUrl;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
function sleep(ms) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
+
}
|
|
17
|
+
function clean(text) {
|
|
18
|
+
return (text || '').replace(/\s+/g, ' ').trim();
|
|
19
|
+
}
|
|
20
|
+
function slugify(value) {
|
|
21
|
+
return clean(value)
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
24
|
+
.replace(/(^-|-$)/g, '') || 'unknown';
|
|
25
|
+
}
|
|
26
|
+
function readJson(filePath, fallback = {}) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch (_) {
|
|
32
|
+
return fallback;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function writeJson(filePath, data) {
|
|
36
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
37
|
+
fs_1.default.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
38
|
+
}
|
|
39
|
+
function parseTicketIdFromUrl(pageUrl = '') {
|
|
40
|
+
const m = String(pageUrl).match(/\/agent\/tickets\/(\d+)/i);
|
|
41
|
+
return m ? m[1] : null;
|
|
42
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zd-agent-cli",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "AI Agent ready Zendesk access through your existing browser session. No API keys required.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"zagent": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"zendesk.config.example.json"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/roger-rodriguez/zd-agent-cli.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/roger-rodriguez/zd-agent-cli#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/roger-rodriguez/zd-agent-cli/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"zendesk",
|
|
29
|
+
"cli",
|
|
30
|
+
"playwright",
|
|
31
|
+
"cdp"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"cli": "tsx src/cli.ts",
|
|
35
|
+
"dev": "tsx src/cli.ts",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
38
|
+
"prepack": "npm run build",
|
|
39
|
+
"lint": "eslint src test scripts",
|
|
40
|
+
"test": "node --test",
|
|
41
|
+
"smoke": "node scripts/smoke.cjs",
|
|
42
|
+
"verify": "npm run lint && npm run typecheck && npm run build && npm run test && npm run smoke"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"commander": "^14.0.3",
|
|
46
|
+
"get-port": "^7.1.0",
|
|
47
|
+
"playwright": "^1.58.2"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^25.3.2",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
52
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
53
|
+
"eslint": "^10.0.2",
|
|
54
|
+
"tsx": "^4.21.0",
|
|
55
|
+
"typescript": "^5.9.3"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"domain": "acme.zendesk.com",
|
|
3
|
+
"cdpUrl": "http://127.0.0.1:9223",
|
|
4
|
+
"cdpPortSpan": 10,
|
|
5
|
+
"allowSharedCdp": false,
|
|
6
|
+
"noAutoPort": false,
|
|
7
|
+
"profileDir": "./output/zendesk/chrome-profile",
|
|
8
|
+
"startPath": "/agent/filters/123456789",
|
|
9
|
+
"storeRoot": "./output/zendesk",
|
|
10
|
+
"cacheTtl": 120,
|
|
11
|
+
"cacheOnly": false,
|
|
12
|
+
"noCache": false,
|
|
13
|
+
"defaultQueue": "support",
|
|
14
|
+
"queues": {
|
|
15
|
+
"support": {
|
|
16
|
+
"path": "/agent/filters/123456789",
|
|
17
|
+
"team": "support"
|
|
18
|
+
},
|
|
19
|
+
"billing": {
|
|
20
|
+
"path": "/agent/filters/987654321",
|
|
21
|
+
"team": "billing"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"uiWaitMs": 1200,
|
|
25
|
+
"noLaunch": false,
|
|
26
|
+
"foreground": false,
|
|
27
|
+
"noStore": false
|
|
28
|
+
}
|