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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # zd-agent-cli
2
+
3
+ AI Agent ready Zendesk access through your existing browser session. No API keys required.
4
+
5
+ `zagent` connects to a locally authenticated Zendesk Agent Workspace session and outputs structured JSON for queue triage, ticket reads, and search.
6
+
7
+ ## Why this exists
8
+
9
+ Most Zendesk automations depend on API credentials that enterprise teams must issue, rotate, and govern.
10
+ `zd-agent-cli` is built for teams that want LLM/agent workflows without introducing long-lived Zendesk API keys.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g zd-agent-cli
16
+ ```
17
+
18
+ Or run from source:
19
+
20
+ ```bash
21
+ npm install
22
+ npm run cli -- --help
23
+ npm run dev -- --help
24
+ ```
25
+
26
+ ## Quickstart
27
+
28
+ 1. Ensure Chrome is running with CDP and logged into Zendesk.
29
+ 2. Add `zendesk.config.json` at your working repo root.
30
+ 3. Run first-launch checks:
31
+
32
+ ```bash
33
+ zagent --json doctor
34
+ zagent --json auth check
35
+ ```
36
+
37
+ 4. If not authenticated yet, run:
38
+
39
+ ```bash
40
+ zagent auth login --timeout 300
41
+ ```
42
+
43
+ 5. Discover queues:
44
+
45
+ ```bash
46
+ zagent --json queue list
47
+ ```
48
+
49
+ 6. Read queue/ticket data:
50
+
51
+ ```bash
52
+ zagent --json queue read support-open --count 20
53
+ zagent --json ticket read 123456 --comments 10
54
+ ```
55
+
56
+ ## Prerequisites
57
+
58
+ - macOS with Google Chrome installed.
59
+ - CDP endpoint reachable (default: `http://127.0.0.1:9223`).
60
+ - Default Chrome CDP profile dir is repo-local: `./output/zendesk/chrome-profile` (gitignored).
61
+ - Chrome profile used by CDP must be logged into Zendesk Agent Workspace.
62
+ - `zendesk.config.json` is present (or pass `--config <path>`).
63
+ - By default, `zagent` enforces CDP/profile ownership (it will not reuse a CDP endpoint launched with a different `--user-data-dir` unless you opt in).
64
+
65
+ ## Config Contract
66
+
67
+ Create `zendesk.config.json`:
68
+
69
+ ```json
70
+ {
71
+ "domain": "acme.zendesk.com",
72
+ "startPath": "/agent/filters/123456789",
73
+ "defaultQueue": "support-open",
74
+ "queues": {
75
+ "support-open": {
76
+ "path": "/agent/filters/123456789",
77
+ "team": "support"
78
+ },
79
+ "technical-support-open": {
80
+ "path": "/agent/filters/360000973008",
81
+ "team": "technical-support"
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ Required:
88
+
89
+ - `domain`
90
+ - `startPath` (must begin with `/agent/`)
91
+ - `defaultQueue` (must match a queue alias)
92
+ - `queues` object where each alias has required `path` (must begin with `/agent/`)
93
+
94
+ ## Core Commands
95
+
96
+ ```bash
97
+ zagent --json queue list
98
+ zagent --json queue read support-open --count 20
99
+ zagent --json ticket read 123456 --comments 10
100
+ zagent --json search tickets "checkout issue" --count 20
101
+ zagent --json auth check
102
+ zagent --json doctor
103
+
104
+ # ticket read cache controls
105
+ zagent --json ticket read 123456 --cache-ttl 120
106
+ zagent --json --cache-only ticket read 123456
107
+ ```
108
+
109
+ ## Security Model
110
+
111
+ ### What it does
112
+
113
+ - Uses the local logged-in Zendesk browser session.
114
+ - Operates with the same permissions as that human user.
115
+ - Produces structured JSON locally for downstream automations/LLMs.
116
+
117
+ ### What it does not do
118
+
119
+ - Does not provision or require Zendesk API keys by default.
120
+ - Does not create elevated access beyond the active user session.
121
+ - Does not bypass Zendesk auth controls (SSO/MFA/session expiry still apply).
122
+
123
+ ### Enterprise posture
124
+
125
+ - Works with existing identity controls and session policies.
126
+ - Reduces secret management overhead for pilot automations.
127
+ - Keeps execution local-first by default.
128
+
129
+ ### Data handling guidance
130
+
131
+ - Do not commit real `zendesk.config.json` files.
132
+ - Do not commit queue/ticket output snapshots with customer data.
133
+ - Redact sample outputs before sharing in issues/docs.
134
+
135
+ ## Troubleshooting
136
+
137
+ - Missing/invalid config:
138
+ - Run `zagent --json doctor` and fix `zendesk.config.json`.
139
+ - Invalid queue alias:
140
+ - Run `zagent --json queue list` and use a returned alias.
141
+ - CDP unavailable:
142
+ - Verify Chrome CDP is live on `http://127.0.0.1:9223`, then rerun `zagent --json doctor`.
143
+ - CDP in use by another profile:
144
+ - By default `zagent` will scan a local port range and either reuse a matching-profile CDP session or launch a new one on a free port.
145
+ - Use `--no-auto-port` to disable this behavior.
146
+ - Use `--allow-shared-cdp` to intentionally reuse a different profile on the same CDP endpoint.
147
+ - Auth/session issues:
148
+ - Run `zagent auth login --timeout 300` and complete login in the opened Chrome profile.
149
+ - Transient navigation/network failures:
150
+ - Retry once, then validate `domain`, `startPath`, and queue `path`.
151
+
152
+ ## Global Options
153
+
154
+ - `--config <path>`
155
+ - `--domain <host>`
156
+ - `--start-path <path>`
157
+ - `--cdp-url <url>`
158
+ - `--cdp-port-span <n>`
159
+ - `--profile-dir <path>`
160
+ - `--store-root <path>`
161
+ - `--cache-ttl <seconds>`
162
+ - `--json`
163
+ - `--out <path>`
164
+ - `--no-store`
165
+ - `--no-cache`
166
+ - `--cache-only`
167
+ - `--no-launch`
168
+ - `--allow-shared-cdp`
169
+ - `--no-auto-port`
170
+ - `--foreground`
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ npm run lint
176
+ npm run typecheck
177
+ npm test
178
+ npm run smoke
179
+ npm run build
180
+ npm run verify
181
+ ```
182
+
183
+ ## Release
184
+
185
+ - Versions follow semantic versioning.
186
+ - `v*` tags trigger npm publish workflow.
187
+ - Keep changelog entries for every release.
package/dist/cli.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const index_1 = __importDefault(require("./core/index"));
9
+ const config_1 = require("./core/config");
10
+ const ticket_read_1 = require("./cmds/ticket-read");
11
+ const queue_list_1 = require("./cmds/queue-list");
12
+ const queue_read_1 = require("./cmds/queue-read");
13
+ const search_tickets_1 = require("./cmds/search-tickets");
14
+ const auth_check_1 = require("./cmds/auth-check");
15
+ const auth_login_1 = require("./cmds/auth-login");
16
+ const doctor_1 = require("./cmds/doctor");
17
+ function resolveConfigPathFromArgv(argv) {
18
+ for (let i = 0; i < argv.length; i += 1) {
19
+ const arg = argv[i];
20
+ if (arg === '--config' && argv[i + 1]) {
21
+ return argv[i + 1];
22
+ }
23
+ if (arg.startsWith('--config=')) {
24
+ return arg.slice('--config='.length);
25
+ }
26
+ }
27
+ return '';
28
+ }
29
+ function buildQueueHelpText() {
30
+ try {
31
+ const configPath = resolveConfigPathFromArgv(process.argv.slice(2));
32
+ const loaded = (0, config_1.loadResolvedConfig)({
33
+ cwd: process.cwd(),
34
+ configPath
35
+ });
36
+ const queueCfg = loaded.queueConfig || { defaultQueue: '', queues: {} };
37
+ const aliases = Object.keys(queueCfg.queues || {}).sort();
38
+ if (!aliases.length) {
39
+ return '';
40
+ }
41
+ const defaultLine = queueCfg.defaultQueue ? `Default queue: ${queueCfg.defaultQueue}\n` : '';
42
+ return `\nConfigured queue aliases:\n${defaultLine}${aliases.map((alias) => `- ${alias}`).join('\n')}\n`;
43
+ }
44
+ catch (_) {
45
+ return '';
46
+ }
47
+ }
48
+ const program = new commander_1.Command();
49
+ program
50
+ .name('zagent')
51
+ .description('Zendesk automation CLI (CDP)')
52
+ .option('--config <path>', 'Path to zendesk config JSON (defaults to zendesk.config.json or zendesk.json)')
53
+ .option('--domain <host>', 'Zendesk host (example: acme.zendesk.com)')
54
+ .option('--cdp-url <url>', 'CDP endpoint URL')
55
+ .option('--profile-dir <path>', 'Chrome user-data-dir for auto-launch')
56
+ .option('--start-path <path>', 'Zendesk agent path to open if none active (example: /agent/filters/12345)')
57
+ .option('--ui-wait-ms <n>', 'Extra wait between UI actions')
58
+ .option('--no-launch', 'Do not auto-launch Chrome when CDP is unavailable')
59
+ .option('--allow-shared-cdp', 'Allow using an already-running CDP endpoint even if profile ownership cannot be verified')
60
+ .option('--no-auto-port', 'Disable automatic CDP port fallback when preferred port is unavailable or owned by another profile')
61
+ .option('--cdp-port-span <n>', 'Number of additional ports to scan for automatic CDP fallback')
62
+ .option('--foreground', 'Allow bring-to-front behavior (default is background mode)')
63
+ .option('--store-root <path>', 'Root folder for structured run state/output')
64
+ .option('--no-store', 'Disable structured state/output persistence')
65
+ .option('--no-cache', 'Disable local cache reads')
66
+ .option('--cache-only', 'Read only from local cache and do not fetch from Zendesk')
67
+ .option('--cache-ttl <seconds>', 'Cache freshness TTL in seconds for cache-enabled commands')
68
+ .option('--json', 'Print JSON output')
69
+ .option('--out <path>', 'Write JSON output to file');
70
+ const ticket = program.command('ticket').description('Ticket commands');
71
+ (0, ticket_read_1.registerTicketRead)(ticket, program, index_1.default);
72
+ const queue = program.command('queue').description('Queue/view commands');
73
+ (0, queue_list_1.registerQueueList)(queue, program, index_1.default);
74
+ (0, queue_read_1.registerQueueRead)(queue, program, index_1.default);
75
+ queue.addHelpText('after', buildQueueHelpText());
76
+ const search = program.command('search').description('Search commands');
77
+ (0, search_tickets_1.registerSearchTickets)(search, program, index_1.default);
78
+ const auth = program.command('auth').description('Auth/session commands');
79
+ (0, auth_check_1.registerAuthCheck)(auth, program, index_1.default);
80
+ (0, auth_login_1.registerAuthLogin)(auth, program, index_1.default);
81
+ (0, doctor_1.registerDoctor)(program, index_1.default);
82
+ program.parseAsync(process.argv).catch((error) => {
83
+ console.error(JSON.stringify({ ok: false, error: error.message }, null, 2));
84
+ process.exit(1);
85
+ });
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAuthCheck = registerAuthCheck;
4
+ function registerAuthCheck(auth, program, core) {
5
+ auth
6
+ .command('check')
7
+ .description('Check CDP reachability, config validity, and Zendesk auth status')
8
+ .action(async () => {
9
+ const globalOpts = core.resolveGlobalOpts(program);
10
+ const cdpReachable = await core.isCdpReachable(globalOpts.cdpUrl);
11
+ const config = {
12
+ ok: Boolean(globalOpts.configPath) && globalOpts.configValidation.ok,
13
+ issues: globalOpts.configValidation.issues || []
14
+ };
15
+ let authResult = {
16
+ checked: false,
17
+ authenticated: false,
18
+ user: null,
19
+ error: null
20
+ };
21
+ if (!globalOpts.domain) {
22
+ authResult.error = 'Missing domain. Set domain in config or pass --domain.';
23
+ }
24
+ else if (!cdpReachable) {
25
+ authResult.error = 'CDP endpoint is unreachable.';
26
+ }
27
+ else {
28
+ try {
29
+ authResult = await core.withZendeskBrowser(program, async ({ page }) => {
30
+ const user = await core.readCurrentUser(page);
31
+ return {
32
+ checked: true,
33
+ authenticated: Boolean(user && user.id),
34
+ user,
35
+ error: null
36
+ };
37
+ });
38
+ }
39
+ catch (error) {
40
+ authResult = {
41
+ checked: true,
42
+ authenticated: false,
43
+ user: null,
44
+ error: String(error && error.message ? error.message : error)
45
+ };
46
+ }
47
+ }
48
+ const result = {
49
+ ok: cdpReachable && config.ok && authResult.authenticated,
50
+ command: 'auth-check',
51
+ cdp: {
52
+ url: globalOpts.cdpUrl,
53
+ reachable: cdpReachable
54
+ },
55
+ config: {
56
+ path: globalOpts.configPath || null,
57
+ ...config
58
+ },
59
+ auth: authResult
60
+ };
61
+ core.emitResult(program, result);
62
+ });
63
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAuthLogin = registerAuthLogin;
4
+ const util_1 = require("../core/util");
5
+ function registerAuthLogin(auth, program, core) {
6
+ auth
7
+ .command('login')
8
+ .description('Open Zendesk and wait for successful authenticated session')
9
+ .option('--timeout <seconds>', 'Max seconds to wait for login confirmation', '300')
10
+ .action(async (options) => {
11
+ const globalOpts = core.resolveGlobalOpts(program);
12
+ const timeoutMs = Math.max(5, Number(options.timeout) || 300) * 1000;
13
+ if (!globalOpts.json) {
14
+ console.log(`Waiting up to ${Math.floor(timeoutMs / 1000)}s for Zendesk login at ${globalOpts.startUrl}`);
15
+ }
16
+ const result = await core.withZendeskBrowser(program, async ({ page, globalOpts: runOpts, cdp }) => {
17
+ await page.goto(runOpts.startUrl, { waitUntil: 'domcontentloaded', timeout: 45000 });
18
+ const deadline = Date.now() + timeoutMs;
19
+ let user = await core.readCurrentUser(page);
20
+ while (!(user && user.id) && Date.now() < deadline) {
21
+ await (0, util_1.sleep)(2000);
22
+ user = await core.readCurrentUser(page);
23
+ }
24
+ return {
25
+ ok: Boolean(user && user.id),
26
+ command: 'auth-login',
27
+ launchedChrome: cdp.launchedChrome,
28
+ cdpUrl: cdp.cdpUrl || runOpts.cdpUrl,
29
+ startUrl: runOpts.startUrl,
30
+ pageUrl: page.url(),
31
+ authenticated: Boolean(user && user.id),
32
+ user,
33
+ timeoutSeconds: Math.floor(timeoutMs / 1000)
34
+ };
35
+ });
36
+ core.emitResult(program, result);
37
+ });
38
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerDoctor = registerDoctor;
4
+ function registerDoctor(program, core) {
5
+ program
6
+ .command('doctor')
7
+ .description('Run environment diagnostics (config, CDP, and Zendesk auth)')
8
+ .action(async () => {
9
+ const globalOpts = core.resolveGlobalOpts(program);
10
+ const checks = [];
11
+ checks.push({
12
+ name: 'config-file',
13
+ ok: Boolean(globalOpts.configPath),
14
+ detail: globalOpts.configPath || 'No zendesk.config.json or zendesk.json found'
15
+ });
16
+ checks.push({
17
+ name: 'config-contract',
18
+ ok: globalOpts.configValidation.ok,
19
+ detail: globalOpts.configValidation.ok
20
+ ? 'valid'
21
+ : (globalOpts.configValidation.issues || []).join('; ')
22
+ });
23
+ checks.push({
24
+ name: 'profile-dir',
25
+ ok: Boolean(globalOpts.profileDir),
26
+ detail: globalOpts.profileDir || 'No profileDir resolved'
27
+ });
28
+ const cdpReachable = await core.isCdpReachable(globalOpts.cdpUrl);
29
+ checks.push({
30
+ name: 'cdp',
31
+ ok: cdpReachable,
32
+ detail: globalOpts.cdpUrl
33
+ });
34
+ if (cdpReachable && !globalOpts.allowSharedCdp) {
35
+ const ownership = core.checkCdpOwnership({
36
+ cdpUrl: globalOpts.cdpUrl,
37
+ expectedProfileDir: globalOpts.profileDir
38
+ });
39
+ checks.push({
40
+ name: 'cdp-profile-ownership',
41
+ ok: ownership.matches,
42
+ detail: ownership.matches
43
+ ? `pid=${ownership.pid || 'unknown'}`
44
+ : `expected=${ownership.expectedProfileDir || 'unknown'} actual=${ownership.actualProfileDir || 'unknown'}`
45
+ });
46
+ }
47
+ let user = null;
48
+ let authDetail = '';
49
+ if (cdpReachable && globalOpts.domain) {
50
+ try {
51
+ user = await core.withZendeskBrowser(program, async ({ page }) => core.readCurrentUser(page));
52
+ authDetail = user && user.id ? user.email || user.name || user.id : 'Not logged into Zendesk';
53
+ }
54
+ catch (error) {
55
+ authDetail = String(error && error.message ? error.message : error);
56
+ }
57
+ }
58
+ else if (!globalOpts.domain) {
59
+ authDetail = 'Missing domain';
60
+ }
61
+ else {
62
+ authDetail = 'Skipped because CDP is unreachable';
63
+ }
64
+ checks.push({
65
+ name: 'zendesk-auth',
66
+ ok: Boolean(user && user.id),
67
+ detail: authDetail
68
+ });
69
+ const result = {
70
+ ok: checks.every((check) => check.ok),
71
+ command: 'doctor',
72
+ checks
73
+ };
74
+ core.emitResult(program, result);
75
+ });
76
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerQueueList = registerQueueList;
4
+ function registerQueueList(queue, program, core) {
5
+ queue
6
+ .command('list')
7
+ .description('List configured queue aliases from zendesk config')
8
+ .option('--team <name>', 'Filter queues by team')
9
+ .action(async (options) => {
10
+ const globalOpts = core.resolveGlobalOpts(program);
11
+ const aliases = globalOpts.queueAliases || {};
12
+ const teamFilter = String(options.team || '').trim().toLowerCase();
13
+ const rows = Object.entries(aliases)
14
+ .map(([alias, meta]) => ({
15
+ alias,
16
+ path: meta.path || '',
17
+ team: meta.team || null,
18
+ isDefault: alias === globalOpts.defaultQueue
19
+ }))
20
+ .filter((row) => !teamFilter || String(row.team || '').toLowerCase() === teamFilter)
21
+ .sort((a, b) => a.alias.localeCompare(b.alias));
22
+ const result = {
23
+ ok: true,
24
+ command: 'list-queues',
25
+ domain: globalOpts.domain || null,
26
+ defaultQueue: globalOpts.defaultQueue || null,
27
+ count: rows.length,
28
+ queues: rows
29
+ };
30
+ core.emitResult(program, result);
31
+ });
32
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerQueueRead = registerQueueRead;
4
+ function registerQueueRead(queue, program, core) {
5
+ queue
6
+ .command('read [name]')
7
+ .description('Read a Zendesk team queue/view by name or alias (uses configured default queue when omitted)')
8
+ .option('--count <n>', 'Max number of tickets to return (omit for full queue sync)')
9
+ .action(async (name, options) => {
10
+ const globalOpts = core.resolveGlobalOpts(program);
11
+ const queueSelection = core.resolveQueueInput(name, {
12
+ defaultQueue: globalOpts.defaultQueue,
13
+ queues: globalOpts.queueAliases
14
+ });
15
+ const queueName = queueSelection.queueName;
16
+ const queuePath = queueSelection.queuePath;
17
+ if (!queueName && !queuePath) {
18
+ throw new Error('Queue name is required. Pass `queue read "<queue>"`, set `defaultQueue` in zendesk.config.json, or set ZENDESK_DEFAULT_QUEUE.');
19
+ }
20
+ const hasExplicitCount = options.count !== undefined && options.count !== null;
21
+ const count = hasExplicitCount ? Math.max(1, Number(options.count) || 20) : Number.MAX_SAFE_INTEGER;
22
+ const result = await core.withZendeskBrowser(program, async ({ page, globalOpts: runOpts, cdp }) => {
23
+ const matchedQueue = await core.openQueueByName(page, queueName, runOpts.uiWaitMs, {
24
+ queuePath
25
+ });
26
+ const queueData = await core.readQueueTickets(page, { count, fetchAll: !hasExplicitCount });
27
+ return {
28
+ ok: true,
29
+ command: 'read-queue',
30
+ launchedChrome: cdp.launchedChrome,
31
+ cdpUrl: cdp.cdpUrl || runOpts.cdpUrl,
32
+ requestedQueueName: queueName,
33
+ requestedQueueDisplayName: queueSelection.queueDisplayName || null,
34
+ requestedQueuePath: queuePath || null,
35
+ requestedQueueAlias: queueSelection.alias || null,
36
+ requestedQueueTeam: queueSelection.team || null,
37
+ matchedQueue,
38
+ ...queueData,
39
+ queueName: queueData.queueName || matchedQueue.name || queueName
40
+ };
41
+ });
42
+ core.emitResult(program, result);
43
+ });
44
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSearchTickets = registerSearchTickets;
4
+ function registerSearchTickets(search, program, core) {
5
+ search
6
+ .command('tickets <query>')
7
+ .description('Search Zendesk tickets by phrase')
8
+ .option('--count <n>', 'Max number of search hits to return', '20')
9
+ .action(async (query, options) => {
10
+ const count = Math.max(1, Number(options.count) || 20);
11
+ const result = await core.withZendeskBrowser(program, async ({ page, globalOpts, cdp }) => {
12
+ const searchData = await core.runTicketSearch(page, query, count, globalOpts.uiWaitMs);
13
+ return {
14
+ ok: true,
15
+ command: 'search-tickets',
16
+ launchedChrome: cdp.launchedChrome,
17
+ cdpUrl: cdp.cdpUrl || globalOpts.cdpUrl,
18
+ ...searchData
19
+ };
20
+ });
21
+ core.emitResult(program, result);
22
+ });
23
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTicketRead = registerTicketRead;
4
+ function registerTicketRead(ticket, program, core) {
5
+ ticket
6
+ .command('read <ticketId>')
7
+ .description('Read a Zendesk ticket by id')
8
+ .option('--comments <n>', 'Max number of comments to return', '10')
9
+ .option('--cache-ttl <seconds>', 'Override cache TTL for ticket reads')
10
+ .action(async (ticketId, options) => {
11
+ const globalOpts = core.resolveGlobalOpts(program);
12
+ const comments = Math.max(1, Number(options.comments) || 10);
13
+ const ticketIdText = String(ticketId || '').replace(/\D+/g, '');
14
+ const ttl = options.cacheTtl !== undefined
15
+ ? Math.max(0, Math.floor(Number(options.cacheTtl) || 0))
16
+ : globalOpts.cacheTtl;
17
+ if (globalOpts.cache || globalOpts.cacheOnly) {
18
+ const cached = core.readCachedTicket({
19
+ storeRoot: globalOpts.storeRoot,
20
+ ticketId: ticketIdText,
21
+ ttlSeconds: ttl
22
+ });
23
+ if (cached) {
24
+ core.emitResult(program, {
25
+ ...cached,
26
+ ok: true,
27
+ command: 'read-ticket',
28
+ requestedTicketId: ticketIdText,
29
+ launchedChrome: false,
30
+ cdpUrl: cached.cdpUrl || globalOpts.cdpUrl
31
+ });
32
+ return;
33
+ }
34
+ }
35
+ if (globalOpts.cacheOnly) {
36
+ throw new Error(`No cached ticket found for ${ticketIdText || ticketId} within ttl=${ttl}s.`);
37
+ }
38
+ const result = await core.withZendeskBrowser(program, async ({ page, globalOpts: runOpts, cdp }) => {
39
+ await core.openTicketById(page, ticketId, runOpts.uiWaitMs);
40
+ const data = await core.readCurrentTicket(page, { count: comments });
41
+ return {
42
+ ok: true,
43
+ command: 'read-ticket',
44
+ launchedChrome: cdp.launchedChrome,
45
+ cdpUrl: cdp.cdpUrl || runOpts.cdpUrl,
46
+ requestedTicketId: ticketIdText || String(ticketId),
47
+ ...data
48
+ };
49
+ });
50
+ core.emitResult(program, result);
51
+ });
52
+ }