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
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
|
+
}
|