@zeyos/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 +87 -0
- package/bin/zeyos.mjs +280 -0
- package/commands/count.mjs +63 -0
- package/commands/create.mjs +62 -0
- package/commands/delete.mjs +68 -0
- package/commands/describe.mjs +102 -0
- package/commands/get.mjs +127 -0
- package/commands/list.mjs +162 -0
- package/commands/login.mjs +223 -0
- package/commands/logout.mjs +63 -0
- package/commands/resources.mjs +49 -0
- package/commands/skills.mjs +363 -0
- package/commands/update.mjs +71 -0
- package/commands/whoami.mjs +100 -0
- package/config/account.json +18 -0
- package/config/item.json +16 -0
- package/config/project.json +16 -0
- package/config/task.json +18 -0
- package/config/ticket.json +19 -0
- package/lib/client.mjs +69 -0
- package/lib/command.mjs +148 -0
- package/lib/config.mjs +164 -0
- package/lib/flags.mjs +44 -0
- package/lib/login-server.mjs +149 -0
- package/lib/output.mjs +284 -0
- package/lib/resource-config.mjs +289 -0
- package/lib/resources.mjs +234 -0
- package/lib/types.mjs +46 -0
- package/package.json +47 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zeyos skills <list|show|install>
|
|
3
|
+
*
|
|
4
|
+
* Discover and install the ZeyOS agent skill packs bundled with @zeyos/client
|
|
5
|
+
* into a coding agent's skills directory, so the agent (Claude, Codex, opencode,
|
|
6
|
+
* Factory Droid, pi, …) can operate against ZeyOS with the right conventions
|
|
7
|
+
* out of the box.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readdirSync, readFileSync, existsSync, cpSync, mkdirSync } from 'node:fs';
|
|
11
|
+
import { createInterface } from 'node:readline';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import { outputMode, printJson, printYaml, printTable, colors, success, error, info, warn } from '../lib/output.mjs';
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
|
|
19
|
+
// ── Supported coding agents ────────────────────────────────────────────────────
|
|
20
|
+
// Each agent advertises where it looks for SKILL.md folders. `local` installs
|
|
21
|
+
// into the current project; `global` installs into the user's home so every
|
|
22
|
+
// project can see the skills. `detect` is the marker directory used to
|
|
23
|
+
// auto-pick an agent when none is specified and we can't prompt.
|
|
24
|
+
const AGENTS = [
|
|
25
|
+
{ key: 'claude', label: 'Claude Code', local: '.claude/skills', global: '~/.claude/skills', detect: '.claude' },
|
|
26
|
+
{ key: 'codex', label: 'OpenAI Codex', local: '.codex/skills', global: '~/.codex/skills', detect: '.codex' },
|
|
27
|
+
{ key: 'opencode', label: 'opencode', local: '.opencode/skills', global: '~/.config/opencode/skills', detect: '.opencode' },
|
|
28
|
+
{ key: 'droid', label: 'Factory Droid', local: '.factory/skills', global: '~/.factory/skills', detect: '.factory' },
|
|
29
|
+
{ key: 'pi', label: 'pi', local: '.pi/skills', global: '~/.pi/agent/skills', detect: '.pi' },
|
|
30
|
+
{ key: 'agents', label: 'Generic (AGENTS.md / .agents)', local: '.agents/skills', global: '~/.agents/skills', detect: '.agents' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const AGENT_KEYS = AGENTS.map((a) => a.key);
|
|
34
|
+
|
|
35
|
+
export const USAGE = `\
|
|
36
|
+
Usage: zeyos skills <command> [skill…]
|
|
37
|
+
|
|
38
|
+
Commands:
|
|
39
|
+
list List the bundled ZeyOS agent skills
|
|
40
|
+
show <skill> Print a skill's instructions (SKILL.md)
|
|
41
|
+
install [skill…] Copy skills into a coding agent (all if none given)
|
|
42
|
+
|
|
43
|
+
Install options:
|
|
44
|
+
--target <agent> Coding agent: ${AGENT_KEYS.join(', ')}
|
|
45
|
+
(prompted when omitted; falls back to auto-detect)
|
|
46
|
+
--global Install for all projects (agent's home directory)
|
|
47
|
+
--local Install into the current project (default)
|
|
48
|
+
--dir <path> Install into an explicit directory (overrides --target)
|
|
49
|
+
--force Overwrite existing skill folders
|
|
50
|
+
-y, --yes Skip prompts and use flags / sensible defaults
|
|
51
|
+
--no-logo Don't print the ⚡️ ZeyOS title
|
|
52
|
+
|
|
53
|
+
Global options:
|
|
54
|
+
--json Output as JSON (also silences the title)
|
|
55
|
+
--yaml Output as YAML
|
|
56
|
+
-h, --help Show this help
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
zeyos skills list
|
|
60
|
+
zeyos skills install # interactive: pick agent + scope
|
|
61
|
+
zeyos skills install --target claude --global # all projects, no prompts
|
|
62
|
+
zeyos skills install --target opencode --local # this project only
|
|
63
|
+
zeyos skills install zeyos-billing-insights -y # one skill, defaults, no prompts
|
|
64
|
+
zeyos skills install --dir ./vendor/skills # any directory you like
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
// ── Title ────────────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function _bannerColorEnabled() {
|
|
70
|
+
return process.stderr.isTTY && !process.argv.includes('--no-color') && !process.env.NO_COLOR;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Print the ZeyOS title to stderr, unless output is machine-readable or muted. */
|
|
74
|
+
function printLogo(values) {
|
|
75
|
+
if (values['no-logo'] || values.json || values.yaml) return;
|
|
76
|
+
if (!process.stderr.isTTY) return; // keep piped / CI output clean
|
|
77
|
+
// ZeyOS brand amber (#F7BC60), bold, when the terminal supports color.
|
|
78
|
+
const amber = (s) => (_bannerColorEnabled() ? `\x1b[1;38;2;247;188;96m${s}\x1b[0m` : s);
|
|
79
|
+
process.stderr.write(`\n⚡️ ${amber('ZeyOS')}\n\n`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Skill discovery ────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
// Locate the agents/ directory shipped inside the @zeyos/client package.
|
|
85
|
+
function findAgentsDir() {
|
|
86
|
+
let entry;
|
|
87
|
+
try {
|
|
88
|
+
entry = require.resolve('@zeyos/client');
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
let dir = path.dirname(entry);
|
|
93
|
+
for (let i = 0; i < 5; i++) {
|
|
94
|
+
const candidate = path.join(dir, 'agents');
|
|
95
|
+
if (existsSync(path.join(candidate, 'README.md')) || existsSync(path.join(candidate, 'shared'))) {
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
const parent = path.dirname(dir);
|
|
99
|
+
if (parent === dir) break;
|
|
100
|
+
dir = parent;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseFrontmatter(content) {
|
|
106
|
+
const out = {};
|
|
107
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
108
|
+
if (match) {
|
|
109
|
+
for (const line of match[1].split('\n')) {
|
|
110
|
+
const kv = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
|
|
111
|
+
if (kv) out[kv[1]] = kv[2].trim();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function listSkills(agentsDir) {
|
|
118
|
+
const skills = [];
|
|
119
|
+
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
120
|
+
if (!entry.isDirectory() || entry.name === 'shared') continue;
|
|
121
|
+
const skillFile = path.join(agentsDir, entry.name, 'SKILL.md');
|
|
122
|
+
if (!existsSync(skillFile)) continue;
|
|
123
|
+
const fm = parseFrontmatter(readFileSync(skillFile, 'utf8'));
|
|
124
|
+
skills.push({
|
|
125
|
+
name: fm.name || entry.name,
|
|
126
|
+
dirName: entry.name,
|
|
127
|
+
description: fm.description || '',
|
|
128
|
+
dir: path.join(agentsDir, entry.name)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Target resolution ──────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
function expandHome(p) {
|
|
137
|
+
if (p === '~') return homedir();
|
|
138
|
+
if (p.startsWith('~/') || p.startsWith('~\\')) return path.join(homedir(), p.slice(2));
|
|
139
|
+
return p;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Render an install path relative to CWD when possible, else with ~ for home. */
|
|
143
|
+
function displayPath(abs) {
|
|
144
|
+
const rel = path.relative(process.cwd(), abs);
|
|
145
|
+
if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) return rel;
|
|
146
|
+
const home = homedir();
|
|
147
|
+
if (abs === home || abs.startsWith(home + path.sep)) return '~' + abs.slice(home.length);
|
|
148
|
+
return abs;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** First agent whose marker directory already exists in the project. */
|
|
152
|
+
function detectAgent() {
|
|
153
|
+
return AGENTS.find((a) => existsSync(path.join(process.cwd(), a.detect))) || null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function canPrompt(values) {
|
|
157
|
+
return process.stdin.isTTY && process.stderr.isTTY && !values.yes;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Resolve where to install, prompting interactively when nothing pins it down. */
|
|
161
|
+
async function resolveTarget(values) {
|
|
162
|
+
// An explicit directory overrides everything else.
|
|
163
|
+
if (values.dir) {
|
|
164
|
+
return { key: 'custom', label: 'custom path', scope: 'custom', root: path.resolve(expandHome(values.dir)) };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const interactive = canPrompt(values);
|
|
168
|
+
|
|
169
|
+
// (a) Which agent?
|
|
170
|
+
let agent;
|
|
171
|
+
if (values.target) {
|
|
172
|
+
agent = AGENTS.find((a) => a.key === values.target);
|
|
173
|
+
if (!agent) {
|
|
174
|
+
error(`Unknown --target "${values.target}". Use one of: ${AGENT_KEYS.join(', ')}.`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
} else if (interactive) {
|
|
178
|
+
agent = await promptAgent();
|
|
179
|
+
} else {
|
|
180
|
+
agent = detectAgent() || AGENTS[0];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// (b) Install globally or just for this project?
|
|
184
|
+
let scope;
|
|
185
|
+
if (values.global && values.local) {
|
|
186
|
+
error('Pass only one of --global or --local.');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
} else if (values.global) {
|
|
189
|
+
scope = 'global';
|
|
190
|
+
} else if (values.local) {
|
|
191
|
+
scope = 'local';
|
|
192
|
+
} else if (interactive) {
|
|
193
|
+
scope = await promptScope(agent);
|
|
194
|
+
} else {
|
|
195
|
+
scope = 'local';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const rel = scope === 'global' ? agent.global : agent.local;
|
|
199
|
+
const root = scope === 'global' ? expandHome(rel) : path.resolve(process.cwd(), rel);
|
|
200
|
+
return { key: agent.key, label: agent.label, scope, root };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Interactive prompts ────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
/** Numbered menu on stderr. Resolves to the chosen item's index. */
|
|
206
|
+
function promptMenu(question, items, defaultIndex = 0) {
|
|
207
|
+
return new Promise((resolve) => {
|
|
208
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
209
|
+
process.stderr.write('\n' + colors.bold(question) + '\n');
|
|
210
|
+
items.forEach((it, i) => {
|
|
211
|
+
const num = colors.cyan(String(i + 1).padStart(2));
|
|
212
|
+
const dflt = i === defaultIndex ? colors.dim(' (default)') : '';
|
|
213
|
+
const hint = it.hint ? ' ' + colors.dim(it.hint) : '';
|
|
214
|
+
process.stderr.write(` ${num}. ${it.label}${dflt}${hint}\n`);
|
|
215
|
+
});
|
|
216
|
+
rl.question(colors.dim(`Select [1-${items.length}, Enter=${defaultIndex + 1}]: `), (answer) => {
|
|
217
|
+
rl.close();
|
|
218
|
+
const trimmed = answer.trim();
|
|
219
|
+
if (trimmed === '') return resolve(defaultIndex);
|
|
220
|
+
const n = Number(trimmed);
|
|
221
|
+
resolve(Number.isInteger(n) && n >= 1 && n <= items.length ? n - 1 : defaultIndex);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function promptAgent() {
|
|
227
|
+
const detected = detectAgent();
|
|
228
|
+
const defaultIndex = detected ? AGENTS.indexOf(detected) : 0;
|
|
229
|
+
const items = AGENTS.map((a) => ({
|
|
230
|
+
label: a.label,
|
|
231
|
+
hint: a === detected ? `${a.local} (detected here)` : a.local,
|
|
232
|
+
}));
|
|
233
|
+
const idx = await promptMenu('Which coding agent should these skills target?', items, defaultIndex);
|
|
234
|
+
return AGENTS[idx];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function promptScope(agent) {
|
|
238
|
+
const items = [
|
|
239
|
+
{ label: 'This project only', hint: path.join(process.cwd().split(path.sep).pop() || '.', agent.local) },
|
|
240
|
+
{ label: 'All my projects (global)', hint: agent.global },
|
|
241
|
+
];
|
|
242
|
+
const idx = await promptMenu('Install for this project or globally?', items, 0);
|
|
243
|
+
return idx === 0 ? 'local' : 'global';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── Commands ────────────────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
function runList(skills, values) {
|
|
249
|
+
const mode = outputMode(values);
|
|
250
|
+
if (mode === 'json') {
|
|
251
|
+
printJson(skills.map(({ name, description }) => ({ name, description })));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (mode === 'yaml') {
|
|
255
|
+
printYaml(skills.map(({ name, description }) => ({ name, description })));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const rows = skills.map((s) => ({
|
|
259
|
+
skill: s.name,
|
|
260
|
+
description: s.description.length > 88 ? s.description.slice(0, 87) + '…' : s.description
|
|
261
|
+
}));
|
|
262
|
+
printTable(rows, ['skill', 'description']);
|
|
263
|
+
info(`Install with: zeyos skills install <skill> (or "install" for all)`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function runShow(skills, name) {
|
|
267
|
+
const skill = skills.find((s) => s.name === name || s.dirName === name);
|
|
268
|
+
if (!skill) {
|
|
269
|
+
error(`Unknown skill "${name}". Run "zeyos skills list".`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
process.stdout.write(readFileSync(path.join(skill.dir, 'SKILL.md'), 'utf8'));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function runInstall(agentsDir, skills, names, values) {
|
|
276
|
+
const mode = outputMode(values);
|
|
277
|
+
if (mode === 'table') printLogo(values);
|
|
278
|
+
|
|
279
|
+
const target = await resolveTarget(values);
|
|
280
|
+
const selected = names.length > 0
|
|
281
|
+
? names.map((n) => skills.find((s) => s.name === n || s.dirName === n) || { missing: n })
|
|
282
|
+
: skills;
|
|
283
|
+
|
|
284
|
+
const missing = selected.filter((s) => s.missing).map((s) => s.missing);
|
|
285
|
+
if (missing.length > 0) {
|
|
286
|
+
error(`Unknown skill(s): ${missing.join(', ')}. Run "zeyos skills list".`);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
mkdirSync(target.root, { recursive: true });
|
|
291
|
+
|
|
292
|
+
const installed = [];
|
|
293
|
+
const skipped = [];
|
|
294
|
+
for (const skill of selected) {
|
|
295
|
+
const dest = path.join(target.root, skill.dirName);
|
|
296
|
+
if (existsSync(dest) && !values.force) {
|
|
297
|
+
skipped.push(skill.name);
|
|
298
|
+
if (mode === 'table') warn(`Skipped ${skill.name} (already exists — use --force to overwrite)`);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
cpSync(skill.dir, dest, { recursive: true });
|
|
302
|
+
installed.push(skill.name);
|
|
303
|
+
if (mode === 'table') success(`Installed ${skill.name} → ${displayPath(dest)}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Skills reference ../shared/* — install it alongside so those links resolve.
|
|
307
|
+
let sharedInstalled = false;
|
|
308
|
+
const sharedSrc = path.join(agentsDir, 'shared');
|
|
309
|
+
if (installed.length > 0 && existsSync(sharedSrc)) {
|
|
310
|
+
const sharedDest = path.join(target.root, 'shared');
|
|
311
|
+
cpSync(sharedSrc, sharedDest, { recursive: true });
|
|
312
|
+
sharedInstalled = true;
|
|
313
|
+
if (mode === 'table') info(`Installed shared references → ${displayPath(sharedDest)}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const summary = {
|
|
317
|
+
target: { agent: target.key, label: target.label, scope: target.scope, path: target.root },
|
|
318
|
+
installed,
|
|
319
|
+
skipped,
|
|
320
|
+
shared: sharedInstalled,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
if (mode === 'json') { printJson(summary); return; }
|
|
324
|
+
if (mode === 'yaml') { printYaml(summary); return; }
|
|
325
|
+
|
|
326
|
+
if (installed.length > 0) {
|
|
327
|
+
const scopeLabel = target.scope === 'global' ? 'global' : target.scope === 'custom' ? 'custom' : 'this project';
|
|
328
|
+
info(`Target: ${target.label} (${scopeLabel}). Point your agent at ${displayPath(target.root)}/`);
|
|
329
|
+
} else if (skipped.length > 0) {
|
|
330
|
+
info('Nothing installed — all selected skills already exist. Use --force to overwrite.');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export async function run(values, positional = []) {
|
|
335
|
+
const agentsDir = findAgentsDir();
|
|
336
|
+
if (!agentsDir) {
|
|
337
|
+
error('Could not locate the bundled ZeyOS skills (the @zeyos/client agents/ directory).');
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const skills = listSkills(agentsDir);
|
|
342
|
+
const sub = positional[0] || 'list';
|
|
343
|
+
const rest = positional.slice(1);
|
|
344
|
+
|
|
345
|
+
switch (sub) {
|
|
346
|
+
case 'list':
|
|
347
|
+
runList(skills, values);
|
|
348
|
+
return;
|
|
349
|
+
case 'show':
|
|
350
|
+
if (!rest[0]) {
|
|
351
|
+
error('Usage: zeyos skills show <skill>');
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
runShow(skills, rest[0]);
|
|
355
|
+
return;
|
|
356
|
+
case 'install':
|
|
357
|
+
await runInstall(agentsDir, skills, rest, values);
|
|
358
|
+
return;
|
|
359
|
+
default:
|
|
360
|
+
error(`Unknown skills command "${sub}".\n\n${USAGE}`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zeyos update <resource> <id> [--data <json>] [--field value …]
|
|
3
|
+
*
|
|
4
|
+
* Update an existing record. Works like `create` but requires an ID.
|
|
5
|
+
*
|
|
6
|
+
* Options:
|
|
7
|
+
* --data <json> Fields to update as a JSON object
|
|
8
|
+
* --json Output updated record as JSON
|
|
9
|
+
* --yaml Output updated record as YAML
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
buildCliClient,
|
|
14
|
+
buildRecordPayload,
|
|
15
|
+
callApi,
|
|
16
|
+
requireRecordId,
|
|
17
|
+
requireResource
|
|
18
|
+
} from '../lib/command.mjs';
|
|
19
|
+
import { outputMode, printJson, printYaml, printRecord, success } from '../lib/output.mjs';
|
|
20
|
+
|
|
21
|
+
export const USAGE = `\
|
|
22
|
+
Usage: zeyos update <resource> <id> [options]
|
|
23
|
+
|
|
24
|
+
Update an existing record.
|
|
25
|
+
|
|
26
|
+
Arguments:
|
|
27
|
+
resource Resource name (e.g. ticket, account)
|
|
28
|
+
id Record ID
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--data <json> Fields to update as a JSON object
|
|
32
|
+
--<field> <value> Set individual fields e.g. --status 2
|
|
33
|
+
--json Output updated record as JSON
|
|
34
|
+
--yaml Output updated record as YAML
|
|
35
|
+
-h, --help Show this help
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
zeyos update ticket 42 --status 3
|
|
39
|
+
zeyos update account 7 --data '{"email":"new@example.com"}'
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
export async function run(values, positional) {
|
|
43
|
+
const resourceName = positional[0];
|
|
44
|
+
const id = positional[1];
|
|
45
|
+
|
|
46
|
+
const res = requireResource(resourceName, 'zeyos update <resource> <id>', 'update', 'updates');
|
|
47
|
+
requireRecordId(id, 'zeyos update <resource> <id>');
|
|
48
|
+
|
|
49
|
+
// ── Build data payload ─────────────────────────────────────────────────────
|
|
50
|
+
// Validate input before requiring credentials. positional[2] is the
|
|
51
|
+
// (optional) JSON body some callers pass positionally instead of via --data.
|
|
52
|
+
const data = buildRecordPayload(values, positional[2]);
|
|
53
|
+
|
|
54
|
+
const clientState = buildCliClient();
|
|
55
|
+
|
|
56
|
+
// ── Call API ───────────────────────────────────────────────────────────────
|
|
57
|
+
const record = await callApi(clientState, res.update, { ID: id, body: data }, {
|
|
58
|
+
notFoundMessage: `${resourceName} #${id} not found.`
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const mode = outputMode(values);
|
|
62
|
+
|
|
63
|
+
if (mode === 'json') {
|
|
64
|
+
printJson(record ?? { ID: id, ...data });
|
|
65
|
+
} else if (mode === 'yaml') {
|
|
66
|
+
printYaml(record ?? { ID: id, ...data });
|
|
67
|
+
} else {
|
|
68
|
+
success(`Updated ${resourceName} #${id}.`);
|
|
69
|
+
if (record) printRecord(record, res.fields);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zeyos whoami
|
|
3
|
+
*
|
|
4
|
+
* Displays information about the currently authenticated user.
|
|
5
|
+
*
|
|
6
|
+
* Options:
|
|
7
|
+
* --json Output as JSON
|
|
8
|
+
* --yaml Output as YAML
|
|
9
|
+
* --show-token Include the current access token in output
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { buildClient, syncTokens } from '../lib/client.mjs';
|
|
13
|
+
import { outputMode, printJson, printYaml, printRecord, formatDate, error } from '../lib/output.mjs';
|
|
14
|
+
|
|
15
|
+
export const USAGE = `\
|
|
16
|
+
Usage: zeyos whoami [options]
|
|
17
|
+
|
|
18
|
+
Show information about the currently authenticated user.
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--json Output as JSON
|
|
22
|
+
--yaml Output as YAML
|
|
23
|
+
--show-token Include the current access token in output
|
|
24
|
+
-h, --help Show this help
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
export async function run(values) {
|
|
28
|
+
let client, config, tokenStore, configSource;
|
|
29
|
+
try {
|
|
30
|
+
({ client, config, tokenStore, configSource } = buildClient());
|
|
31
|
+
} catch (err) {
|
|
32
|
+
error(err.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let userInfo;
|
|
37
|
+
try {
|
|
38
|
+
userInfo = await client.oauth2.getUserInfo();
|
|
39
|
+
await syncTokens(tokenStore, configSource);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
error(`Failed to fetch user info: ${err.message}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const mode = outputMode(values);
|
|
46
|
+
|
|
47
|
+
const output = { ...userInfo };
|
|
48
|
+
if (values['show-token']) {
|
|
49
|
+
const tokenSet = await tokenStore.get();
|
|
50
|
+
if (tokenSet?.accessToken) output.accessToken = tokenSet.accessToken;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (mode === 'json') {
|
|
54
|
+
printJson(output);
|
|
55
|
+
} else if (mode === 'yaml') {
|
|
56
|
+
printYaml(output);
|
|
57
|
+
} else {
|
|
58
|
+
// Pretty key-value record with custom formatters
|
|
59
|
+
const dateFormat = config.dateFormat ?? 'YYYY-MM-DD HH:mm';
|
|
60
|
+
const keys = Object.keys(output);
|
|
61
|
+
const formatters = {};
|
|
62
|
+
|
|
63
|
+
// Format updated_at as a human-readable date
|
|
64
|
+
if (output.updated_at != null) {
|
|
65
|
+
formatters.updated_at = (val) => formatDate(val, dateFormat);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Format groups as a multi-line list
|
|
69
|
+
if (Array.isArray(output.groups)) {
|
|
70
|
+
formatters.groups = (val) => _formatObjectList(val, 'name', 'writable');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Format permissions as a multi-line list
|
|
74
|
+
if (Array.isArray(output.permissions)) {
|
|
75
|
+
formatters.permissions = (val) => _formatObjectList(val, 'identifier', 'writable');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
printRecord(output, keys, {}, formatters);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Format an array of objects as a multi-line list.
|
|
84
|
+
* Each item is shown as "name (rw)" or "name (ro)" on its own line.
|
|
85
|
+
*
|
|
86
|
+
* @param {Record<string, string|number|boolean|null|undefined>[]} items
|
|
87
|
+
* @param {string} nameKey - key to use as display name
|
|
88
|
+
* @param {string} writableKey - key indicating write access (boolean)
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function _formatObjectList(items, nameKey, writableKey) {
|
|
92
|
+
if (!Array.isArray(items) || items.length === 0) return '(none)';
|
|
93
|
+
return items
|
|
94
|
+
.map(item => {
|
|
95
|
+
const name = item[nameKey] ?? '?';
|
|
96
|
+
const rw = item[writableKey] ? 'rw' : 'ro';
|
|
97
|
+
return `· ${name} (${rw})`;
|
|
98
|
+
})
|
|
99
|
+
.join('\n');
|
|
100
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"list": {
|
|
3
|
+
"fields": {
|
|
4
|
+
"ID": "ID",
|
|
5
|
+
"Num": "customernum",
|
|
6
|
+
"Lastname": "lastname",
|
|
7
|
+
"Firstname": "firstname",
|
|
8
|
+
"City": "contact.city",
|
|
9
|
+
"Country": "contact.country",
|
|
10
|
+
"Agent": "assigneduser.name",
|
|
11
|
+
"Modified": "lastmodified"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"get": {
|
|
15
|
+
"fields": ["ID", "customernum", "lastname", "firstname", "type", "assigneduser", "visibility", "created", "lastmodified"],
|
|
16
|
+
"params": { "extdata": 1 }
|
|
17
|
+
}
|
|
18
|
+
}
|
package/config/item.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"list": {
|
|
3
|
+
"fields": {
|
|
4
|
+
"ID": "ID",
|
|
5
|
+
"Num": "itemnum",
|
|
6
|
+
"Name": "name",
|
|
7
|
+
"Manufacturer": "manufacturer",
|
|
8
|
+
"Type": "type",
|
|
9
|
+
"Selling": "sellingprice",
|
|
10
|
+
"Purchase": "purchaseprice"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"get": {
|
|
14
|
+
"fields": ["ID", "itemnum", "name", "manufacturer", "type", "sellingprice", "purchaseprice", "description", "created", "lastmodified"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"list": {
|
|
3
|
+
"fields": {
|
|
4
|
+
"ID": "ID",
|
|
5
|
+
"Num": "projectnum",
|
|
6
|
+
"Name": "name",
|
|
7
|
+
"Status": "status",
|
|
8
|
+
"Agent": "assigneduser.name",
|
|
9
|
+
"Modified": "lastmodified"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"get": {
|
|
13
|
+
"fields": ["ID", "projectnum", "name", "description", "status", "assigneduser", "visibility", "created", "lastmodified"],
|
|
14
|
+
"params": { "extdata": 1 }
|
|
15
|
+
}
|
|
16
|
+
}
|
package/config/task.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"list": {
|
|
3
|
+
"fields": {
|
|
4
|
+
"ID": "ID",
|
|
5
|
+
"Num": "tasknum",
|
|
6
|
+
"Name": "name",
|
|
7
|
+
"Status": "status",
|
|
8
|
+
"Priority": "priority",
|
|
9
|
+
"Agent": "assigneduser.name",
|
|
10
|
+
"Due": "duedate",
|
|
11
|
+
"Ticket": "ticket"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"get": {
|
|
15
|
+
"fields": ["ID", "tasknum", "name", "description", "status", "priority", "duedate", "ticket", "creator", "created", "lastmodified"],
|
|
16
|
+
"params": { "extdata": 1 }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"list": {
|
|
3
|
+
"fields": {
|
|
4
|
+
"ID": "ID",
|
|
5
|
+
"Num": "ticketnum",
|
|
6
|
+
"Name": "name",
|
|
7
|
+
"Status": "status",
|
|
8
|
+
"Priority": "priority",
|
|
9
|
+
"Account": "account.lastname",
|
|
10
|
+
"Agent": "assigneduser.name",
|
|
11
|
+
"Due": "duedate",
|
|
12
|
+
"Modified": "lastmodified"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"get": {
|
|
16
|
+
"fields": ["ID", "ticketnum", "name", "description", "status", "priority", "duedate", "created", "creator", "lastmodified"],
|
|
17
|
+
"params": { "extdata": 1 }
|
|
18
|
+
}
|
|
19
|
+
}
|