@totoday/quinn-cli 0.1.1 → 0.1.3
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/CHANGELOG.md +20 -0
- package/README.md +54 -0
- package/dist/index.js +50 -370
- package/package.json +2 -2
- package/src/index.ts +57 -430
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @totoday/quinn-cli
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 16a1fe9: Move organization path handling into the shared HTTP client to simplify service construction.
|
|
8
|
+
- Updated dependencies [16a1fe9]
|
|
9
|
+
- @totoday/quinn-sdk@0.1.3
|
|
10
|
+
|
|
11
|
+
## 0.1.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 629fba0: Unify organization query semantics around `organizations current`:
|
|
16
|
+
- remove `organizations details` command from CLI
|
|
17
|
+
- make `organizations current` return organization details with aggregate stats
|
|
18
|
+
- align SDK and skill documentation with the simplified flow
|
|
19
|
+
- Updated dependencies [45948dc]
|
|
20
|
+
- @totoday/quinn-sdk@0.1.2
|
|
21
|
+
|
|
3
22
|
## 0.1.1
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -8,3 +27,4 @@
|
|
|
8
27
|
- default to interactive hidden password prompt
|
|
9
28
|
- add `--password-stdin` for script-friendly secure input
|
|
10
29
|
- deprecate plain-text `--password` with warning
|
|
30
|
+
- @totoday/quinn-sdk@0.1.1
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @totoday/quinn-cli
|
|
2
|
+
|
|
3
|
+
Minimal CLI for Quinn auth/config/connectivity setup.
|
|
4
|
+
|
|
5
|
+
Business data operations should use `@totoday/quinn-sdk`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g @totoday/quinn-cli
|
|
11
|
+
quinn --help
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
One-off without global install:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @totoday/quinn-cli --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# login (recommended: hidden password prompt)
|
|
24
|
+
quinn login --email <email>
|
|
25
|
+
|
|
26
|
+
# login via stdin (scripts/password managers)
|
|
27
|
+
echo "<password>" | quinn login --email <email> --password-stdin
|
|
28
|
+
|
|
29
|
+
# inspect local config
|
|
30
|
+
quinn config path
|
|
31
|
+
quinn config get
|
|
32
|
+
|
|
33
|
+
# update local config
|
|
34
|
+
quinn config set --org-id <orgId>
|
|
35
|
+
quinn config set --api-url <apiUrl> --api-token <token> --org-id <orgId>
|
|
36
|
+
|
|
37
|
+
# connectivity test (calls organizations.current via SDK)
|
|
38
|
+
quinn test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Config
|
|
42
|
+
|
|
43
|
+
CLI reads config from:
|
|
44
|
+
|
|
45
|
+
- `~/.config/quinn/config.json` (default)
|
|
46
|
+
- `QUINN_CONFIG_PATH` (override)
|
|
47
|
+
|
|
48
|
+
Runtime override order:
|
|
49
|
+
|
|
50
|
+
1. command flags
|
|
51
|
+
2. env vars (`QUINN_API_URL`, `QUINN_API_TOKEN`, `QUINN_ORG_ID`)
|
|
52
|
+
3. config file
|
|
53
|
+
|
|
54
|
+
If `apiUrl` is missing, default is `https://api.lunapark.com`.
|
package/dist/index.js
CHANGED
|
@@ -12,25 +12,6 @@ const quinn_sdk_1 = require("@totoday/quinn-sdk");
|
|
|
12
12
|
function print(data) {
|
|
13
13
|
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
14
14
|
}
|
|
15
|
-
function asCsv(raw) {
|
|
16
|
-
if (!raw) {
|
|
17
|
-
return [];
|
|
18
|
-
}
|
|
19
|
-
return raw
|
|
20
|
-
.split(',')
|
|
21
|
-
.map((item) => item.trim())
|
|
22
|
-
.filter((item) => item.length > 0);
|
|
23
|
-
}
|
|
24
|
-
function asPrivilege(raw) {
|
|
25
|
-
const values = asCsv(raw);
|
|
26
|
-
if (values.length === 0) {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
if (values.length === 1) {
|
|
30
|
-
return values[0];
|
|
31
|
-
}
|
|
32
|
-
return values;
|
|
33
|
-
}
|
|
34
15
|
function maskToken(token) {
|
|
35
16
|
if (!token) {
|
|
36
17
|
return null;
|
|
@@ -40,45 +21,6 @@ function maskToken(token) {
|
|
|
40
21
|
}
|
|
41
22
|
return `${token.slice(0, 6)}...${token.slice(-4)}`;
|
|
42
23
|
}
|
|
43
|
-
function getConfigPath(opts) {
|
|
44
|
-
return (opts.configPath ||
|
|
45
|
-
process.env.QUINN_CONFIG_PATH ||
|
|
46
|
-
node_path_1.default.join(node_os_1.default.homedir(), '.config', 'quinn', 'config.json'));
|
|
47
|
-
}
|
|
48
|
-
function readConfig(configPath) {
|
|
49
|
-
if (!node_fs_1.default.existsSync(configPath)) {
|
|
50
|
-
return {};
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const raw = node_fs_1.default.readFileSync(configPath, 'utf8');
|
|
54
|
-
const parsed = JSON.parse(raw);
|
|
55
|
-
return parsed ?? {};
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function writeConfig(configPath, config) {
|
|
62
|
-
node_fs_1.default.mkdirSync(node_path_1.default.dirname(configPath), { recursive: true });
|
|
63
|
-
node_fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
64
|
-
}
|
|
65
|
-
function resolveRuntimeConfig(opts, fileConfig) {
|
|
66
|
-
return {
|
|
67
|
-
apiUrl: opts.apiUrl || process.env.QUINN_API_URL || fileConfig.apiUrl || quinn_sdk_1.DEFAULT_QUINN_API_URL,
|
|
68
|
-
token: opts.apiToken || opts.token || process.env.QUINN_API_TOKEN || fileConfig.token,
|
|
69
|
-
orgId: opts.orgId || process.env.QUINN_ORG_ID || fileConfig.orgId,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function createClient(config) {
|
|
73
|
-
const { apiUrl, token, orgId } = config;
|
|
74
|
-
if (!token) {
|
|
75
|
-
throw new Error('missing token: set --api-token/--token or QUINN_API_TOKEN or config');
|
|
76
|
-
}
|
|
77
|
-
if (!orgId) {
|
|
78
|
-
throw new Error('missing orgId: set --org-id or QUINN_ORG_ID or config');
|
|
79
|
-
}
|
|
80
|
-
return new quinn_sdk_1.Quinn({ apiUrl, token, orgId });
|
|
81
|
-
}
|
|
82
24
|
function readPasswordFromStdin() {
|
|
83
25
|
const value = node_fs_1.default.readFileSync(0, 'utf8').trim();
|
|
84
26
|
if (!value) {
|
|
@@ -133,12 +75,51 @@ async function promptPasswordHidden(prompt = 'Password: ') {
|
|
|
133
75
|
stdin.on('data', onData);
|
|
134
76
|
});
|
|
135
77
|
}
|
|
78
|
+
function getConfigPath(opts) {
|
|
79
|
+
return (opts.configPath ||
|
|
80
|
+
process.env.QUINN_CONFIG_PATH ||
|
|
81
|
+
node_path_1.default.join(node_os_1.default.homedir(), '.config', 'quinn', 'config.json'));
|
|
82
|
+
}
|
|
83
|
+
function readConfig(configPath) {
|
|
84
|
+
if (!node_fs_1.default.existsSync(configPath)) {
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const raw = node_fs_1.default.readFileSync(configPath, 'utf8');
|
|
89
|
+
const parsed = JSON.parse(raw);
|
|
90
|
+
return parsed ?? {};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function writeConfig(configPath, config) {
|
|
97
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(configPath), { recursive: true });
|
|
98
|
+
node_fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
99
|
+
}
|
|
100
|
+
function resolveRuntimeConfig(opts, fileConfig) {
|
|
101
|
+
return {
|
|
102
|
+
apiUrl: opts.apiUrl || process.env.QUINN_API_URL || fileConfig.apiUrl || quinn_sdk_1.DEFAULT_QUINN_API_URL,
|
|
103
|
+
token: opts.apiToken || opts.token || process.env.QUINN_API_TOKEN || fileConfig.token,
|
|
104
|
+
orgId: opts.orgId || process.env.QUINN_ORG_ID || fileConfig.orgId,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function createClient(config) {
|
|
108
|
+
const { apiUrl, token, orgId } = config;
|
|
109
|
+
if (!token) {
|
|
110
|
+
throw new Error('missing token: set --api-token/--token or QUINN_API_TOKEN or config');
|
|
111
|
+
}
|
|
112
|
+
if (!orgId) {
|
|
113
|
+
throw new Error('missing orgId: set --org-id or QUINN_ORG_ID or config');
|
|
114
|
+
}
|
|
115
|
+
return new quinn_sdk_1.Quinn({ apiUrl, token, orgId });
|
|
116
|
+
}
|
|
136
117
|
function getContext(command) {
|
|
137
118
|
const global = command.optsWithGlobals();
|
|
138
119
|
const configPath = getConfigPath(global);
|
|
139
120
|
const fileConfig = readConfig(configPath);
|
|
140
121
|
const runtimeConfig = resolveRuntimeConfig(global, fileConfig);
|
|
141
|
-
return {
|
|
122
|
+
return { configPath, fileConfig, runtimeConfig };
|
|
142
123
|
}
|
|
143
124
|
async function withHandler(fn) {
|
|
144
125
|
try {
|
|
@@ -179,16 +160,14 @@ async function withHandler(fn) {
|
|
|
179
160
|
const program = new commander_1.Command();
|
|
180
161
|
program
|
|
181
162
|
.name('quinn')
|
|
182
|
-
.description('Quinn CLI')
|
|
163
|
+
.description('Quinn CLI (minimal setup utility, SDK-first runtime)')
|
|
183
164
|
.addHelpText('after', [
|
|
184
165
|
'',
|
|
185
166
|
'Examples:',
|
|
186
167
|
' quinn login --email <email>',
|
|
187
168
|
' echo "<password>" | quinn login --email <email> --password-stdin',
|
|
188
|
-
' quinn config
|
|
189
|
-
' quinn
|
|
190
|
-
' quinn members find alice',
|
|
191
|
-
' quinn members get user-1,user-2,user@example.com',
|
|
169
|
+
' quinn config get',
|
|
170
|
+
' quinn test',
|
|
192
171
|
].join('\n'))
|
|
193
172
|
.showHelpAfterError()
|
|
194
173
|
.option('--config-path <path>', 'config file path (default ~/.config/quinn/config.json)')
|
|
@@ -248,13 +227,6 @@ configCmd
|
|
|
248
227
|
},
|
|
249
228
|
});
|
|
250
229
|
});
|
|
251
|
-
configCmd.addHelpText('after', [
|
|
252
|
-
'',
|
|
253
|
-
'Examples:',
|
|
254
|
-
' quinn config path',
|
|
255
|
-
' quinn config get',
|
|
256
|
-
' quinn config set --api-url http://localhost:8090 --api-token <token> --org-id <orgId>',
|
|
257
|
-
].join('\n'));
|
|
258
230
|
program
|
|
259
231
|
.command('login')
|
|
260
232
|
.description('login and save token (+orgId) into config (password input is hidden by default)')
|
|
@@ -279,14 +251,8 @@ program
|
|
|
279
251
|
else {
|
|
280
252
|
password = await promptPasswordHidden();
|
|
281
253
|
}
|
|
282
|
-
const auth = new quinn_sdk_1.QuinnAuth({
|
|
283
|
-
|
|
284
|
-
configPath,
|
|
285
|
-
});
|
|
286
|
-
const login = await auth.login({
|
|
287
|
-
email: opts.email,
|
|
288
|
-
password,
|
|
289
|
-
});
|
|
254
|
+
const auth = new quinn_sdk_1.QuinnAuth({ apiUrl: runtimeConfig.apiUrl, configPath });
|
|
255
|
+
const login = await auth.login({ email: opts.email, password });
|
|
290
256
|
const resolvedOrgId = opts.orgId || login.orgId || fileConfig.orgId;
|
|
291
257
|
if (!resolvedOrgId) {
|
|
292
258
|
throw new Error('orgId not found in login response, please pass --org-id');
|
|
@@ -308,303 +274,17 @@ program
|
|
|
308
274
|
});
|
|
309
275
|
});
|
|
310
276
|
});
|
|
311
|
-
|
|
312
|
-
.command('
|
|
313
|
-
.description('
|
|
314
|
-
orgCmd
|
|
315
|
-
.command('current')
|
|
316
|
-
.description('get current organization basic info (id, name)')
|
|
317
|
-
.action(function () {
|
|
318
|
-
return withHandler(async () => {
|
|
319
|
-
const { runtimeConfig } = getContext(this);
|
|
320
|
-
const quinn = createClient(runtimeConfig);
|
|
321
|
-
print(await quinn.organizations.current());
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
orgCmd
|
|
325
|
-
.command('details')
|
|
326
|
-
.description('get organization details with aggregate stats')
|
|
277
|
+
program
|
|
278
|
+
.command('test')
|
|
279
|
+
.description('test connectivity and auth by calling organizations.current()')
|
|
327
280
|
.action(function () {
|
|
328
281
|
return withHandler(async () => {
|
|
329
282
|
const { runtimeConfig } = getContext(this);
|
|
330
283
|
const quinn = createClient(runtimeConfig);
|
|
331
|
-
|
|
284
|
+
const item = await quinn.organizations.current();
|
|
285
|
+
print({ ok: true, item });
|
|
332
286
|
});
|
|
333
287
|
});
|
|
334
|
-
orgCmd.addHelpText('after', [
|
|
335
|
-
'',
|
|
336
|
-
'Examples:',
|
|
337
|
-
' quinn organizations current',
|
|
338
|
-
' quinn organizations details',
|
|
339
|
-
].join('\n'));
|
|
340
|
-
const membersCmd = program.command('members').description('member operations');
|
|
341
|
-
membersCmd
|
|
342
|
-
.command('list')
|
|
343
|
-
.description('list members with structured filters (no keyword search)')
|
|
344
|
-
.option('--privilege <value>', 'filter by privilege: owner|admin|member, supports comma-separated')
|
|
345
|
-
.option('--manager-uid <uid>', 'filter by manager UID')
|
|
346
|
-
.option('--limit <n>', 'page size')
|
|
347
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
348
|
-
.action(function (opts) {
|
|
349
|
-
return withHandler(async () => {
|
|
350
|
-
const { runtimeConfig } = getContext(this);
|
|
351
|
-
const quinn = createClient(runtimeConfig);
|
|
352
|
-
print(await quinn.members.list({
|
|
353
|
-
privilege: asPrivilege(opts.privilege),
|
|
354
|
-
managerUid: opts.managerUid,
|
|
355
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
356
|
-
token: opts.pageToken,
|
|
357
|
-
}));
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
membersCmd
|
|
361
|
-
.command('find')
|
|
362
|
-
.description('find members by keyword (name/email fuzzy search)')
|
|
363
|
-
.argument('<query>')
|
|
364
|
-
.option('--limit <n>', 'page size')
|
|
365
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
366
|
-
.action(function (query, opts) {
|
|
367
|
-
return withHandler(async () => {
|
|
368
|
-
const { runtimeConfig } = getContext(this);
|
|
369
|
-
const quinn = createClient(runtimeConfig);
|
|
370
|
-
print(await quinn.members.list({
|
|
371
|
-
search: query,
|
|
372
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
373
|
-
token: opts.pageToken,
|
|
374
|
-
}));
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
membersCmd
|
|
378
|
-
.command('list-managers')
|
|
379
|
-
.description('list unique manager members in the organization')
|
|
380
|
-
.option('--search <text>', 'optional keyword filter on manager name/email')
|
|
381
|
-
.option('--limit <n>', 'page size')
|
|
382
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
383
|
-
.action(function (opts) {
|
|
384
|
-
return withHandler(async () => {
|
|
385
|
-
const { runtimeConfig } = getContext(this);
|
|
386
|
-
const quinn = createClient(runtimeConfig);
|
|
387
|
-
print(await quinn.members.listManagers({
|
|
388
|
-
search: opts.search,
|
|
389
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
390
|
-
token: opts.pageToken,
|
|
391
|
-
}));
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
membersCmd
|
|
395
|
-
.command('get')
|
|
396
|
-
.description('get one member by ID/email, or many members by comma-separated values')
|
|
397
|
-
.argument('<memberIdOrEmail>')
|
|
398
|
-
.action(function (memberIdOrEmail) {
|
|
399
|
-
return withHandler(async () => {
|
|
400
|
-
const { runtimeConfig } = getContext(this);
|
|
401
|
-
const quinn = createClient(runtimeConfig);
|
|
402
|
-
const values = asCsv(memberIdOrEmail);
|
|
403
|
-
if (values.length <= 1) {
|
|
404
|
-
print(await quinn.members.get(memberIdOrEmail));
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const ids = [];
|
|
408
|
-
const emails = [];
|
|
409
|
-
for (const value of values) {
|
|
410
|
-
if (value.includes('@')) {
|
|
411
|
-
emails.push(value);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
ids.push(value);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
print(await quinn.members.batchGet({
|
|
418
|
-
ids: ids.length > 0 ? ids : undefined,
|
|
419
|
-
emails: emails.length > 0 ? emails : undefined,
|
|
420
|
-
}));
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
membersCmd.addHelpText('after', [
|
|
424
|
-
'',
|
|
425
|
-
'Examples:',
|
|
426
|
-
' quinn members list --privilege owner,admin',
|
|
427
|
-
' quinn members list --manager-uid <uid>',
|
|
428
|
-
' quinn members find alice',
|
|
429
|
-
' quinn members list-managers --search bob',
|
|
430
|
-
' quinn members get <memberId>',
|
|
431
|
-
' quinn members get <memberId1,memberId2,user@example.com>',
|
|
432
|
-
].join('\n'));
|
|
433
|
-
const rolesCmd = program.command('roles').description('role operations');
|
|
434
|
-
rolesCmd.command('list').description('list all roles in the organization').action(function () {
|
|
435
|
-
return withHandler(async () => {
|
|
436
|
-
const { runtimeConfig } = getContext(this);
|
|
437
|
-
const quinn = createClient(runtimeConfig);
|
|
438
|
-
print(await quinn.roles.list());
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
rolesCmd
|
|
442
|
-
.command('get')
|
|
443
|
-
.description('get one role by ID, or many roles by comma-separated values')
|
|
444
|
-
.argument('<roleId>')
|
|
445
|
-
.action(function (roleId) {
|
|
446
|
-
return withHandler(async () => {
|
|
447
|
-
const { runtimeConfig } = getContext(this);
|
|
448
|
-
const quinn = createClient(runtimeConfig);
|
|
449
|
-
const values = asCsv(roleId);
|
|
450
|
-
if (values.length <= 1) {
|
|
451
|
-
print(await quinn.roles.get(roleId));
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
print(await quinn.roles.batchGet(values));
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
rolesCmd.addHelpText('after', [
|
|
458
|
-
'',
|
|
459
|
-
'Examples:',
|
|
460
|
-
' quinn roles list',
|
|
461
|
-
' quinn roles get <roleId>',
|
|
462
|
-
' quinn roles get <roleId1,roleId2>',
|
|
463
|
-
].join('\n'));
|
|
464
|
-
const levelsCmd = program.command('levels').description('level operations');
|
|
465
|
-
levelsCmd
|
|
466
|
-
.command('list')
|
|
467
|
-
.description('list levels under a role')
|
|
468
|
-
.requiredOption('--role-id <id>', 'role ID to scope the query')
|
|
469
|
-
.option('--limit <n>', 'page size')
|
|
470
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
471
|
-
.action(function (opts) {
|
|
472
|
-
return withHandler(async () => {
|
|
473
|
-
const { runtimeConfig } = getContext(this);
|
|
474
|
-
const quinn = createClient(runtimeConfig);
|
|
475
|
-
print(await quinn.levels.list({
|
|
476
|
-
roleId: opts.roleId,
|
|
477
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
478
|
-
token: opts.pageToken,
|
|
479
|
-
}));
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
levelsCmd
|
|
483
|
-
.command('get')
|
|
484
|
-
.description('get one level by ID, or many levels by comma-separated values')
|
|
485
|
-
.argument('<levelId>')
|
|
486
|
-
.action(function (levelId) {
|
|
487
|
-
return withHandler(async () => {
|
|
488
|
-
const { runtimeConfig } = getContext(this);
|
|
489
|
-
const quinn = createClient(runtimeConfig);
|
|
490
|
-
const values = asCsv(levelId);
|
|
491
|
-
if (values.length <= 1) {
|
|
492
|
-
print(await quinn.levels.get(levelId));
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
print(await quinn.levels.batchGet(values));
|
|
496
|
-
});
|
|
497
|
-
});
|
|
498
|
-
levelsCmd.addHelpText('after', [
|
|
499
|
-
'',
|
|
500
|
-
'Examples:',
|
|
501
|
-
' quinn levels list --role-id <roleId>',
|
|
502
|
-
' quinn levels get <levelId>',
|
|
503
|
-
' quinn levels get <levelId1,levelId2>',
|
|
504
|
-
].join('\n'));
|
|
505
|
-
const compsCmd = program.command('competencies').description('competency operations');
|
|
506
|
-
compsCmd
|
|
507
|
-
.command('list')
|
|
508
|
-
.description('list competencies scoped by role + level')
|
|
509
|
-
.requiredOption('--role-id <id>', 'role ID')
|
|
510
|
-
.requiredOption('--level-id <id>', 'level ID')
|
|
511
|
-
.option('--search <text>', 'optional keyword filter')
|
|
512
|
-
.option('--limit <n>', 'page size')
|
|
513
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
514
|
-
.action(function (opts) {
|
|
515
|
-
return withHandler(async () => {
|
|
516
|
-
const { runtimeConfig } = getContext(this);
|
|
517
|
-
const quinn = createClient(runtimeConfig);
|
|
518
|
-
print(await quinn.competencies.list({
|
|
519
|
-
roleId: opts.roleId,
|
|
520
|
-
levelId: opts.levelId,
|
|
521
|
-
search: opts.search,
|
|
522
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
523
|
-
token: opts.pageToken,
|
|
524
|
-
}));
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
compsCmd
|
|
528
|
-
.command('get')
|
|
529
|
-
.description('get one competency by ID, or many competencies by comma-separated values')
|
|
530
|
-
.argument('<competencyId>')
|
|
531
|
-
.action(function (competencyId) {
|
|
532
|
-
return withHandler(async () => {
|
|
533
|
-
const { runtimeConfig } = getContext(this);
|
|
534
|
-
const quinn = createClient(runtimeConfig);
|
|
535
|
-
const values = asCsv(competencyId);
|
|
536
|
-
if (values.length <= 1) {
|
|
537
|
-
print(await quinn.competencies.get(competencyId));
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
print(await quinn.competencies.batchGet(values));
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
compsCmd
|
|
544
|
-
.command('courses')
|
|
545
|
-
.description('list courses under a competency')
|
|
546
|
-
.argument('<competencyId>')
|
|
547
|
-
.action(function (competencyId) {
|
|
548
|
-
return withHandler(async () => {
|
|
549
|
-
const { runtimeConfig } = getContext(this);
|
|
550
|
-
const quinn = createClient(runtimeConfig);
|
|
551
|
-
print(await quinn.competencies.listCourses(competencyId));
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
compsCmd.addHelpText('after', [
|
|
555
|
-
'',
|
|
556
|
-
'Examples:',
|
|
557
|
-
' quinn competencies list --role-id <roleId> --level-id <levelId>',
|
|
558
|
-
' quinn competencies get <competencyId>',
|
|
559
|
-
' quinn competencies get <id1,id2>',
|
|
560
|
-
' quinn competencies courses <competencyId>',
|
|
561
|
-
].join('\n'));
|
|
562
|
-
const endorseCmd = program.command('endorsements').description('endorsement operations');
|
|
563
|
-
endorseCmd
|
|
564
|
-
.command('get')
|
|
565
|
-
.description('get one endorsement by ID')
|
|
566
|
-
.argument('<endorsementId>')
|
|
567
|
-
.action(function (endorsementId) {
|
|
568
|
-
return withHandler(async () => {
|
|
569
|
-
const { runtimeConfig } = getContext(this);
|
|
570
|
-
const quinn = createClient(runtimeConfig);
|
|
571
|
-
print(await quinn.endorsements.get(endorsementId));
|
|
572
|
-
});
|
|
573
|
-
});
|
|
574
|
-
endorseCmd
|
|
575
|
-
.command('find')
|
|
576
|
-
.description('find one endorsement by user + competency')
|
|
577
|
-
.requiredOption('--uid <uid>', 'user ID')
|
|
578
|
-
.requiredOption('--competency-id <id>', 'competency ID')
|
|
579
|
-
.action(function (opts) {
|
|
580
|
-
return withHandler(async () => {
|
|
581
|
-
const { runtimeConfig } = getContext(this);
|
|
582
|
-
const quinn = createClient(runtimeConfig);
|
|
583
|
-
print(await quinn.endorsements.find(opts.uid, opts.competencyId));
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
endorseCmd
|
|
587
|
-
.command('list')
|
|
588
|
-
.description('list endorsements by user IDs and competency IDs')
|
|
589
|
-
.requiredOption('--uids <uids>', 'comma-separated user IDs')
|
|
590
|
-
.requiredOption('--competency-ids <ids>', 'comma-separated competency IDs')
|
|
591
|
-
.action(function (opts) {
|
|
592
|
-
return withHandler(async () => {
|
|
593
|
-
const { runtimeConfig } = getContext(this);
|
|
594
|
-
const quinn = createClient(runtimeConfig);
|
|
595
|
-
print(await quinn.endorsements.list({
|
|
596
|
-
uids: asCsv(opts.uids),
|
|
597
|
-
competencyIds: asCsv(opts.competencyIds),
|
|
598
|
-
}));
|
|
599
|
-
});
|
|
600
|
-
});
|
|
601
|
-
endorseCmd.addHelpText('after', [
|
|
602
|
-
'',
|
|
603
|
-
'Examples:',
|
|
604
|
-
' quinn endorsements get <endorsementId>',
|
|
605
|
-
' quinn endorsements find --uid <uid> --competency-id <competencyId>',
|
|
606
|
-
' quinn endorsements list --uids <u1,u2> --competency-ids <c1,c2>',
|
|
607
|
-
].join('\n'));
|
|
608
288
|
program.parseAsync(process.argv).catch((error) => {
|
|
609
289
|
const message = error instanceof Error ? error.message : String(error);
|
|
610
290
|
process.stderr.write(`${message}\n`);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@totoday/quinn-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"bin": {
|
|
5
5
|
"quinn": "dist/index.js"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"commander": "^13.1.0",
|
|
9
|
-
"@totoday/quinn-sdk": "0.1.
|
|
9
|
+
"@totoday/quinn-sdk": "0.1.3"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/node": "^22.13.10",
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
|
-
import { DEFAULT_QUINN_API_URL, Quinn, QuinnAuth
|
|
7
|
+
import { DEFAULT_QUINN_API_URL, Quinn, QuinnAuth } from '@totoday/quinn-sdk';
|
|
8
8
|
|
|
9
9
|
type QuinnCliConfig = {
|
|
10
10
|
apiUrl?: string;
|
|
@@ -24,27 +24,6 @@ function print(data: unknown): void {
|
|
|
24
24
|
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function asCsv(raw?: string): string[] {
|
|
28
|
-
if (!raw) {
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
return raw
|
|
32
|
-
.split(',')
|
|
33
|
-
.map((item) => item.trim())
|
|
34
|
-
.filter((item) => item.length > 0);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function asPrivilege(raw?: string): Privilege | Privilege[] | undefined {
|
|
38
|
-
const values = asCsv(raw);
|
|
39
|
-
if (values.length === 0) {
|
|
40
|
-
return undefined;
|
|
41
|
-
}
|
|
42
|
-
if (values.length === 1) {
|
|
43
|
-
return values[0] as Privilege;
|
|
44
|
-
}
|
|
45
|
-
return values as Privilege[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
27
|
function maskToken(token?: string): string | null {
|
|
49
28
|
if (!token) {
|
|
50
29
|
return null;
|
|
@@ -55,51 +34,6 @@ function maskToken(token?: string): string | null {
|
|
|
55
34
|
return `${token.slice(0, 6)}...${token.slice(-4)}`;
|
|
56
35
|
}
|
|
57
36
|
|
|
58
|
-
function getConfigPath(opts: GlobalOptions): string {
|
|
59
|
-
return (
|
|
60
|
-
opts.configPath ||
|
|
61
|
-
process.env.QUINN_CONFIG_PATH ||
|
|
62
|
-
path.join(os.homedir(), '.config', 'quinn', 'config.json')
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function readConfig(configPath: string): QuinnCliConfig {
|
|
67
|
-
if (!fs.existsSync(configPath)) {
|
|
68
|
-
return {};
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
const raw = fs.readFileSync(configPath, 'utf8');
|
|
72
|
-
const parsed = JSON.parse(raw) as QuinnCliConfig;
|
|
73
|
-
return parsed ?? {};
|
|
74
|
-
} catch {
|
|
75
|
-
return {};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function writeConfig(configPath: string, config: QuinnCliConfig): void {
|
|
80
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
81
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function resolveRuntimeConfig(opts: GlobalOptions, fileConfig: QuinnCliConfig): QuinnCliConfig {
|
|
85
|
-
return {
|
|
86
|
-
apiUrl: opts.apiUrl || process.env.QUINN_API_URL || fileConfig.apiUrl || DEFAULT_QUINN_API_URL,
|
|
87
|
-
token: opts.apiToken || opts.token || process.env.QUINN_API_TOKEN || fileConfig.token,
|
|
88
|
-
orgId: opts.orgId || process.env.QUINN_ORG_ID || fileConfig.orgId,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function createClient(config: QuinnCliConfig): Quinn {
|
|
93
|
-
const { apiUrl, token, orgId } = config;
|
|
94
|
-
if (!token) {
|
|
95
|
-
throw new Error('missing token: set --api-token/--token or QUINN_API_TOKEN or config');
|
|
96
|
-
}
|
|
97
|
-
if (!orgId) {
|
|
98
|
-
throw new Error('missing orgId: set --org-id or QUINN_ORG_ID or config');
|
|
99
|
-
}
|
|
100
|
-
return new Quinn({ apiUrl, token, orgId });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
37
|
function readPasswordFromStdin(): string {
|
|
104
38
|
const value = fs.readFileSync(0, 'utf8').trim();
|
|
105
39
|
if (!value) {
|
|
@@ -163,8 +97,52 @@ async function promptPasswordHidden(prompt = 'Password: '): Promise<string> {
|
|
|
163
97
|
});
|
|
164
98
|
}
|
|
165
99
|
|
|
100
|
+
function getConfigPath(opts: GlobalOptions): string {
|
|
101
|
+
return (
|
|
102
|
+
opts.configPath ||
|
|
103
|
+
process.env.QUINN_CONFIG_PATH ||
|
|
104
|
+
path.join(os.homedir(), '.config', 'quinn', 'config.json')
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readConfig(configPath: string): QuinnCliConfig {
|
|
109
|
+
if (!fs.existsSync(configPath)) {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
114
|
+
const parsed = JSON.parse(raw) as QuinnCliConfig;
|
|
115
|
+
return parsed ?? {};
|
|
116
|
+
} catch {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function writeConfig(configPath: string, config: QuinnCliConfig): void {
|
|
122
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
123
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function resolveRuntimeConfig(opts: GlobalOptions, fileConfig: QuinnCliConfig): QuinnCliConfig {
|
|
127
|
+
return {
|
|
128
|
+
apiUrl: opts.apiUrl || process.env.QUINN_API_URL || fileConfig.apiUrl || DEFAULT_QUINN_API_URL,
|
|
129
|
+
token: opts.apiToken || opts.token || process.env.QUINN_API_TOKEN || fileConfig.token,
|
|
130
|
+
orgId: opts.orgId || process.env.QUINN_ORG_ID || fileConfig.orgId,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function createClient(config: QuinnCliConfig): Quinn {
|
|
135
|
+
const { apiUrl, token, orgId } = config;
|
|
136
|
+
if (!token) {
|
|
137
|
+
throw new Error('missing token: set --api-token/--token or QUINN_API_TOKEN or config');
|
|
138
|
+
}
|
|
139
|
+
if (!orgId) {
|
|
140
|
+
throw new Error('missing orgId: set --org-id or QUINN_ORG_ID or config');
|
|
141
|
+
}
|
|
142
|
+
return new Quinn({ apiUrl, token, orgId });
|
|
143
|
+
}
|
|
144
|
+
|
|
166
145
|
function getContext(command: Command): {
|
|
167
|
-
global: GlobalOptions;
|
|
168
146
|
configPath: string;
|
|
169
147
|
fileConfig: QuinnCliConfig;
|
|
170
148
|
runtimeConfig: QuinnCliConfig;
|
|
@@ -173,7 +151,7 @@ function getContext(command: Command): {
|
|
|
173
151
|
const configPath = getConfigPath(global);
|
|
174
152
|
const fileConfig = readConfig(configPath);
|
|
175
153
|
const runtimeConfig = resolveRuntimeConfig(global, fileConfig);
|
|
176
|
-
return {
|
|
154
|
+
return { configPath, fileConfig, runtimeConfig };
|
|
177
155
|
}
|
|
178
156
|
|
|
179
157
|
async function withHandler(fn: () => Promise<void>): Promise<void> {
|
|
@@ -219,7 +197,7 @@ async function withHandler(fn: () => Promise<void>): Promise<void> {
|
|
|
219
197
|
const program = new Command();
|
|
220
198
|
program
|
|
221
199
|
.name('quinn')
|
|
222
|
-
.description('Quinn CLI')
|
|
200
|
+
.description('Quinn CLI (minimal setup utility, SDK-first runtime)')
|
|
223
201
|
.addHelpText(
|
|
224
202
|
'after',
|
|
225
203
|
[
|
|
@@ -227,10 +205,8 @@ program
|
|
|
227
205
|
'Examples:',
|
|
228
206
|
' quinn login --email <email>',
|
|
229
207
|
' echo "<password>" | quinn login --email <email> --password-stdin',
|
|
230
|
-
' quinn config
|
|
231
|
-
' quinn
|
|
232
|
-
' quinn members find alice',
|
|
233
|
-
' quinn members get user-1,user-2,user@example.com',
|
|
208
|
+
' quinn config get',
|
|
209
|
+
' quinn test',
|
|
234
210
|
].join('\n')
|
|
235
211
|
)
|
|
236
212
|
.showHelpAfterError()
|
|
@@ -294,16 +270,6 @@ configCmd
|
|
|
294
270
|
},
|
|
295
271
|
});
|
|
296
272
|
});
|
|
297
|
-
configCmd.addHelpText(
|
|
298
|
-
'after',
|
|
299
|
-
[
|
|
300
|
-
'',
|
|
301
|
-
'Examples:',
|
|
302
|
-
' quinn config path',
|
|
303
|
-
' quinn config get',
|
|
304
|
-
' quinn config set --api-url http://localhost:8090 --api-token <token> --org-id <orgId>',
|
|
305
|
-
].join('\n')
|
|
306
|
-
);
|
|
307
273
|
|
|
308
274
|
program
|
|
309
275
|
.command('login')
|
|
@@ -329,14 +295,8 @@ program
|
|
|
329
295
|
} else {
|
|
330
296
|
password = await promptPasswordHidden();
|
|
331
297
|
}
|
|
332
|
-
const auth = new QuinnAuth({
|
|
333
|
-
|
|
334
|
-
configPath,
|
|
335
|
-
});
|
|
336
|
-
const login = await auth.login({
|
|
337
|
-
email: opts.email,
|
|
338
|
-
password,
|
|
339
|
-
});
|
|
298
|
+
const auth = new QuinnAuth({ apiUrl: runtimeConfig.apiUrl, configPath });
|
|
299
|
+
const login = await auth.login({ email: opts.email, password });
|
|
340
300
|
const resolvedOrgId = opts.orgId || login.orgId || fileConfig.orgId;
|
|
341
301
|
if (!resolvedOrgId) {
|
|
342
302
|
throw new Error('orgId not found in login response, please pass --org-id');
|
|
@@ -359,350 +319,17 @@ program
|
|
|
359
319
|
});
|
|
360
320
|
});
|
|
361
321
|
|
|
362
|
-
|
|
363
|
-
.command('
|
|
364
|
-
.description('
|
|
365
|
-
orgCmd
|
|
366
|
-
.command('current')
|
|
367
|
-
.description('get current organization basic info (id, name)')
|
|
368
|
-
.action(function () {
|
|
369
|
-
return withHandler(async () => {
|
|
370
|
-
const { runtimeConfig } = getContext(this);
|
|
371
|
-
const quinn = createClient(runtimeConfig);
|
|
372
|
-
print(await quinn.organizations.current());
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
orgCmd
|
|
377
|
-
.command('details')
|
|
378
|
-
.description('get organization details with aggregate stats')
|
|
322
|
+
program
|
|
323
|
+
.command('test')
|
|
324
|
+
.description('test connectivity and auth by calling organizations.current()')
|
|
379
325
|
.action(function () {
|
|
380
326
|
return withHandler(async () => {
|
|
381
327
|
const { runtimeConfig } = getContext(this);
|
|
382
328
|
const quinn = createClient(runtimeConfig);
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
});
|
|
386
|
-
orgCmd.addHelpText(
|
|
387
|
-
'after',
|
|
388
|
-
[
|
|
389
|
-
'',
|
|
390
|
-
'Examples:',
|
|
391
|
-
' quinn organizations current',
|
|
392
|
-
' quinn organizations details',
|
|
393
|
-
].join('\n')
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
const membersCmd = program.command('members').description('member operations');
|
|
397
|
-
membersCmd
|
|
398
|
-
.command('list')
|
|
399
|
-
.description('list members with structured filters (no keyword search)')
|
|
400
|
-
.option('--privilege <value>', 'filter by privilege: owner|admin|member, supports comma-separated')
|
|
401
|
-
.option('--manager-uid <uid>', 'filter by manager UID')
|
|
402
|
-
.option('--limit <n>', 'page size')
|
|
403
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
404
|
-
.action(function (opts: { privilege?: string; managerUid?: string; limit?: string; pageToken?: string }) {
|
|
405
|
-
return withHandler(async () => {
|
|
406
|
-
const { runtimeConfig } = getContext(this);
|
|
407
|
-
const quinn = createClient(runtimeConfig);
|
|
408
|
-
print(
|
|
409
|
-
await quinn.members.list({
|
|
410
|
-
privilege: asPrivilege(opts.privilege),
|
|
411
|
-
managerUid: opts.managerUid,
|
|
412
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
413
|
-
token: opts.pageToken,
|
|
414
|
-
})
|
|
415
|
-
);
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
membersCmd
|
|
420
|
-
.command('find')
|
|
421
|
-
.description('find members by keyword (name/email fuzzy search)')
|
|
422
|
-
.argument('<query>')
|
|
423
|
-
.option('--limit <n>', 'page size')
|
|
424
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
425
|
-
.action(function (query: string, opts: { limit?: string; pageToken?: string }) {
|
|
426
|
-
return withHandler(async () => {
|
|
427
|
-
const { runtimeConfig } = getContext(this);
|
|
428
|
-
const quinn = createClient(runtimeConfig);
|
|
429
|
-
print(
|
|
430
|
-
await quinn.members.list({
|
|
431
|
-
search: query,
|
|
432
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
433
|
-
token: opts.pageToken,
|
|
434
|
-
})
|
|
435
|
-
);
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
membersCmd
|
|
440
|
-
.command('list-managers')
|
|
441
|
-
.description('list unique manager members in the organization')
|
|
442
|
-
.option('--search <text>', 'optional keyword filter on manager name/email')
|
|
443
|
-
.option('--limit <n>', 'page size')
|
|
444
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
445
|
-
.action(function (opts: { search?: string; limit?: string; pageToken?: string }) {
|
|
446
|
-
return withHandler(async () => {
|
|
447
|
-
const { runtimeConfig } = getContext(this);
|
|
448
|
-
const quinn = createClient(runtimeConfig);
|
|
449
|
-
print(
|
|
450
|
-
await quinn.members.listManagers({
|
|
451
|
-
search: opts.search,
|
|
452
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
453
|
-
token: opts.pageToken,
|
|
454
|
-
})
|
|
455
|
-
);
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
membersCmd
|
|
460
|
-
.command('get')
|
|
461
|
-
.description('get one member by ID/email, or many members by comma-separated values')
|
|
462
|
-
.argument('<memberIdOrEmail>')
|
|
463
|
-
.action(function (memberIdOrEmail: string) {
|
|
464
|
-
return withHandler(async () => {
|
|
465
|
-
const { runtimeConfig } = getContext(this);
|
|
466
|
-
const quinn = createClient(runtimeConfig);
|
|
467
|
-
const values = asCsv(memberIdOrEmail);
|
|
468
|
-
if (values.length <= 1) {
|
|
469
|
-
print(await quinn.members.get(memberIdOrEmail));
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const ids: string[] = [];
|
|
474
|
-
const emails: string[] = [];
|
|
475
|
-
for (const value of values) {
|
|
476
|
-
if (value.includes('@')) {
|
|
477
|
-
emails.push(value);
|
|
478
|
-
} else {
|
|
479
|
-
ids.push(value);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
print(
|
|
483
|
-
await quinn.members.batchGet({
|
|
484
|
-
ids: ids.length > 0 ? ids : undefined,
|
|
485
|
-
emails: emails.length > 0 ? emails : undefined,
|
|
486
|
-
})
|
|
487
|
-
);
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
membersCmd.addHelpText(
|
|
491
|
-
'after',
|
|
492
|
-
[
|
|
493
|
-
'',
|
|
494
|
-
'Examples:',
|
|
495
|
-
' quinn members list --privilege owner,admin',
|
|
496
|
-
' quinn members list --manager-uid <uid>',
|
|
497
|
-
' quinn members find alice',
|
|
498
|
-
' quinn members list-managers --search bob',
|
|
499
|
-
' quinn members get <memberId>',
|
|
500
|
-
' quinn members get <memberId1,memberId2,user@example.com>',
|
|
501
|
-
].join('\n')
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
const rolesCmd = program.command('roles').description('role operations');
|
|
505
|
-
rolesCmd.command('list').description('list all roles in the organization').action(function () {
|
|
506
|
-
return withHandler(async () => {
|
|
507
|
-
const { runtimeConfig } = getContext(this);
|
|
508
|
-
const quinn = createClient(runtimeConfig);
|
|
509
|
-
print(await quinn.roles.list());
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
rolesCmd
|
|
514
|
-
.command('get')
|
|
515
|
-
.description('get one role by ID, or many roles by comma-separated values')
|
|
516
|
-
.argument('<roleId>')
|
|
517
|
-
.action(function (roleId: string) {
|
|
518
|
-
return withHandler(async () => {
|
|
519
|
-
const { runtimeConfig } = getContext(this);
|
|
520
|
-
const quinn = createClient(runtimeConfig);
|
|
521
|
-
const values = asCsv(roleId);
|
|
522
|
-
if (values.length <= 1) {
|
|
523
|
-
print(await quinn.roles.get(roleId));
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
print(await quinn.roles.batchGet(values));
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
rolesCmd.addHelpText(
|
|
530
|
-
'after',
|
|
531
|
-
[
|
|
532
|
-
'',
|
|
533
|
-
'Examples:',
|
|
534
|
-
' quinn roles list',
|
|
535
|
-
' quinn roles get <roleId>',
|
|
536
|
-
' quinn roles get <roleId1,roleId2>',
|
|
537
|
-
].join('\n')
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
const levelsCmd = program.command('levels').description('level operations');
|
|
541
|
-
levelsCmd
|
|
542
|
-
.command('list')
|
|
543
|
-
.description('list levels under a role')
|
|
544
|
-
.requiredOption('--role-id <id>', 'role ID to scope the query')
|
|
545
|
-
.option('--limit <n>', 'page size')
|
|
546
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
547
|
-
.action(function (opts: { roleId: string; limit?: string; pageToken?: string }) {
|
|
548
|
-
return withHandler(async () => {
|
|
549
|
-
const { runtimeConfig } = getContext(this);
|
|
550
|
-
const quinn = createClient(runtimeConfig);
|
|
551
|
-
print(
|
|
552
|
-
await quinn.levels.list({
|
|
553
|
-
roleId: opts.roleId,
|
|
554
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
555
|
-
token: opts.pageToken,
|
|
556
|
-
})
|
|
557
|
-
);
|
|
558
|
-
});
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
levelsCmd
|
|
562
|
-
.command('get')
|
|
563
|
-
.description('get one level by ID, or many levels by comma-separated values')
|
|
564
|
-
.argument('<levelId>')
|
|
565
|
-
.action(function (levelId: string) {
|
|
566
|
-
return withHandler(async () => {
|
|
567
|
-
const { runtimeConfig } = getContext(this);
|
|
568
|
-
const quinn = createClient(runtimeConfig);
|
|
569
|
-
const values = asCsv(levelId);
|
|
570
|
-
if (values.length <= 1) {
|
|
571
|
-
print(await quinn.levels.get(levelId));
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
print(await quinn.levels.batchGet(values));
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
levelsCmd.addHelpText(
|
|
578
|
-
'after',
|
|
579
|
-
[
|
|
580
|
-
'',
|
|
581
|
-
'Examples:',
|
|
582
|
-
' quinn levels list --role-id <roleId>',
|
|
583
|
-
' quinn levels get <levelId>',
|
|
584
|
-
' quinn levels get <levelId1,levelId2>',
|
|
585
|
-
].join('\n')
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
const compsCmd = program.command('competencies').description('competency operations');
|
|
589
|
-
compsCmd
|
|
590
|
-
.command('list')
|
|
591
|
-
.description('list competencies scoped by role + level')
|
|
592
|
-
.requiredOption('--role-id <id>', 'role ID')
|
|
593
|
-
.requiredOption('--level-id <id>', 'level ID')
|
|
594
|
-
.option('--search <text>', 'optional keyword filter')
|
|
595
|
-
.option('--limit <n>', 'page size')
|
|
596
|
-
.option('--page-token <token>', 'pagination token from previous page')
|
|
597
|
-
.action(function (opts: { roleId: string; levelId: string; search?: string; limit?: string; pageToken?: string }) {
|
|
598
|
-
return withHandler(async () => {
|
|
599
|
-
const { runtimeConfig } = getContext(this);
|
|
600
|
-
const quinn = createClient(runtimeConfig);
|
|
601
|
-
print(
|
|
602
|
-
await quinn.competencies.list({
|
|
603
|
-
roleId: opts.roleId,
|
|
604
|
-
levelId: opts.levelId,
|
|
605
|
-
search: opts.search,
|
|
606
|
-
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
607
|
-
token: opts.pageToken,
|
|
608
|
-
})
|
|
609
|
-
);
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
compsCmd
|
|
614
|
-
.command('get')
|
|
615
|
-
.description('get one competency by ID, or many competencies by comma-separated values')
|
|
616
|
-
.argument('<competencyId>')
|
|
617
|
-
.action(function (competencyId: string) {
|
|
618
|
-
return withHandler(async () => {
|
|
619
|
-
const { runtimeConfig } = getContext(this);
|
|
620
|
-
const quinn = createClient(runtimeConfig);
|
|
621
|
-
const values = asCsv(competencyId);
|
|
622
|
-
if (values.length <= 1) {
|
|
623
|
-
print(await quinn.competencies.get(competencyId));
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
print(await quinn.competencies.batchGet(values));
|
|
627
|
-
});
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
compsCmd
|
|
631
|
-
.command('courses')
|
|
632
|
-
.description('list courses under a competency')
|
|
633
|
-
.argument('<competencyId>')
|
|
634
|
-
.action(function (competencyId: string) {
|
|
635
|
-
return withHandler(async () => {
|
|
636
|
-
const { runtimeConfig } = getContext(this);
|
|
637
|
-
const quinn = createClient(runtimeConfig);
|
|
638
|
-
print(await quinn.competencies.listCourses(competencyId));
|
|
639
|
-
});
|
|
640
|
-
});
|
|
641
|
-
compsCmd.addHelpText(
|
|
642
|
-
'after',
|
|
643
|
-
[
|
|
644
|
-
'',
|
|
645
|
-
'Examples:',
|
|
646
|
-
' quinn competencies list --role-id <roleId> --level-id <levelId>',
|
|
647
|
-
' quinn competencies get <competencyId>',
|
|
648
|
-
' quinn competencies get <id1,id2>',
|
|
649
|
-
' quinn competencies courses <competencyId>',
|
|
650
|
-
].join('\n')
|
|
651
|
-
);
|
|
652
|
-
|
|
653
|
-
const endorseCmd = program.command('endorsements').description('endorsement operations');
|
|
654
|
-
endorseCmd
|
|
655
|
-
.command('get')
|
|
656
|
-
.description('get one endorsement by ID')
|
|
657
|
-
.argument('<endorsementId>')
|
|
658
|
-
.action(function (endorsementId: string) {
|
|
659
|
-
return withHandler(async () => {
|
|
660
|
-
const { runtimeConfig } = getContext(this);
|
|
661
|
-
const quinn = createClient(runtimeConfig);
|
|
662
|
-
print(await quinn.endorsements.get(endorsementId));
|
|
663
|
-
});
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
endorseCmd
|
|
667
|
-
.command('find')
|
|
668
|
-
.description('find one endorsement by user + competency')
|
|
669
|
-
.requiredOption('--uid <uid>', 'user ID')
|
|
670
|
-
.requiredOption('--competency-id <id>', 'competency ID')
|
|
671
|
-
.action(function (opts: { uid: string; competencyId: string }) {
|
|
672
|
-
return withHandler(async () => {
|
|
673
|
-
const { runtimeConfig } = getContext(this);
|
|
674
|
-
const quinn = createClient(runtimeConfig);
|
|
675
|
-
print(await quinn.endorsements.find(opts.uid, opts.competencyId));
|
|
676
|
-
});
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
endorseCmd
|
|
680
|
-
.command('list')
|
|
681
|
-
.description('list endorsements by user IDs and competency IDs')
|
|
682
|
-
.requiredOption('--uids <uids>', 'comma-separated user IDs')
|
|
683
|
-
.requiredOption('--competency-ids <ids>', 'comma-separated competency IDs')
|
|
684
|
-
.action(function (opts: { uids: string; competencyIds: string }) {
|
|
685
|
-
return withHandler(async () => {
|
|
686
|
-
const { runtimeConfig } = getContext(this);
|
|
687
|
-
const quinn = createClient(runtimeConfig);
|
|
688
|
-
print(
|
|
689
|
-
await quinn.endorsements.list({
|
|
690
|
-
uids: asCsv(opts.uids),
|
|
691
|
-
competencyIds: asCsv(opts.competencyIds),
|
|
692
|
-
})
|
|
693
|
-
);
|
|
329
|
+
const item = await quinn.organizations.current();
|
|
330
|
+
print({ ok: true, item });
|
|
694
331
|
});
|
|
695
332
|
});
|
|
696
|
-
endorseCmd.addHelpText(
|
|
697
|
-
'after',
|
|
698
|
-
[
|
|
699
|
-
'',
|
|
700
|
-
'Examples:',
|
|
701
|
-
' quinn endorsements get <endorsementId>',
|
|
702
|
-
' quinn endorsements find --uid <uid> --competency-id <competencyId>',
|
|
703
|
-
' quinn endorsements list --uids <u1,u2> --competency-ids <c1,c2>',
|
|
704
|
-
].join('\n')
|
|
705
|
-
);
|
|
706
333
|
|
|
707
334
|
program.parseAsync(process.argv).catch((error: unknown) => {
|
|
708
335
|
const message = error instanceof Error ? error.message : String(error);
|