@pitcher/cli-scripts 2.5.0 → 2.6.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/README.md +88 -0
- package/dist/index.js +10 -0
- package/dist/src/commands/groups.d.ts +35 -0
- package/dist/src/commands/groups.js +126 -0
- package/dist/src/commands/invitations.d.ts +18 -0
- package/dist/src/commands/invitations.js +49 -0
- package/dist/src/commands/memberships.d.ts +26 -0
- package/dist/src/commands/memberships.js +97 -0
- package/dist/src/commands/roles.d.ts +11 -0
- package/dist/src/commands/roles.js +38 -0
- package/dist/src/commands/users.d.ts +23 -0
- package/dist/src/commands/users.js +64 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -306,3 +306,91 @@ p analytics org --api-key myapikey
|
|
|
306
306
|
# Instance rollups (instance-admin or org-admin key required)
|
|
307
307
|
p analytics instance --instance-id 01... --api-key myapikey
|
|
308
308
|
```
|
|
309
|
+
|
|
310
|
+
## To run the `users` command:
|
|
311
|
+
|
|
312
|
+
Read users in the org (read-only). Org scope comes from the API key via the proxy `x-org-id` header — there is no `--org-id` flag.
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# List users in the org (paginated)
|
|
316
|
+
p users list --api-key myapikey
|
|
317
|
+
|
|
318
|
+
# Filter by instance membership, roles, active status, or content-distribution group
|
|
319
|
+
p users list --instance-id 01... --api-key myapikey
|
|
320
|
+
p users list --instance-id 01... --instance-role editor --api-key myapikey
|
|
321
|
+
p users list --role admin --api-key myapikey
|
|
322
|
+
p users list --is-active --api-key myapikey
|
|
323
|
+
p users list --cdg 01GROUPID... --api-key myapikey
|
|
324
|
+
|
|
325
|
+
# Get a single user by id (user ids are integers, not ULIDs)
|
|
326
|
+
p users get --id 2446 --api-key myapikey
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
List filters: `--instance-id`, `--instance-role` (server-side requires `--instance-id`), `--role`, `--is-active`, `--cdg`, plus `--page`/`--page-size` (max 500). **ID shapes:** user ids are **integers**; instance and group ids are ULIDs.
|
|
330
|
+
|
|
331
|
+
## To run the `invitations` command:
|
|
332
|
+
|
|
333
|
+
Read pending user invitations (read-only).
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
# List pending invitations (paginated)
|
|
337
|
+
p invitations list --api-key myapikey
|
|
338
|
+
|
|
339
|
+
# Get a single invitation by id (invitation ids are 'uinv_...' strings, not ULIDs)
|
|
340
|
+
p invitations get --id uinv_0yKQ41LLqOI2kn4E --api-key myapikey
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## To run the `memberships` command:
|
|
344
|
+
|
|
345
|
+
Read instance memberships, add members, and change their role.
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# List memberships (filter by instance or user; user ids are integers)
|
|
349
|
+
p memberships list --instance-id 01... --api-key myapikey
|
|
350
|
+
p memberships list --user-id 2446 --api-key myapikey
|
|
351
|
+
|
|
352
|
+
# Add a user to an instance (always created as 'member')
|
|
353
|
+
p memberships add --instance-id 01... --user-id 2446 --api-key myapikey
|
|
354
|
+
|
|
355
|
+
# Change a membership's role (org-admin key required; membership ids are integers)
|
|
356
|
+
p memberships set-role --membership-id 3537 --role editor --api-key myapikey
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
`add` always creates the membership as `member`; use `set-role` to change it. `set-role` needs an **org-admin** key (an instance-admin key can `add` but not `set-role`). `--membership-id` is the **integer** membership row id from `memberships list` (not a user id). `--role` is the **short** role name (e.g. `editor`) discovered via `p roles list` — the prefixed key (`instance:editor`) is rejected. `--scope` defaults to `instance`; `--scope organization` targets the **organization** membership row instead — note its row id is NOT discoverable via this CLI today (`memberships list` returns instance memberships only), so use it only when you already have the org-membership id from another source.
|
|
360
|
+
|
|
361
|
+
## To run the `groups` command:
|
|
362
|
+
|
|
363
|
+
Read/create content-distribution groups and manage their members.
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
# List groups (filter by instance)
|
|
367
|
+
p groups list --instance-id 01... --api-key myapikey
|
|
368
|
+
|
|
369
|
+
# Get a group by id
|
|
370
|
+
p groups get --id 01GROUPID... --api-key myapikey
|
|
371
|
+
|
|
372
|
+
# Create a group (name min 3 chars)
|
|
373
|
+
p groups create --instance-id 01... --name "West Region" --api-key myapikey
|
|
374
|
+
|
|
375
|
+
# Add a user to a group (user ids are integers)
|
|
376
|
+
p groups add-user --group-id 01GROUPID... --user-id 2446 --api-key myapikey
|
|
377
|
+
|
|
378
|
+
# List a group's members
|
|
379
|
+
p groups list-users --group-id 01GROUPID... --api-key myapikey
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`add-user` is rejected on auto-login-managed groups (their membership is derived, not manually editable).
|
|
383
|
+
|
|
384
|
+
## To run the `roles` command:
|
|
385
|
+
|
|
386
|
+
Read the org's role set (6 system roles + any custom roles) — the discovery aid for `p memberships set-role`.
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
# List the org's roles
|
|
390
|
+
p roles list --api-key myapikey
|
|
391
|
+
|
|
392
|
+
# Filter by scope
|
|
393
|
+
p roles list --scope instance --api-key myapikey
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`list` returns a `{ items: [...] }` object; each item's `role_key` is **prefixed** (`org:admin`, `instance:editor`). Pass the **short** name (e.g. `editor`) to `set-role`, not the prefixed key. Like `set-role`, `roles list` currently needs an **org-admin** key: while the granular-permissions rollout is off (the production default), the endpoint 403s non-org-admin callers.
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,11 @@ import { createAnnouncementsCommand } from './src/commands/announcements.js';
|
|
|
18
18
|
import { createDsrLandingCommand } from './src/commands/dsrLanding.js';
|
|
19
19
|
import { createEmailTemplateCommand } from './src/commands/emailTemplate.js';
|
|
20
20
|
import { createThemesCommand } from './src/commands/themes.js';
|
|
21
|
+
import { createUsersCommand } from './src/commands/users.js';
|
|
22
|
+
import { createInvitationsCommand } from './src/commands/invitations.js';
|
|
23
|
+
import { createMembershipsCommand } from './src/commands/memberships.js';
|
|
24
|
+
import { createGroupsCommand } from './src/commands/groups.js';
|
|
25
|
+
import { createRolesCommand } from './src/commands/roles.js';
|
|
21
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
22
27
|
const __dirname = dirname(__filename);
|
|
23
28
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -41,6 +46,11 @@ program.addCommand(createAnnouncementsCommand());
|
|
|
41
46
|
program.addCommand(createDsrLandingCommand());
|
|
42
47
|
program.addCommand(createEmailTemplateCommand());
|
|
43
48
|
program.addCommand(createThemesCommand());
|
|
49
|
+
program.addCommand(createUsersCommand());
|
|
50
|
+
program.addCommand(createInvitationsCommand());
|
|
51
|
+
program.addCommand(createMembershipsCommand());
|
|
52
|
+
program.addCommand(createGroupsCommand());
|
|
53
|
+
program.addCommand(createRolesCommand());
|
|
44
54
|
program.addCommand(createOrgCommand());
|
|
45
55
|
program.addCommand(createAnalyticsCommand());
|
|
46
56
|
program.parseAsync(process.argv);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface GroupsDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runGroupsList(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
page?: number;
|
|
11
|
+
pageSize?: number;
|
|
12
|
+
}, deps?: GroupsDeps): Promise<Envelope<{
|
|
13
|
+
results: unknown[];
|
|
14
|
+
pagination: Record<string, unknown>;
|
|
15
|
+
}>>;
|
|
16
|
+
export declare function runGroupsGet(opts: CommonOpts & {
|
|
17
|
+
id?: string;
|
|
18
|
+
}, deps?: GroupsDeps): Promise<Envelope<unknown>>;
|
|
19
|
+
export declare function runGroupsCreate(opts: CommonOpts & {
|
|
20
|
+
instanceId?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
}, deps?: GroupsDeps): Promise<Envelope<unknown>>;
|
|
23
|
+
export declare function runGroupsAddUser(opts: CommonOpts & {
|
|
24
|
+
groupId?: string;
|
|
25
|
+
userId?: string;
|
|
26
|
+
}, deps?: GroupsDeps): Promise<Envelope<unknown>>;
|
|
27
|
+
export declare function runGroupsListUsers(opts: CommonOpts & {
|
|
28
|
+
groupId?: string;
|
|
29
|
+
page?: number;
|
|
30
|
+
pageSize?: number;
|
|
31
|
+
}, deps?: GroupsDeps): Promise<Envelope<{
|
|
32
|
+
results: unknown[];
|
|
33
|
+
pagination: Record<string, unknown>;
|
|
34
|
+
}>>;
|
|
35
|
+
export declare function createGroupsCommand(): Command;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
7
|
+
import { assertIntFlags } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
|
+
const GROUPS = '/api/v1/content-distribution-groups/';
|
|
10
|
+
const GROUP_MEMBERS = '/api/v1/content-distribution-group-memberships/';
|
|
11
|
+
export async function runGroupsList(opts, deps = {}) {
|
|
12
|
+
try {
|
|
13
|
+
assertIntFlags(opts);
|
|
14
|
+
const client = clientFor(opts, deps);
|
|
15
|
+
const params = { ...listPageParams(opts) };
|
|
16
|
+
if (opts.instanceId)
|
|
17
|
+
params.instance_id = opts.instanceId;
|
|
18
|
+
const res = await client.get(GROUPS, { params });
|
|
19
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
return fail(toCliError(e));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function runGroupsGet(opts, deps = {}) {
|
|
26
|
+
try {
|
|
27
|
+
if (!opts.id)
|
|
28
|
+
throw createCliError('VALIDATION', '--id is required for groups get');
|
|
29
|
+
const client = clientFor(opts, deps);
|
|
30
|
+
const res = await client.get(`${GROUPS}${encodeURIComponent(opts.id)}`);
|
|
31
|
+
return ok(res.data);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return fail(toCliError(e));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function runGroupsCreate(opts, deps = {}) {
|
|
38
|
+
try {
|
|
39
|
+
if (!opts.instanceId)
|
|
40
|
+
throw createCliError('VALIDATION', '--instance-id is required for groups create');
|
|
41
|
+
if (!opts.name)
|
|
42
|
+
throw createCliError('VALIDATION', '--name is required for groups create');
|
|
43
|
+
const client = clientFor(opts, deps);
|
|
44
|
+
const res = await client.post(GROUPS, { instance_id: opts.instanceId, name: opts.name });
|
|
45
|
+
return ok(res.data);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return fail(toCliError(e));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function runGroupsAddUser(opts, deps = {}) {
|
|
52
|
+
try {
|
|
53
|
+
if (!opts.groupId)
|
|
54
|
+
throw createCliError('VALIDATION', '--group-id is required for groups add-user');
|
|
55
|
+
if (!opts.userId)
|
|
56
|
+
throw createCliError('VALIDATION', '--user-id is required for groups add-user');
|
|
57
|
+
const client = clientFor(opts, deps);
|
|
58
|
+
const res = await client.post(GROUP_MEMBERS, {
|
|
59
|
+
content_distribution_group_id: opts.groupId,
|
|
60
|
+
user_id: opts.userId,
|
|
61
|
+
});
|
|
62
|
+
return ok(res.data);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return fail(toCliError(e));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function runGroupsListUsers(opts, deps = {}) {
|
|
69
|
+
try {
|
|
70
|
+
if (!opts.groupId)
|
|
71
|
+
throw createCliError('VALIDATION', '--group-id is required for groups list-users');
|
|
72
|
+
assertIntFlags(opts);
|
|
73
|
+
const client = clientFor(opts, deps);
|
|
74
|
+
const params = { ...listPageParams(opts) };
|
|
75
|
+
// The list filterset is content_distribution_group_id (NOT the bare relation
|
|
76
|
+
// name) — an unknown param is silently ignored by DjangoFilterBackend and the
|
|
77
|
+
// endpoint returns every visible membership in the org.
|
|
78
|
+
params.content_distribution_group_id = opts.groupId;
|
|
79
|
+
const res = await client.get(GROUP_MEMBERS, { params });
|
|
80
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
return fail(toCliError(e));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function createGroupsCommand() {
|
|
87
|
+
const cmd = new Command('groups').description('read/create content-distribution groups and manage their members');
|
|
88
|
+
withAuthFlags(cmd.command('list'))
|
|
89
|
+
.option('--instance-id <id>', 'Filter by instance')
|
|
90
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
91
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
92
|
+
.action(async (options, command) => {
|
|
93
|
+
const globals = command.optsWithGlobals();
|
|
94
|
+
emitEnvelope(await runGroupsList({ ...options, quiet: globals.quiet }), globals);
|
|
95
|
+
});
|
|
96
|
+
withAuthFlags(cmd.command('get'))
|
|
97
|
+
.option('--id <id>', 'Group ID')
|
|
98
|
+
.action(async (options, command) => {
|
|
99
|
+
const globals = command.optsWithGlobals();
|
|
100
|
+
emitEnvelope(await runGroupsGet({ ...options, quiet: globals.quiet }), globals);
|
|
101
|
+
});
|
|
102
|
+
withAuthFlags(cmd.command('create'))
|
|
103
|
+
.option('--instance-id <id>', 'Instance ID (required)')
|
|
104
|
+
.option('--name <name>', 'Group name (required, min 3 chars)')
|
|
105
|
+
.action(async (options, command) => {
|
|
106
|
+
const globals = command.optsWithGlobals();
|
|
107
|
+
emitEnvelope(await runGroupsCreate({ ...options, quiet: globals.quiet }), globals);
|
|
108
|
+
});
|
|
109
|
+
withAuthFlags(cmd.command('add-user'))
|
|
110
|
+
.description('add a user to a group (rejected on auto-login-managed groups)')
|
|
111
|
+
.option('--group-id <id>', 'Group ID (required)')
|
|
112
|
+
.option('--user-id <id>', 'User ID (integer, required)')
|
|
113
|
+
.action(async (options, command) => {
|
|
114
|
+
const globals = command.optsWithGlobals();
|
|
115
|
+
emitEnvelope(await runGroupsAddUser({ ...options, quiet: globals.quiet }), globals);
|
|
116
|
+
});
|
|
117
|
+
withAuthFlags(cmd.command('list-users'))
|
|
118
|
+
.option('--group-id <id>', 'Group whose members to list (required)')
|
|
119
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
120
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
121
|
+
.action(async (options, command) => {
|
|
122
|
+
const globals = command.optsWithGlobals();
|
|
123
|
+
emitEnvelope(await runGroupsListUsers({ ...options, quiet: globals.quiet }), globals);
|
|
124
|
+
});
|
|
125
|
+
return cmd;
|
|
126
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface InvitationsDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runInvitationsList(opts: CommonOpts & {
|
|
9
|
+
page?: number;
|
|
10
|
+
pageSize?: number;
|
|
11
|
+
}, deps?: InvitationsDeps): Promise<Envelope<{
|
|
12
|
+
results: unknown[];
|
|
13
|
+
pagination: Record<string, unknown>;
|
|
14
|
+
}>>;
|
|
15
|
+
export declare function runInvitationsGet(opts: CommonOpts & {
|
|
16
|
+
id?: string;
|
|
17
|
+
}, deps?: InvitationsDeps): Promise<Envelope<unknown>>;
|
|
18
|
+
export declare function createInvitationsCommand(): Command;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
7
|
+
import { assertIntFlags } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
|
+
export async function runInvitationsList(opts, deps = {}) {
|
|
10
|
+
try {
|
|
11
|
+
assertIntFlags(opts);
|
|
12
|
+
const client = clientFor(opts, deps);
|
|
13
|
+
const params = { ...listPageParams(opts) };
|
|
14
|
+
const res = await client.get('/api/v1/invitations/', { params });
|
|
15
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
return fail(toCliError(e));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function runInvitationsGet(opts, deps = {}) {
|
|
22
|
+
try {
|
|
23
|
+
if (!opts.id)
|
|
24
|
+
throw createCliError('VALIDATION', '--id is required for invitations get');
|
|
25
|
+
const client = clientFor(opts, deps);
|
|
26
|
+
const res = await client.get(`/api/v1/invitations/${encodeURIComponent(opts.id)}`);
|
|
27
|
+
return ok(res.data);
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
return fail(toCliError(e));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function createInvitationsCommand() {
|
|
34
|
+
const cmd = new Command('invitations').description('read pending user invitations (read-only)');
|
|
35
|
+
withAuthFlags(cmd.command('list'))
|
|
36
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
37
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
38
|
+
.action(async (options, command) => {
|
|
39
|
+
const globals = command.optsWithGlobals();
|
|
40
|
+
emitEnvelope(await runInvitationsList({ ...options, quiet: globals.quiet }), globals);
|
|
41
|
+
});
|
|
42
|
+
withAuthFlags(cmd.command('get'))
|
|
43
|
+
.option('--id <id>', 'Invitation ID')
|
|
44
|
+
.action(async (options, command) => {
|
|
45
|
+
const globals = command.optsWithGlobals();
|
|
46
|
+
emitEnvelope(await runInvitationsGet({ ...options, quiet: globals.quiet }), globals);
|
|
47
|
+
});
|
|
48
|
+
return cmd;
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface MembershipsDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runMembershipsList(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
userId?: string;
|
|
11
|
+
page?: number;
|
|
12
|
+
pageSize?: number;
|
|
13
|
+
}, deps?: MembershipsDeps): Promise<Envelope<{
|
|
14
|
+
results: unknown[];
|
|
15
|
+
pagination: Record<string, unknown>;
|
|
16
|
+
}>>;
|
|
17
|
+
export declare function runMembershipsAdd(opts: CommonOpts & {
|
|
18
|
+
instanceId?: string;
|
|
19
|
+
userId?: string;
|
|
20
|
+
}, deps?: MembershipsDeps): Promise<Envelope<unknown>>;
|
|
21
|
+
export declare function runMembershipsSetRole(opts: CommonOpts & {
|
|
22
|
+
membershipId?: string;
|
|
23
|
+
role?: string;
|
|
24
|
+
scope?: string;
|
|
25
|
+
}, deps?: MembershipsDeps): Promise<Envelope<unknown>>;
|
|
26
|
+
export declare function createMembershipsCommand(): Command;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
7
|
+
import { assertIntFlags } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
|
+
export async function runMembershipsList(opts, deps = {}) {
|
|
10
|
+
try {
|
|
11
|
+
assertIntFlags(opts);
|
|
12
|
+
const client = clientFor(opts, deps);
|
|
13
|
+
const params = { ...listPageParams(opts) };
|
|
14
|
+
if (opts.instanceId)
|
|
15
|
+
params.instance_id = opts.instanceId;
|
|
16
|
+
if (opts.userId)
|
|
17
|
+
params.user_id = opts.userId;
|
|
18
|
+
const res = await client.get('/api/v1/instance-memberships/', { params });
|
|
19
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
return fail(toCliError(e));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function runMembershipsAdd(opts, deps = {}) {
|
|
26
|
+
try {
|
|
27
|
+
if (!opts.instanceId)
|
|
28
|
+
throw createCliError('VALIDATION', '--instance-id is required for memberships add');
|
|
29
|
+
if (!opts.userId)
|
|
30
|
+
throw createCliError('VALIDATION', '--user-id is required for memberships add');
|
|
31
|
+
const client = clientFor(opts, deps);
|
|
32
|
+
// Role is hardcoded to 'member' — never send a caller-supplied role to
|
|
33
|
+
// Django's unvalidated create endpoint. Role changes go through set-role.
|
|
34
|
+
const body = { instance_id: opts.instanceId, user_id: opts.userId, role: 'member' };
|
|
35
|
+
const res = await client.post('/api/v1/instance-memberships/', body);
|
|
36
|
+
return ok(res.data);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
return fail(toCliError(e));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function runMembershipsSetRole(opts, deps = {}) {
|
|
43
|
+
try {
|
|
44
|
+
if (!opts.membershipId)
|
|
45
|
+
throw createCliError('VALIDATION', '--membership-id is required for memberships set-role');
|
|
46
|
+
if (!opts.role)
|
|
47
|
+
throw createCliError('VALIDATION', '--role is required for memberships set-role');
|
|
48
|
+
const client = clientFor(opts, deps);
|
|
49
|
+
const scope = opts.scope ?? 'instance';
|
|
50
|
+
// Role passed through unvalidated — next-core validates against the org's
|
|
51
|
+
// real role set (system + custom) and 400s on anything bogus.
|
|
52
|
+
const res = await client.patch(`/core/api/protected/roles/assignments/${encodeURIComponent(opts.membershipId)}`, { scope, role: opts.role });
|
|
53
|
+
return ok(res.data);
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
return fail(toCliError(e));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const SET_ROLE_HELP = `
|
|
60
|
+
Changes a membership's role via the validating next-core endpoint. Requires an
|
|
61
|
+
ORG-admin key (an instance-admin key can 'add' but not 'set-role').
|
|
62
|
+
--membership-id the INTEGER membership ROW id (from 'memberships list'), not a user id
|
|
63
|
+
--role SHORT role name (e.g. 'editor'); run 'p roles list' to discover.
|
|
64
|
+
Do NOT pass the prefixed key ('instance:editor') — it is rejected.
|
|
65
|
+
--scope 'instance' (default) or 'organization'. Organization scope targets
|
|
66
|
+
the ORG membership row, whose id is NOT discoverable via this CLI
|
|
67
|
+
('memberships list' returns instance memberships only).`;
|
|
68
|
+
export function createMembershipsCommand() {
|
|
69
|
+
const cmd = new Command('memberships').description('read instance memberships, add members, and set roles');
|
|
70
|
+
withAuthFlags(cmd.command('list'))
|
|
71
|
+
.option('--instance-id <id>', 'Filter by instance')
|
|
72
|
+
.option('--user-id <id>', 'Filter by user (integer id)')
|
|
73
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
74
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
75
|
+
.action(async (options, command) => {
|
|
76
|
+
const globals = command.optsWithGlobals();
|
|
77
|
+
emitEnvelope(await runMembershipsList({ ...options, quiet: globals.quiet }), globals);
|
|
78
|
+
});
|
|
79
|
+
withAuthFlags(cmd.command('add'))
|
|
80
|
+
.description("add a user to an instance as 'member' (use set-role to change the role)")
|
|
81
|
+
.option('--instance-id <id>', 'Instance ID (required)')
|
|
82
|
+
.option('--user-id <id>', 'User ID (integer, required)')
|
|
83
|
+
.action(async (options, command) => {
|
|
84
|
+
const globals = command.optsWithGlobals();
|
|
85
|
+
emitEnvelope(await runMembershipsAdd({ ...options, quiet: globals.quiet }), globals);
|
|
86
|
+
});
|
|
87
|
+
withAuthFlags(cmd.command('set-role'))
|
|
88
|
+
.option('--membership-id <id>', 'Membership row ID (integer, required)')
|
|
89
|
+
.option('--role <role>', 'Short role name, e.g. editor (required)')
|
|
90
|
+
.option('--scope <scope>', 'instance | organization', 'instance')
|
|
91
|
+
.addHelpText('after', SET_ROLE_HELP)
|
|
92
|
+
.action(async (options, command) => {
|
|
93
|
+
const globals = command.optsWithGlobals();
|
|
94
|
+
emitEnvelope(await runMembershipsSetRole({ ...options, quiet: globals.quiet }), globals);
|
|
95
|
+
});
|
|
96
|
+
return cmd;
|
|
97
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface RolesDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runRolesList(opts: CommonOpts & {
|
|
9
|
+
scope?: string;
|
|
10
|
+
}, deps?: RolesDeps): Promise<Envelope<unknown>>;
|
|
11
|
+
export declare function createRolesCommand(): Command;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { emitEnvelope } from '../core/output.js';
|
|
5
|
+
import { withAuthFlags } from './withAuth.js';
|
|
6
|
+
const ROLES_ENDPOINT = '/core/api/protected/roles/configs';
|
|
7
|
+
export async function runRolesList(opts, deps = {}) {
|
|
8
|
+
try {
|
|
9
|
+
const client = clientFor(opts, deps);
|
|
10
|
+
const params = {};
|
|
11
|
+
if (opts.scope)
|
|
12
|
+
params.scope = opts.scope;
|
|
13
|
+
const res = await client.get(ROLES_ENDPOINT, { params });
|
|
14
|
+
return ok(res.data);
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
return fail(toCliError(e));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const LIST_HELP = `
|
|
21
|
+
Lists the org's role set (6 system roles + any custom roles) — the discovery
|
|
22
|
+
aid for 'p memberships set-role'. Response is a { items: [...] } object; each
|
|
23
|
+
item's role_key is PREFIXED (org:admin, instance:editor). Pass the SHORT name
|
|
24
|
+
(e.g. 'editor') to set-role, NOT the prefixed key.
|
|
25
|
+
|
|
26
|
+
Needs an ORG-admin key: while the granular-permissions rollout is off (the
|
|
27
|
+
production default), the endpoint 403s non-org-admin callers.`;
|
|
28
|
+
export function createRolesCommand() {
|
|
29
|
+
const cmd = new Command('roles').description('read the org role set (system + custom) for set-role');
|
|
30
|
+
withAuthFlags(cmd.command('list'))
|
|
31
|
+
.option('--scope <scope>', 'Filter by scope: instance | organization')
|
|
32
|
+
.addHelpText('after', LIST_HELP)
|
|
33
|
+
.action(async (options, command) => {
|
|
34
|
+
const globals = command.optsWithGlobals();
|
|
35
|
+
emitEnvelope(await runRolesList({ ...options, quiet: globals.quiet }), globals);
|
|
36
|
+
});
|
|
37
|
+
return cmd;
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface UsersDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runUsersList(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
instanceRole?: string;
|
|
11
|
+
role?: string;
|
|
12
|
+
isActive?: boolean;
|
|
13
|
+
cdg?: string;
|
|
14
|
+
page?: number;
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
}, deps?: UsersDeps): Promise<Envelope<{
|
|
17
|
+
results: unknown[];
|
|
18
|
+
pagination: Record<string, unknown>;
|
|
19
|
+
}>>;
|
|
20
|
+
export declare function runUsersGet(opts: CommonOpts & {
|
|
21
|
+
id?: string;
|
|
22
|
+
}, deps?: UsersDeps): Promise<Envelope<unknown>>;
|
|
23
|
+
export declare function createUsersCommand(): Command;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
7
|
+
import { assertIntFlags } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
|
+
export async function runUsersList(opts, deps = {}) {
|
|
10
|
+
try {
|
|
11
|
+
assertIntFlags(opts);
|
|
12
|
+
const client = clientFor(opts, deps);
|
|
13
|
+
const params = { ...listPageParams(opts) };
|
|
14
|
+
if (opts.instanceId)
|
|
15
|
+
params.instance_id = opts.instanceId;
|
|
16
|
+
if (opts.instanceRole)
|
|
17
|
+
params.instance_role = opts.instanceRole;
|
|
18
|
+
if (opts.role)
|
|
19
|
+
params.role = opts.role;
|
|
20
|
+
if (opts.isActive)
|
|
21
|
+
params.is_active = true;
|
|
22
|
+
if (opts.cdg)
|
|
23
|
+
params.content_distribution_groups = opts.cdg;
|
|
24
|
+
const res = await client.get('/api/v1/users/', { params });
|
|
25
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
return fail(toCliError(e));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function runUsersGet(opts, deps = {}) {
|
|
32
|
+
try {
|
|
33
|
+
if (!opts.id)
|
|
34
|
+
throw createCliError('VALIDATION', '--id is required for users get');
|
|
35
|
+
const client = clientFor(opts, deps);
|
|
36
|
+
const res = await client.get(`/api/v1/users/${encodeURIComponent(opts.id)}`);
|
|
37
|
+
return ok(res.data);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
return fail(toCliError(e));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function createUsersCommand() {
|
|
44
|
+
const cmd = new Command('users').description('read users in the org (read-only)');
|
|
45
|
+
withAuthFlags(cmd.command('list'))
|
|
46
|
+
.option('--instance-id <id>', 'Filter to users with a membership in this instance')
|
|
47
|
+
.option('--instance-role <role>', 'Filter by instance role (requires --instance-id server-side)')
|
|
48
|
+
.option('--role <role>', 'Filter by org role')
|
|
49
|
+
.option('--is-active', 'Only users with an active membership')
|
|
50
|
+
.option('--cdg <id>', 'Filter to members of this content-distribution group')
|
|
51
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
52
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
53
|
+
.action(async (options, command) => {
|
|
54
|
+
const globals = command.optsWithGlobals();
|
|
55
|
+
emitEnvelope(await runUsersList({ ...options, quiet: globals.quiet }), globals);
|
|
56
|
+
});
|
|
57
|
+
withAuthFlags(cmd.command('get'))
|
|
58
|
+
.option('--id <id>', 'User ID (integer)')
|
|
59
|
+
.action(async (options, command) => {
|
|
60
|
+
const globals = command.optsWithGlobals();
|
|
61
|
+
emitEnvelope(await runUsersGet({ ...options, quiet: globals.quiet }), globals);
|
|
62
|
+
});
|
|
63
|
+
return cmd;
|
|
64
|
+
}
|