@zeyos/cli 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/bin/zeyos.mjs +139 -27
- package/commands/count.mjs +15 -7
- package/commands/create.mjs +12 -5
- package/commands/delete.mjs +8 -3
- package/commands/describe.mjs +22 -1
- package/commands/doctor.mjs +186 -0
- package/commands/get.mjs +17 -4
- package/commands/list.mjs +34 -11
- package/commands/login.mjs +33 -8
- package/commands/logout.mjs +12 -7
- package/commands/profile.mjs +211 -0
- package/commands/skills.mjs +3 -2
- package/commands/update.mjs +11 -4
- package/commands/whoami.mjs +9 -2
- package/config/actionstep.json +21 -0
- package/config/customfield.json +17 -0
- package/config/message.json +20 -0
- package/config/task.json +4 -2
- package/config/ticket.json +2 -1
- package/lib/client.mjs +19 -10
- package/lib/command.mjs +79 -4
- package/lib/config.mjs +280 -45
- package/lib/flags.mjs +3 -3
- package/lib/output.mjs +194 -4
- package/lib/resources.mjs +26 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ Inspect the CLI-supported resource registry:
|
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
zeyos resources
|
|
55
|
+
zeyos doctor agent --json
|
|
55
56
|
```
|
|
56
57
|
|
|
57
58
|
List tickets for automation:
|
|
@@ -65,11 +66,33 @@ zeyos list tickets \
|
|
|
65
66
|
--json
|
|
66
67
|
```
|
|
67
68
|
|
|
69
|
+
For larger or reusable filters, put the JSON in a file:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
zeyos list tickets --filter-file ./filters/open-tickets.json --json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Inspect dynamic schema definitions:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
zeyos count customfields --json
|
|
79
|
+
zeyos list customfields --fields ID,name,identifier,context,type --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Inspect actionsteps/time-entry evidence and ticket mail:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
zeyos list actionsteps --fields ID,name,status,date,duedate,effort,ticket,account --json
|
|
86
|
+
zeyos list messages --fields ID,date,mailbox,subject,sender_email,to_email,ticket,reference --filter '{"ticket":42}' --json
|
|
87
|
+
```
|
|
88
|
+
|
|
68
89
|
Create, update, and delete:
|
|
69
90
|
|
|
70
91
|
```bash
|
|
71
92
|
zeyos create ticket --data '{"name":"Fix login bug","status":0,"priority":3,"visibility":0}' --json
|
|
93
|
+
zeyos create ticket --data-file ./ticket.json --json
|
|
72
94
|
zeyos update ticket 42 --data '{"status":4}' --json
|
|
95
|
+
zeyos update ticket 42 --data-file ./ticket-update.json --json
|
|
73
96
|
zeyos delete ticket 42
|
|
74
97
|
```
|
|
75
98
|
|
|
@@ -82,6 +105,6 @@ zeyos delete ticket 42
|
|
|
82
105
|
|
|
83
106
|
## Coverage Boundary
|
|
84
107
|
|
|
85
|
-
The CLI intentionally covers a curated registry instead of the full API surface.
|
|
108
|
+
The CLI intentionally covers a curated registry instead of the full API surface. It includes operational resources such as tickets, tasks, messages, and actionsteps; use `zeyos resources` to see the supported set.
|
|
86
109
|
|
|
87
110
|
When you need unsupported resources or low-level request control, switch to [`@zeyos/client`](../docs/02-javascript-client/01-getting-started.md) and follow the escalation guidance in [CLI Coverage and Escalation](../docs/04-agent-workflows/03-cli-coverage-and-escalation.md).
|
package/bin/zeyos.mjs
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* update <resource> Update a record
|
|
17
17
|
* delete <resource> Delete a record
|
|
18
18
|
* resources List available resource types
|
|
19
|
+
* doctor agent Check local CLI readiness for coding agents
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
// ── Version ───────────────────────────────────────────────────────────────────
|
|
@@ -23,45 +24,54 @@
|
|
|
23
24
|
import { createRequire as _createRequire } from 'node:module';
|
|
24
25
|
import { dirname as _dirname } from 'node:path';
|
|
25
26
|
import { fileURLToPath as _fileURLToPath } from 'node:url';
|
|
27
|
+
import { colors as _c } from '../lib/output.mjs';
|
|
26
28
|
const _require = _createRequire(import.meta.url);
|
|
27
29
|
const _VERSION = _require('../package.json').version;
|
|
28
30
|
|
|
29
31
|
// ── Global help ───────────────────────────────────────────────────────────────
|
|
30
32
|
|
|
33
|
+
// Section headers are bold and the `zeyos` binary / command names are cyan,
|
|
34
|
+
// gated by USE_COLOR in output.mjs (so `zeyos --help | less` stays plain text).
|
|
35
|
+
const _z = _c.cyan('zeyos');
|
|
31
36
|
const HELP = `\
|
|
32
|
-
Usage:
|
|
33
|
-
|
|
34
|
-
Commands:
|
|
35
|
-
login Authenticate with a ZeyOS instance
|
|
36
|
-
logout Revoke session and clear stored tokens
|
|
37
|
-
whoami Show currently authenticated user
|
|
38
|
-
list <resource> List / query records
|
|
39
|
-
count <resource> Count records (with optional filter)
|
|
40
|
-
get <resource> <id> Fetch a single record by ID
|
|
41
|
-
show <resource> <id> Alias for get
|
|
42
|
-
create <resource> Create a new record
|
|
43
|
-
update <resource> <id> Update an existing record
|
|
44
|
-
delete <resource> <id> Delete a record
|
|
45
|
-
resources List all available resource types
|
|
46
|
-
describe <resource> Show a resource's fields, types and enums
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
Usage: ${_z} <command> [options] [args…]
|
|
38
|
+
|
|
39
|
+
${_c.bold('Commands:')}
|
|
40
|
+
${_c.cyan('login')} Authenticate with a ZeyOS instance
|
|
41
|
+
${_c.cyan('logout')} Revoke session and clear stored tokens
|
|
42
|
+
${_c.cyan('whoami')} Show currently authenticated user
|
|
43
|
+
${_c.cyan('list')} <resource> List / query records
|
|
44
|
+
${_c.cyan('count')} <resource> Count records (with optional filter)
|
|
45
|
+
${_c.cyan('get')} <resource> <id> Fetch a single record by ID
|
|
46
|
+
${_c.cyan('show')} <resource> <id> Alias for get
|
|
47
|
+
${_c.cyan('create')} <resource> Create a new record
|
|
48
|
+
${_c.cyan('update')} <resource> <id> Update an existing record
|
|
49
|
+
${_c.cyan('delete')} <resource> <id> Delete a record
|
|
50
|
+
${_c.cyan('resources')} List all available resource types
|
|
51
|
+
${_c.cyan('describe')} <resource> Show a resource's fields, types and enums
|
|
52
|
+
${_c.cyan('doctor')} agent Check local CLI readiness for coding agents
|
|
53
|
+
${_c.cyan('skills')} <command> List / show / install ZeyOS agent skills
|
|
54
|
+
${_c.cyan('profile')} <command> Manage credential profiles / switch instances
|
|
55
|
+
|
|
56
|
+
${_c.bold('Global options:')}
|
|
50
57
|
--json Output as JSON
|
|
51
58
|
--yaml Output as YAML
|
|
59
|
+
--query Print the API route + JSON payload without sending it
|
|
60
|
+
--profile <name> Use a named credential profile for this command
|
|
52
61
|
--no-color Disable ANSI colors
|
|
53
62
|
-h, --help Show help for a command
|
|
54
63
|
-v, --version Print the CLI version and exit
|
|
55
64
|
|
|
56
|
-
Examples:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
${_c.bold('Examples:')}
|
|
66
|
+
${_z} login --base-url https://cloud.zeyos.com/demo --client-id myapp --secret "$ZEYOS_CLIENT_SECRET"
|
|
67
|
+
${_z} list tickets --filter '{"status":1}' --sort -lastmodified
|
|
68
|
+
${_z} list tickets --filter-file ./filters/open-tickets.json
|
|
69
|
+
${_z} count tickets --filter '{"status":1}'
|
|
70
|
+
${_z} get ticket 42
|
|
71
|
+
${_z} get ticket 42 --all
|
|
72
|
+
${_z} create ticket --name "Fix login bug" --priority 3
|
|
73
|
+
${_z} update ticket 42 --status 2
|
|
74
|
+
${_z} delete ticket 42 --force
|
|
65
75
|
`;
|
|
66
76
|
|
|
67
77
|
// ── Argument definitions ──────────────────────────────────────────────────────
|
|
@@ -73,6 +83,8 @@ const OPTIONS = {
|
|
|
73
83
|
'json': { type: 'boolean' },
|
|
74
84
|
'yaml': { type: 'boolean' },
|
|
75
85
|
'no-color': { type: 'boolean' },
|
|
86
|
+
'query': { type: 'boolean' },
|
|
87
|
+
'profile': { type: 'string' },
|
|
76
88
|
// login
|
|
77
89
|
'base-url': { type: 'string' },
|
|
78
90
|
'client-id': { type: 'string' },
|
|
@@ -88,6 +100,7 @@ const OPTIONS = {
|
|
|
88
100
|
// list
|
|
89
101
|
'fields': { type: 'string' },
|
|
90
102
|
'filter': { type: 'string' },
|
|
103
|
+
'filter-file': { type: 'string' },
|
|
91
104
|
'sort': { type: 'string' },
|
|
92
105
|
'limit': { type: 'string' },
|
|
93
106
|
'offset': { type: 'string' },
|
|
@@ -100,12 +113,15 @@ const OPTIONS = {
|
|
|
100
113
|
'show-token': { type: 'boolean' },
|
|
101
114
|
// create / update
|
|
102
115
|
'data': { type: 'string' },
|
|
116
|
+
'data-file': { type: 'string' },
|
|
103
117
|
// delete
|
|
104
118
|
// (--force is already declared above)
|
|
105
119
|
// skills install
|
|
106
120
|
'target': { type: 'string' },
|
|
107
121
|
'dir': { type: 'string' },
|
|
108
122
|
'no-logo': { type: 'boolean' },
|
|
123
|
+
// profile
|
|
124
|
+
'from-current': { type: 'boolean' },
|
|
109
125
|
};
|
|
110
126
|
|
|
111
127
|
// ── Command registry ──────────────────────────────────────────────────────────
|
|
@@ -128,8 +144,46 @@ const COMMANDS = {
|
|
|
128
144
|
resources: '../commands/resources.mjs',
|
|
129
145
|
resource: '../commands/resources.mjs',
|
|
130
146
|
describe: '../commands/describe.mjs',
|
|
147
|
+
doctor: '../commands/doctor.mjs',
|
|
131
148
|
skills: '../commands/skills.mjs',
|
|
132
149
|
skill: '../commands/skills.mjs',
|
|
150
|
+
profile: '../commands/profile.mjs',
|
|
151
|
+
profiles: '../commands/profile.mjs',
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// ── Per-command flag allow-lists ────────────────────────────────────────────────
|
|
155
|
+
// Unknown flags are rejected (e.g. `zeyos list --invalid`) so typos surface
|
|
156
|
+
// immediately instead of being silently ignored. `create`/`update` are the
|
|
157
|
+
// exception: they accept arbitrary `--<field>` flags, marked with `null` below.
|
|
158
|
+
|
|
159
|
+
const ALWAYS_FLAGS = ['help', 'json', 'yaml', 'no-color', 'profile'];
|
|
160
|
+
const SKILLS_FLAGS = ['target', 'dir', 'global', 'local', 'force', 'yes', 'no-logo'];
|
|
161
|
+
const PROFILE_FLAGS = ['base-url', 'client-id', 'secret', 'local', 'from-current'];
|
|
162
|
+
const DELETE_FLAGS = ['force', 'query'];
|
|
163
|
+
const GET_FLAGS = ['fields', 'extdata', 'tags', 'expand', 'all', 'query'];
|
|
164
|
+
|
|
165
|
+
const COMMAND_FLAGS = {
|
|
166
|
+
login: ['base-url', 'client-id', 'secret', 'scope', 'port', 'global', 'force', 'clean', 'manual'],
|
|
167
|
+
logout: ['global'],
|
|
168
|
+
whoami: ['show-token'],
|
|
169
|
+
list: ['fields', 'filter', 'filter-file', 'sort', 'limit', 'offset', 'extdata', 'expand', 'query'],
|
|
170
|
+
count: ['filter', 'filter-file', 'query'],
|
|
171
|
+
get: GET_FLAGS,
|
|
172
|
+
show: GET_FLAGS,
|
|
173
|
+
create: null,
|
|
174
|
+
update: null,
|
|
175
|
+
edit: null,
|
|
176
|
+
delete: DELETE_FLAGS,
|
|
177
|
+
rm: DELETE_FLAGS,
|
|
178
|
+
remove: DELETE_FLAGS,
|
|
179
|
+
resources: [],
|
|
180
|
+
resource: [],
|
|
181
|
+
describe: [],
|
|
182
|
+
doctor: [],
|
|
183
|
+
skills: SKILLS_FLAGS,
|
|
184
|
+
skill: SKILLS_FLAGS,
|
|
185
|
+
profile: PROFILE_FLAGS,
|
|
186
|
+
profiles: PROFILE_FLAGS,
|
|
133
187
|
};
|
|
134
188
|
|
|
135
189
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
@@ -149,6 +203,14 @@ async function main() {
|
|
|
149
203
|
}
|
|
150
204
|
|
|
151
205
|
const command = argv[0];
|
|
206
|
+
|
|
207
|
+
// A leading flag (e.g. `zeyos --invalid`) is not a command — surface it as a
|
|
208
|
+
// bad option rather than letting it masquerade as one.
|
|
209
|
+
if (command.startsWith('-')) {
|
|
210
|
+
process.stderr.write(`Unknown option: "${command}". Run 'zeyos --help' for usage.\n`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
152
214
|
const rest = argv.slice(1);
|
|
153
215
|
|
|
154
216
|
// Parse remaining args permissively: known options are parsed normally and
|
|
@@ -168,6 +230,24 @@ async function main() {
|
|
|
168
230
|
process.exit(0);
|
|
169
231
|
}
|
|
170
232
|
|
|
233
|
+
// Reject unknown flags so typos / unsupported options fail loudly instead of
|
|
234
|
+
// being silently ignored. `create`/`update` opt out (COMMAND_FLAGS = null)
|
|
235
|
+
// because they accept arbitrary `--<field>` flags as record data.
|
|
236
|
+
const allowed = COMMAND_FLAGS[command];
|
|
237
|
+
if (allowed) {
|
|
238
|
+
const allowedSet = new Set([...ALWAYS_FLAGS, ...allowed]);
|
|
239
|
+
const unknown = Object.keys(values).filter((key) => !allowedSet.has(key));
|
|
240
|
+
if (unknown.length > 0) {
|
|
241
|
+
const flag = unknown[0];
|
|
242
|
+
const hint = _suggestFlag(flag, [...allowedSet]);
|
|
243
|
+
process.stderr.write(
|
|
244
|
+
`Unknown option: --${flag}${hint ? ` (did you mean --${hint}?)` : ''}\n\n` +
|
|
245
|
+
`Run 'zeyos ${command} --help' for available options.\n`
|
|
246
|
+
);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
171
251
|
await mod.run(values, positional);
|
|
172
252
|
}
|
|
173
253
|
|
|
@@ -274,6 +354,38 @@ function _parsePermissive(argv, options) {
|
|
|
274
354
|
return { values, positional };
|
|
275
355
|
}
|
|
276
356
|
|
|
357
|
+
/** Suggest the closest allowed flag for an unknown one, if it's a near miss. */
|
|
358
|
+
function _suggestFlag(input, candidates) {
|
|
359
|
+
let best = null;
|
|
360
|
+
let bestDist = Infinity;
|
|
361
|
+
for (const candidate of candidates) {
|
|
362
|
+
const dist = _levenshtein(input, candidate);
|
|
363
|
+
if (dist < bestDist) {
|
|
364
|
+
bestDist = dist;
|
|
365
|
+
best = candidate;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Only suggest a reasonably close match (avoid nonsense "did you mean").
|
|
369
|
+
return bestDist <= Math.max(2, Math.floor(input.length / 2)) ? best : null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Levenshtein edit distance between two short strings. */
|
|
373
|
+
function _levenshtein(a, b) {
|
|
374
|
+
const m = a.length;
|
|
375
|
+
const n = b.length;
|
|
376
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => i);
|
|
377
|
+
for (let j = 1; j <= n; j++) {
|
|
378
|
+
let prev = dp[0];
|
|
379
|
+
dp[0] = j;
|
|
380
|
+
for (let i = 1; i <= m; i++) {
|
|
381
|
+
const tmp = dp[i];
|
|
382
|
+
dp[i] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[i], dp[i - 1]);
|
|
383
|
+
prev = tmp;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return dp[m];
|
|
387
|
+
}
|
|
388
|
+
|
|
277
389
|
main().catch(err => {
|
|
278
390
|
process.stderr.write(`Fatal: ${err.message}\n`);
|
|
279
391
|
process.exit(1);
|
package/commands/count.mjs
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* Return the count of records matching an optional filter.
|
|
5
5
|
*
|
|
6
6
|
* Options:
|
|
7
|
-
* --filter <json>
|
|
8
|
-
* --
|
|
9
|
-
* --
|
|
7
|
+
* --filter <json> JSON filter object e.g. '{"status":1}'
|
|
8
|
+
* --filter-file <path> Read JSON filter object from a file
|
|
9
|
+
* --json Output as JSON
|
|
10
|
+
* --yaml Output as YAML
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { normalizeCountResult } from '@zeyos/client';
|
|
13
|
-
import { buildCliClient, callApi,
|
|
14
|
+
import { buildCliClient, callApi, maybeDryRun, parseJsonOptionOrFile, requireResource } from '../lib/command.mjs';
|
|
14
15
|
import { outputMode, printJson, printYaml } from '../lib/output.mjs';
|
|
15
16
|
|
|
16
17
|
export const USAGE = `\
|
|
@@ -23,29 +24,36 @@ Arguments:
|
|
|
23
24
|
|
|
24
25
|
Options:
|
|
25
26
|
--filter <json> JSON filter object e.g. '{"status":1}'
|
|
27
|
+
--filter-file <path>
|
|
28
|
+
Read JSON filter object from a file
|
|
26
29
|
--json Output as JSON ({ "count": N })
|
|
27
30
|
--yaml Output as YAML
|
|
31
|
+
--query Print the request route + JSON body without sending it
|
|
28
32
|
-h, --help Show this help
|
|
29
33
|
|
|
30
34
|
Examples:
|
|
31
35
|
zeyos count tickets
|
|
32
36
|
zeyos count tickets --filter '{"status":1}'
|
|
37
|
+
zeyos count tickets --filter-file ./filters/open-tickets.json
|
|
33
38
|
zeyos count accounts --json
|
|
34
39
|
`;
|
|
35
40
|
|
|
36
41
|
export async function run(values, positional) {
|
|
37
42
|
const resourceName = positional[0];
|
|
38
43
|
const res = requireResource(resourceName, 'zeyos count <resource>');
|
|
39
|
-
const clientState = buildCliClient();
|
|
40
44
|
|
|
41
45
|
// ── Build request body ─────────────────────────────────────────────────────
|
|
42
46
|
const body = { count: true };
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
const filters = parseJsonOptionOrFile(values, 'filter', 'filter-file');
|
|
49
|
+
if (filters !== undefined) {
|
|
50
|
+
body.filters = filters;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
// ── Call API ───────────────────────────────────────────────────────────────
|
|
54
|
+
const clientState = buildCliClient(values);
|
|
55
|
+
if (await maybeDryRun(clientState, res.list, body, values)) return;
|
|
56
|
+
|
|
49
57
|
const result = await callApi(clientState, res.list, body);
|
|
50
58
|
|
|
51
59
|
const count = normalizeCountResult(result);
|
package/commands/create.mjs
CHANGED
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Create a new record. Field values can be supplied either as:
|
|
5
5
|
* - a JSON blob via --data '{"name":"foo","status":1}'
|
|
6
|
+
* - a JSON file via --data-file ./ticket.json
|
|
6
7
|
* - individual --<field> <value> flags (converted automatically)
|
|
7
8
|
*
|
|
8
9
|
* Options:
|
|
9
|
-
* --data <json>
|
|
10
|
-
* --
|
|
11
|
-
* --
|
|
10
|
+
* --data <json> Full record as JSON object
|
|
11
|
+
* --data-file <path> Read full record JSON object from a file
|
|
12
|
+
* --json Output created record as JSON
|
|
13
|
+
* --yaml Output created record as YAML
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
import { buildCliClient, buildRecordPayload, callApi, requireResource } from '../lib/command.mjs';
|
|
16
|
+
import { buildCliClient, buildRecordPayload, callApi, maybeDryRun, requireResource } from '../lib/command.mjs';
|
|
15
17
|
import { outputMode, printJson, printYaml, printRecord, success } from '../lib/output.mjs';
|
|
16
18
|
|
|
17
19
|
export const USAGE = `\
|
|
@@ -24,14 +26,17 @@ Arguments:
|
|
|
24
26
|
|
|
25
27
|
Options:
|
|
26
28
|
--data <json> Record fields as a JSON object
|
|
29
|
+
--data-file <path> Read record fields as a JSON object from a file
|
|
27
30
|
--<field> <value> Set individual fields e.g. --name "My Ticket" --status 1
|
|
28
31
|
--json Output created record as JSON
|
|
29
32
|
--yaml Output created record as YAML
|
|
33
|
+
--query Print the request route + JSON body without sending it
|
|
30
34
|
-h, --help Show this help
|
|
31
35
|
|
|
32
36
|
Examples:
|
|
33
37
|
zeyos create ticket --name "Fix login bug" --status 0 --priority 2
|
|
34
38
|
zeyos create account --data '{"lastname":"Acme Corp","email":"info@acme.com"}'
|
|
39
|
+
zeyos create ticket --data-file ./ticket.json
|
|
35
40
|
`;
|
|
36
41
|
|
|
37
42
|
export async function run(values, positional) {
|
|
@@ -43,9 +48,11 @@ export async function run(values, positional) {
|
|
|
43
48
|
// (optional) JSON body some callers pass positionally instead of via --data.
|
|
44
49
|
const data = buildRecordPayload(values, positional[1]);
|
|
45
50
|
|
|
46
|
-
const clientState = buildCliClient();
|
|
51
|
+
const clientState = buildCliClient(values);
|
|
47
52
|
|
|
48
53
|
// ── Call API ───────────────────────────────────────────────────────────────
|
|
54
|
+
if (await maybeDryRun(clientState, res.create, data, values)) return;
|
|
55
|
+
|
|
49
56
|
const record = await callApi(clientState, res.create, data);
|
|
50
57
|
|
|
51
58
|
const mode = outputMode(values);
|
package/commands/delete.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { createInterface } from 'node:readline';
|
|
11
|
-
import { buildCliClient, callApi, requireRecordId, requireResource } from '../lib/command.mjs';
|
|
11
|
+
import { buildCliClient, callApi, maybeDryRun, requireRecordId, requireResource } from '../lib/command.mjs';
|
|
12
12
|
import { success, warn } from '../lib/output.mjs';
|
|
13
13
|
|
|
14
14
|
export const USAGE = `\
|
|
@@ -22,6 +22,7 @@ Arguments:
|
|
|
22
22
|
|
|
23
23
|
Options:
|
|
24
24
|
--force Skip confirmation prompt
|
|
25
|
+
--query Print the request route + JSON body without sending it
|
|
25
26
|
-h, --help Show this help
|
|
26
27
|
|
|
27
28
|
Examples:
|
|
@@ -36,6 +37,12 @@ export async function run(values, positional) {
|
|
|
36
37
|
const res = requireResource(resourceName, 'zeyos delete <resource> <id>', 'delete', 'deletion');
|
|
37
38
|
requireRecordId(id, 'zeyos delete <resource> <id>');
|
|
38
39
|
|
|
40
|
+
const clientState = buildCliClient(values);
|
|
41
|
+
|
|
42
|
+
// ── Dry run ────────────────────────────────────────────────────────────────
|
|
43
|
+
// Show the request without prompting or deleting anything.
|
|
44
|
+
if (await maybeDryRun(clientState, res.delete, { ID: id }, values)) return;
|
|
45
|
+
|
|
39
46
|
// ── Confirmation ───────────────────────────────────────────────────────────
|
|
40
47
|
if (!values.force) {
|
|
41
48
|
const confirmed = await _confirm(`Delete ${resourceName} #${id}? [y/N] `);
|
|
@@ -45,8 +52,6 @@ export async function run(values, positional) {
|
|
|
45
52
|
}
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
const clientState = buildCliClient();
|
|
49
|
-
|
|
50
55
|
// ── Call API ───────────────────────────────────────────────────────────────
|
|
51
56
|
await callApi(clientState, res.delete, { ID: id }, {
|
|
52
57
|
notFoundMessage: `${resourceName} #${id} not found.`
|
package/commands/describe.mjs
CHANGED
|
@@ -82,12 +82,19 @@ export function run(values, positional = []) {
|
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// Keep the join-critical flags (→ fk, indexed, enum) in the table, but keep
|
|
86
|
+
// the `enum:` note SHORT so the long value list never blows out the column.
|
|
87
|
+
// The full enum values are printed below the table (see `enumDetails`), so FK
|
|
88
|
+
// and index flags stay legible in-line and the enum codes remain discoverable.
|
|
89
|
+
const enumDetails = [];
|
|
85
90
|
const rows = Object.entries(def.fields).map(([name, field]) => {
|
|
86
91
|
const notes = [];
|
|
87
92
|
if (field.fk) notes.push(`→ ${field.fk}`);
|
|
88
93
|
if (field.indexed) notes.push('indexed');
|
|
89
94
|
if (field.enum) {
|
|
90
|
-
|
|
95
|
+
const count = Object.keys(field.enum).length;
|
|
96
|
+
notes.push(`enum (${count})`);
|
|
97
|
+
enumDetails.push({ name, values: field.enum });
|
|
91
98
|
}
|
|
92
99
|
return { field: name, type: field.type, notes: notes.join(' ') };
|
|
93
100
|
});
|
|
@@ -96,6 +103,20 @@ export function run(values, positional = []) {
|
|
|
96
103
|
|
|
97
104
|
process.stdout.write(`\n ${c.bold(def.name)} ${c.dim(`(${def.type}, ${rows.length} fields)`)}\n`);
|
|
98
105
|
printTable(rows, ['field', 'type', 'notes']);
|
|
106
|
+
|
|
107
|
+
// Full enum values, one field per block, below the table. Each `code = LABEL`
|
|
108
|
+
// pair is on its own line so even long enums (e.g. ticket status) stay readable.
|
|
109
|
+
if (enumDetails.length > 0) {
|
|
110
|
+
process.stdout.write(` ${c.bold('enums')}\n`);
|
|
111
|
+
for (const { name, values } of enumDetails) {
|
|
112
|
+
process.stdout.write(` ${c.cyan(name)}\n`);
|
|
113
|
+
for (const [code, label] of Object.entries(values)) {
|
|
114
|
+
process.stdout.write(` ${c.dim(code.padStart(2))} ${label}\n`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
process.stdout.write('\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
if (operations.length > 0) {
|
|
100
121
|
process.stdout.write(` ${c.bold('operations')} ${c.dim(operations.join(', '))}\n\n`);
|
|
101
122
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zeyos doctor agent
|
|
3
|
+
*
|
|
4
|
+
* Offline diagnostic for coding agents before they rely on the CLI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
9
|
+
import { loadConfigWithSource, localConfigPath, globalConfigPath } from '../lib/config.mjs';
|
|
10
|
+
import { loadResourceConfig } from '../lib/resource-config.mjs';
|
|
11
|
+
import { listResources, resolveResource } from '../lib/resources.mjs';
|
|
12
|
+
import { colors as c, error, outputMode, printJson, printYaml } from '../lib/output.mjs';
|
|
13
|
+
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const VERSION = require('../package.json').version;
|
|
16
|
+
|
|
17
|
+
const ENV_KEYS = {
|
|
18
|
+
ZEYOS_BASE_URL: 'baseUrl',
|
|
19
|
+
ZEYOS_INSTANCE: 'instance',
|
|
20
|
+
ZEYOS_CLIENT_ID: 'clientId',
|
|
21
|
+
ZEYOS_CLIENT_SECRET: 'clientSecret',
|
|
22
|
+
ZEYOS_TOKEN: 'accessToken',
|
|
23
|
+
ZEYOS_REFRESH_TOKEN: 'refreshToken',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const USAGE = `\
|
|
27
|
+
Usage: zeyos doctor agent [options]
|
|
28
|
+
|
|
29
|
+
Check local CLI readiness for coding agents. Runs offline and never prints
|
|
30
|
+
tokens or client secrets.
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--json Output as JSON
|
|
34
|
+
--yaml Output as YAML
|
|
35
|
+
-h, --help Show this help
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
zeyos doctor agent
|
|
39
|
+
zeyos doctor agent --json
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
export function run(values, positional = []) {
|
|
43
|
+
const subject = positional[0];
|
|
44
|
+
if (subject !== 'agent') {
|
|
45
|
+
error('Unknown doctor target. Usage: zeyos doctor agent');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const report = buildAgentReport();
|
|
50
|
+
const mode = outputMode(values);
|
|
51
|
+
|
|
52
|
+
if (mode === 'json') {
|
|
53
|
+
printJson(report);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (mode === 'yaml') {
|
|
57
|
+
printYaml(report);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
printAgentReport(report);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildAgentReport() {
|
|
65
|
+
const localPath = localConfigPath();
|
|
66
|
+
const globalPath = globalConfigPath();
|
|
67
|
+
const envVariables = Object.keys(ENV_KEYS).filter((key) => process.env[key]);
|
|
68
|
+
let loaded = { config: {}, source: null };
|
|
69
|
+
let configError = null;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
loaded = loadConfigWithSource();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
configError = err.message || String(err);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const config = loaded.config;
|
|
78
|
+
const effective = {
|
|
79
|
+
baseUrl: Boolean(config.baseUrl),
|
|
80
|
+
instance: Boolean(config.instance),
|
|
81
|
+
clientId: Boolean(config.clientId),
|
|
82
|
+
clientSecret: Boolean(config.clientSecret),
|
|
83
|
+
accessToken: Boolean(config.accessToken),
|
|
84
|
+
refreshToken: Boolean(config.refreshToken),
|
|
85
|
+
};
|
|
86
|
+
const ready = Boolean(effective.baseUrl && effective.clientId && effective.clientSecret && effective.accessToken);
|
|
87
|
+
const resources = inspectResources();
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
ok: ready && !configError && resources.ok,
|
|
91
|
+
cli: {
|
|
92
|
+
version: VERSION,
|
|
93
|
+
},
|
|
94
|
+
connection: {
|
|
95
|
+
baseUrl: config.baseUrl ?? null,
|
|
96
|
+
instance: config.instance ?? null,
|
|
97
|
+
},
|
|
98
|
+
auth: {
|
|
99
|
+
ready,
|
|
100
|
+
source: envVariables.length > 0 ? 'env' : loaded.source,
|
|
101
|
+
env: {
|
|
102
|
+
present: envVariables.length > 0,
|
|
103
|
+
variables: envVariables,
|
|
104
|
+
},
|
|
105
|
+
local: {
|
|
106
|
+
present: Boolean(localPath),
|
|
107
|
+
path: localPath,
|
|
108
|
+
},
|
|
109
|
+
global: {
|
|
110
|
+
present: existsSync(globalPath),
|
|
111
|
+
path: globalPath,
|
|
112
|
+
},
|
|
113
|
+
effective,
|
|
114
|
+
error: configError,
|
|
115
|
+
},
|
|
116
|
+
resources,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function inspectResources() {
|
|
121
|
+
const names = listResources();
|
|
122
|
+
const missing = [];
|
|
123
|
+
const configErrors = [];
|
|
124
|
+
|
|
125
|
+
for (const name of names) {
|
|
126
|
+
if (!resolveResource(name)) {
|
|
127
|
+
missing.push(name);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
loadResourceConfig(name);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
configErrors.push(err.message || String(err));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
ok: names.length > 0 && missing.length === 0 && configErrors.length === 0,
|
|
140
|
+
count: names.length,
|
|
141
|
+
shippedConfigCount: countShippedResourceConfigs(),
|
|
142
|
+
missing,
|
|
143
|
+
configErrors,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function countShippedResourceConfigs() {
|
|
148
|
+
try {
|
|
149
|
+
return readdirSync(new URL('../config/', import.meta.url))
|
|
150
|
+
.filter((name) => name.endsWith('.json'))
|
|
151
|
+
.length;
|
|
152
|
+
} catch {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function printAgentReport(report) {
|
|
158
|
+
process.stdout.write('\n');
|
|
159
|
+
process.stdout.write(` ${c.bold('ZeyOS CLI doctor: agent')}\n\n`);
|
|
160
|
+
process.stdout.write(` CLI version ${report.cli.version}\n`);
|
|
161
|
+
process.stdout.write(` Base URL ${report.connection.baseUrl ?? '(not set)'}\n`);
|
|
162
|
+
process.stdout.write(` Instance ${report.connection.instance ?? '(not set)'}\n`);
|
|
163
|
+
process.stdout.write(` Auth ready ${yesNo(report.auth.ready)}\n`);
|
|
164
|
+
process.stdout.write(` Auth source ${report.auth.source ?? '(none)'}\n`);
|
|
165
|
+
process.stdout.write(` Env config ${report.auth.env.present ? report.auth.env.variables.join(', ') : '(none)'}\n`);
|
|
166
|
+
process.stdout.write(` Local config ${report.auth.local.present ? report.auth.local.path : '(none)'}\n`);
|
|
167
|
+
process.stdout.write(` Global config ${report.auth.global.present ? report.auth.global.path : '(none)'}\n`);
|
|
168
|
+
process.stdout.write(` Resource registry ${report.resources.ok ? 'ok' : 'problem'} (${report.resources.count} resources, ${report.resources.shippedConfigCount} shipped configs)\n`);
|
|
169
|
+
|
|
170
|
+
if (report.auth.error) {
|
|
171
|
+
process.stdout.write(`\n ${c.bold('Auth config error')}\n`);
|
|
172
|
+
process.stdout.write(` ${report.auth.error}\n`);
|
|
173
|
+
}
|
|
174
|
+
if (report.resources.configErrors.length > 0) {
|
|
175
|
+
process.stdout.write(`\n ${c.bold('Resource config errors')}\n`);
|
|
176
|
+
for (const message of report.resources.configErrors) {
|
|
177
|
+
process.stdout.write(` ${message}\n`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
process.stdout.write('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function yesNo(value) {
|
|
185
|
+
return value ? 'yes' : 'no';
|
|
186
|
+
}
|