@pitcher/cli-scripts 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -0
- package/dist/index.js +12 -0
- package/dist/src/commands/groups.d.ts +35 -0
- package/dist/src/commands/groups.js +125 -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/themes.d.ts +17 -0
- package/dist/src/commands/themes.js +82 -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
|
@@ -270,6 +270,23 @@ p announcements scaffold --out ./announcement.json --api-key myapikey
|
|
|
270
270
|
|
|
271
271
|
`scaffold` returns the starter JSON in the envelope `data`; pass `--out <path>` to write it to a file instead.
|
|
272
272
|
|
|
273
|
+
## To run the `themes` command:
|
|
274
|
+
|
|
275
|
+
Read canvas/branding themes (colors via `settings`, fonts via `typography`, plus `assets`/`logo`) to reference when authoring email/DSR/canvas content. Read-only.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# List all themes in the org (returns full theme objects)
|
|
279
|
+
p themes list --api-key myapikey
|
|
280
|
+
|
|
281
|
+
# Narrow to one instance; add per-theme canvas usage and canvas ids
|
|
282
|
+
p themes list --instance-id 01... --with-usage --with-canvas-ids --api-key myapikey
|
|
283
|
+
|
|
284
|
+
# Get one theme by id
|
|
285
|
+
p themes get --id 01THEMEID... --api-key myapikey
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`list` is server-capped (newest-first) with no `--limit`/`--page`; when the cap is hit the envelope carries a truncation warning. `--instance-id` narrows to themes bound to that instance and **excludes** org-level (default) themes, so omit it to see everything usable in the org.
|
|
289
|
+
|
|
273
290
|
## To run the `org` command:
|
|
274
291
|
|
|
275
292
|
Read the organization the API key belongs to.
|
|
@@ -289,3 +306,91 @@ p analytics org --api-key myapikey
|
|
|
289
306
|
# Instance rollups (instance-admin or org-admin key required)
|
|
290
307
|
p analytics instance --instance-id 01... --api-key myapikey
|
|
291
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
|
@@ -17,6 +17,12 @@ import { createAnalyticsCommand } from './src/commands/analytics.js';
|
|
|
17
17
|
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
|
+
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';
|
|
20
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
27
|
const __dirname = dirname(__filename);
|
|
22
28
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -39,6 +45,12 @@ program.addCommand(createTagsCommand());
|
|
|
39
45
|
program.addCommand(createAnnouncementsCommand());
|
|
40
46
|
program.addCommand(createDsrLandingCommand());
|
|
41
47
|
program.addCommand(createEmailTemplateCommand());
|
|
48
|
+
program.addCommand(createThemesCommand());
|
|
49
|
+
program.addCommand(createUsersCommand());
|
|
50
|
+
program.addCommand(createInvitationsCommand());
|
|
51
|
+
program.addCommand(createMembershipsCommand());
|
|
52
|
+
program.addCommand(createGroupsCommand());
|
|
53
|
+
program.addCommand(createRolesCommand());
|
|
42
54
|
program.addCommand(createOrgCommand());
|
|
43
55
|
program.addCommand(createAnalyticsCommand());
|
|
44
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,125 @@
|
|
|
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
|
+
assertIntFlags(opts);
|
|
71
|
+
const client = clientFor(opts, deps);
|
|
72
|
+
const params = { ...listPageParams(opts) };
|
|
73
|
+
// The list filterset is content_distribution_group_id (NOT the bare relation
|
|
74
|
+
// name) — an unknown param is silently ignored by DjangoFilterBackend and the
|
|
75
|
+
// endpoint returns every visible membership in the org.
|
|
76
|
+
if (opts.groupId)
|
|
77
|
+
params.content_distribution_group_id = opts.groupId;
|
|
78
|
+
const res = await client.get(GROUP_MEMBERS, { params });
|
|
79
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
return fail(toCliError(e));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function createGroupsCommand() {
|
|
86
|
+
const cmd = new Command('groups').description('read/create content-distribution groups and manage their members');
|
|
87
|
+
withAuthFlags(cmd.command('list'))
|
|
88
|
+
.option('--instance-id <id>', 'Filter by instance')
|
|
89
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
90
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
91
|
+
.action(async (options, command) => {
|
|
92
|
+
const globals = command.optsWithGlobals();
|
|
93
|
+
emitEnvelope(await runGroupsList({ ...options, quiet: globals.quiet }), globals);
|
|
94
|
+
});
|
|
95
|
+
withAuthFlags(cmd.command('get'))
|
|
96
|
+
.option('--id <id>', 'Group ID')
|
|
97
|
+
.action(async (options, command) => {
|
|
98
|
+
const globals = command.optsWithGlobals();
|
|
99
|
+
emitEnvelope(await runGroupsGet({ ...options, quiet: globals.quiet }), globals);
|
|
100
|
+
});
|
|
101
|
+
withAuthFlags(cmd.command('create'))
|
|
102
|
+
.option('--instance-id <id>', 'Instance ID (required)')
|
|
103
|
+
.option('--name <name>', 'Group name (required, min 3 chars)')
|
|
104
|
+
.action(async (options, command) => {
|
|
105
|
+
const globals = command.optsWithGlobals();
|
|
106
|
+
emitEnvelope(await runGroupsCreate({ ...options, quiet: globals.quiet }), globals);
|
|
107
|
+
});
|
|
108
|
+
withAuthFlags(cmd.command('add-user'))
|
|
109
|
+
.description('add a user to a group (rejected on auto-login-managed groups)')
|
|
110
|
+
.option('--group-id <id>', 'Group ID (required)')
|
|
111
|
+
.option('--user-id <id>', 'User ID (integer, required)')
|
|
112
|
+
.action(async (options, command) => {
|
|
113
|
+
const globals = command.optsWithGlobals();
|
|
114
|
+
emitEnvelope(await runGroupsAddUser({ ...options, quiet: globals.quiet }), globals);
|
|
115
|
+
});
|
|
116
|
+
withAuthFlags(cmd.command('list-users'))
|
|
117
|
+
.option('--group-id <id>', 'Group whose members to list')
|
|
118
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
119
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
120
|
+
.action(async (options, command) => {
|
|
121
|
+
const globals = command.optsWithGlobals();
|
|
122
|
+
emitEnvelope(await runGroupsListUsers({ ...options, quiet: globals.quiet }), globals);
|
|
123
|
+
});
|
|
124
|
+
return cmd;
|
|
125
|
+
}
|
|
@@ -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,17 @@
|
|
|
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 declare const THEME_RETRIEVE_LIMIT = 1000;
|
|
6
|
+
export interface ThemesDeps {
|
|
7
|
+
client?: AxiosInstance;
|
|
8
|
+
}
|
|
9
|
+
export declare function runThemesList(opts: CommonOpts & {
|
|
10
|
+
instanceId?: string;
|
|
11
|
+
withUsage?: boolean;
|
|
12
|
+
withCanvasIds?: boolean;
|
|
13
|
+
}, deps?: ThemesDeps): Promise<Envelope<unknown>>;
|
|
14
|
+
export declare function runThemesGet(opts: CommonOpts & {
|
|
15
|
+
id?: string;
|
|
16
|
+
}, deps?: ThemesDeps): Promise<Envelope<unknown>>;
|
|
17
|
+
export declare function createThemesCommand(): Command;
|
|
@@ -0,0 +1,82 @@
|
|
|
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 { withAuthFlags } from './withAuth.js';
|
|
7
|
+
const THEMES_ENDPOINT = '/core/api/protected/themes';
|
|
8
|
+
// Mirror of next-core THEME_RETRIEVE_LIMIT (themes/limits.ts). The list route
|
|
9
|
+
// caps at this many rows (newest-first) with no total/hasMore, so a full page
|
|
10
|
+
// may be silently truncated — we warn when the count equals the cap. Keep this
|
|
11
|
+
// in sync with the server constant: if the server cap changes and this does not,
|
|
12
|
+
// the truncation warning stops firing.
|
|
13
|
+
export const THEME_RETRIEVE_LIMIT = 1000;
|
|
14
|
+
export async function runThemesList(opts, deps = {}) {
|
|
15
|
+
try {
|
|
16
|
+
const client = clientFor(opts, deps);
|
|
17
|
+
const params = {};
|
|
18
|
+
if (opts.instanceId)
|
|
19
|
+
params.instance_id = opts.instanceId;
|
|
20
|
+
// The route compares === 'true' (string); send the literal only when set.
|
|
21
|
+
if (opts.withUsage)
|
|
22
|
+
params.used_in_canvases_count = 'true';
|
|
23
|
+
if (opts.withCanvasIds)
|
|
24
|
+
params.include_canvas_ids = 'true';
|
|
25
|
+
const res = await client.get(THEMES_ENDPOINT, { params });
|
|
26
|
+
const data = res.data;
|
|
27
|
+
if (Array.isArray(data) && data.length === THEME_RETRIEVE_LIMIT) {
|
|
28
|
+
const base = `results may be truncated at the server cap of ${THEME_RETRIEVE_LIMIT}`;
|
|
29
|
+
// Only advise --instance-id when it isn't already narrowing the scope.
|
|
30
|
+
return ok(data, [opts.instanceId ? base : `${base}; narrow with --instance-id`]);
|
|
31
|
+
}
|
|
32
|
+
return ok(data);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
return fail(toCliError(e));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function runThemesGet(opts, deps = {}) {
|
|
39
|
+
try {
|
|
40
|
+
if (!opts.id)
|
|
41
|
+
throw createCliError('VALIDATION', '--id is required for themes get');
|
|
42
|
+
const client = clientFor(opts, deps);
|
|
43
|
+
const res = await client.get(`${THEMES_ENDPOINT}/${encodeURIComponent(opts.id)}`);
|
|
44
|
+
return ok(res.data);
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return fail(toCliError(e));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const LIST_HELP = `
|
|
51
|
+
Themes carry the instance branding you reference when authoring:
|
|
52
|
+
settings colors
|
|
53
|
+
typography fonts
|
|
54
|
+
assets, logo branding assets
|
|
55
|
+
|
|
56
|
+
Notes:
|
|
57
|
+
- Results are server-capped (newest-first); there is no --limit/--page.
|
|
58
|
+
- --instance-id narrows to themes bound to that instance and EXCLUDES
|
|
59
|
+
org-level (default) themes; omit it to see all themes in the org.`;
|
|
60
|
+
export function createThemesCommand() {
|
|
61
|
+
const cmd = new Command('themes').description('read canvas/branding themes (colors, fonts, assets) for authoring');
|
|
62
|
+
withAuthFlags(cmd.command('list'))
|
|
63
|
+
.option('--instance-id <id>', 'Filter to themes bound to this instance (excludes org-level themes)')
|
|
64
|
+
.option('--with-usage', 'Include a per-theme count of canvases using it')
|
|
65
|
+
.option('--with-canvas-ids', 'Include the canvas ids using each theme')
|
|
66
|
+
.addHelpText('after', LIST_HELP)
|
|
67
|
+
.action(async (options, command) => {
|
|
68
|
+
const globals = command.optsWithGlobals();
|
|
69
|
+
emitEnvelope(await runThemesList({ ...options, quiet: globals.quiet }), globals);
|
|
70
|
+
});
|
|
71
|
+
withAuthFlags(cmd.command('get'))
|
|
72
|
+
// --id via .option (not .requiredOption) so a missing id flows through the
|
|
73
|
+
// in-run guard in runThemesGet and returns a VALIDATION envelope (the agent
|
|
74
|
+
// contract: envelope + exit code on stdout), rather than commander's plain
|
|
75
|
+
// parse error + exit 1.
|
|
76
|
+
.option('--id <id>', 'Theme ID')
|
|
77
|
+
.action(async (options, command) => {
|
|
78
|
+
const globals = command.optsWithGlobals();
|
|
79
|
+
emitEnvelope(await runThemesGet({ ...options, quiet: globals.quiet }), globals);
|
|
80
|
+
});
|
|
81
|
+
return cmd;
|
|
82
|
+
}
|
|
@@ -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
|
+
}
|