@knowttl/mg-axi 0.1.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.
Files changed (57) hide show
  1. package/README.md +1138 -0
  2. package/dist/api.js +953 -0
  3. package/dist/app-auth.js +47 -0
  4. package/dist/auth.js +59 -0
  5. package/dist/bin/mg-axi.js +7 -0
  6. package/dist/catalogue.js +2191 -0
  7. package/dist/cli.js +1314 -0
  8. package/dist/docs.js +559 -0
  9. package/dist/doctor.js +75 -0
  10. package/dist/entra-access-reviews.js +425 -0
  11. package/dist/entra-apps.js +374 -0
  12. package/dist/entra-audit-logs.js +381 -0
  13. package/dist/entra-auth-methods.js +296 -0
  14. package/dist/entra-ca-policy-update.js +353 -0
  15. package/dist/entra-certificate-auth.js +384 -0
  16. package/dist/entra-conditional-access.js +317 -0
  17. package/dist/entra-contacts.js +827 -0
  18. package/dist/entra-contracts.js +252 -0
  19. package/dist/entra-custom-security-attributes.js +464 -0
  20. package/dist/entra-data-policy-operations.js +274 -0
  21. package/dist/entra-delegated-admin.js +468 -0
  22. package/dist/entra-deleted-items.js +461 -0
  23. package/dist/entra-directory-objects.js +274 -0
  24. package/dist/entra-directory.js +411 -0
  25. package/dist/entra-domains.js +399 -0
  26. package/dist/entra-entitlement-management.js +645 -0
  27. package/dist/entra-federation-configurations.js +290 -0
  28. package/dist/entra-grants.js +272 -0
  29. package/dist/entra-group-lifecycle.js +349 -0
  30. package/dist/entra-group-member-add.js +278 -0
  31. package/dist/entra-groups.js +336 -0
  32. package/dist/entra-identity-providers.js +297 -0
  33. package/dist/entra-lifecycle-workflows.js +673 -0
  34. package/dist/entra-multi-tenant-organization.js +309 -0
  35. package/dist/entra-on-premises-synchronization.js +231 -0
  36. package/dist/entra-organization.js +398 -0
  37. package/dist/entra-risk-dismiss.js +191 -0
  38. package/dist/entra-risk.js +404 -0
  39. package/dist/entra-roles.js +310 -0
  40. package/dist/entra-subscriptions.js +289 -0
  41. package/dist/entra-tenant-information.js +152 -0
  42. package/dist/entra-terms-of-use.js +380 -0
  43. package/dist/entra-user-revoke-sessions.js +170 -0
  44. package/dist/entra-user-update.js +170 -0
  45. package/dist/entra-users.js +211 -0
  46. package/dist/graph-session.js +1034 -0
  47. package/dist/msal-app-provider.js +42 -0
  48. package/dist/msal-provider.js +82 -0
  49. package/dist/mutations.js +529 -0
  50. package/dist/profiles.js +139 -0
  51. package/dist/setup.js +116 -0
  52. package/dist/version.js +2 -0
  53. package/docs/coverage.md +1028 -0
  54. package/inventory/operations.json +4 -0
  55. package/inventory/schema.json +71 -0
  56. package/package.json +32 -0
  57. package/skills/mg-axi/SKILL.md +293 -0
@@ -0,0 +1,468 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ // EXT-04 delegated-admin subfamily: the read mapping behind
3
+ // `mg-axi entra delegated-admin-customer list/show`,
4
+ // `mg-axi entra delegated-admin-relationship list/show` and the navigation
5
+ // reads `mg-axi entra delegated-admin-relationship
6
+ // list-access-assignments/show-access-assignment/list-operations/show-operation/list-requests/show-request`
7
+ // and `mg-axi entra delegated-admin-customer
8
+ // list-service-management-details/show-service-management-detail`. Operation
9
+ // construction stays beside its command; the shared session owns URLs,
10
+ // credentials, paging, retries and error translation, and the SDK owns TOON
11
+ // rendering. This module only maps flags to session calls and projects rows
12
+ // for compact output.
13
+ //
14
+ // Reviewed against the v1.0 delegatedAdminCustomer list/get, the
15
+ // delegatedAdminRelationship list/get, the access-assignment, operation and
16
+ // request list/get pages, the serviceManagementDetails list page, the
17
+ // delegatedAdminCustomer and delegatedAdminRelationship resource pages and
18
+ // the delegatedAdminAccessAssignment, delegatedAdminRelationshipOperation,
19
+ // delegatedAdminRelationshipRequest and delegatedAdminServiceManagementDetail
20
+ // resource pages on 2026-10-05. The serviceManagementDetail single has no
21
+ // REST page; its route exists alongside the documented list (the PowerShell
22
+ // Get cmdlet takes both the customer and the detail identifiers), so the
23
+ // single runs queryless like the list. Every read takes D/A
24
+ // DelegatedAdminRelationship.Read.All as least privilege; the scope is
25
+ // already in the shared READ_SCOPES allowlist, so no new scope and no
26
+ // deferral is needed here. Delegated callers pass it as --scopes;
27
+ // application callers need it admin-consented on the configured .default
28
+ // audience. None of the reviewed pages states an Entra role or a P1/P2
29
+ // prerequisite for these reads, and personal Microsoft accounts are not
30
+ // supported. Reads run in the partner tenant: delegatedAdminCustomer objects
31
+ // are created by the system when a relationship exists and deleted when none
32
+ // remain, so a non-partner tenant lists zero customers, which is an answer
33
+ // rather than an error. The access-assignment, operation and request lists
34
+ // document $select, $filter, $top and more; $top supports up to 300 objects.
35
+ // --filter passes through as plain $filter with no $count or ConsistencyLevel
36
+ // contract. serviceManagementDetails documents no query parameters, so both
37
+ // service-management-detail reads run queryless and project locally. The
38
+ // $count scalars and the tenantRelationship container root stay scheduled
39
+ // for later EXT-04 subfamilies, as do the multi-tenant-organization reads
40
+ // (shipped separately) and the tenant-lookup functions (shipped as
41
+ // tenant-information show under mg-ext-04e). Beta stays out. No delegated-admin mutation exists in this slice:
42
+ // relationship creation, approval and termination are writes.
43
+ // Every customer property this slice may request or display, matching the
44
+ // reviewed delegatedAdminCustomer resource. Anything else fails before
45
+ // credentials.
46
+ export const KNOWN_DELEGATED_ADMIN_CUSTOMER_FIELDS = [
47
+ "displayName",
48
+ "id",
49
+ "tenantId",
50
+ ];
51
+ // Every relationship property this slice may request or display, matching
52
+ // the reviewed delegatedAdminRelationship resource. The list/get responses
53
+ // may carry a resellerDelegatedAdminRelationship subtype with unreviewed
54
+ // extras (isPartnerConsentPending, indirectProviderTenantId); projection
55
+ // drops them, so subtype rows render on the base set only.
56
+ export const KNOWN_DELEGATED_ADMIN_RELATIONSHIP_FIELDS = [
57
+ "accessDetails",
58
+ "activatedDateTime",
59
+ "autoExtendDuration",
60
+ "createdDateTime",
61
+ "customer",
62
+ "displayName",
63
+ "duration",
64
+ "endDateTime",
65
+ "id",
66
+ "lastModifiedDateTime",
67
+ "status",
68
+ ];
69
+ const KNOWN_CUSTOMERS = new Set(KNOWN_DELEGATED_ADMIN_CUSTOMER_FIELDS);
70
+ const KNOWN_RELATIONSHIPS = new Set(KNOWN_DELEGATED_ADMIN_RELATIONSHIP_FIELDS);
71
+ // Every access-assignment property this slice may request or display,
72
+ // matching the reviewed delegatedAdminAccessAssignment resource. Anything
73
+ // else fails before credentials.
74
+ export const KNOWN_DELEGATED_ADMIN_ACCESS_ASSIGNMENT_FIELDS = [
75
+ "accessContainer",
76
+ "accessDetails",
77
+ "createdDateTime",
78
+ "id",
79
+ "lastModifiedDateTime",
80
+ "status",
81
+ ];
82
+ // Every relationship-operation property this slice may request or display,
83
+ // matching the reviewed delegatedAdminRelationshipOperation resource. The
84
+ // data payload is a JSON-encoded string and truncates like any long text.
85
+ export const KNOWN_DELEGATED_ADMIN_OPERATION_FIELDS = [
86
+ "createdDateTime",
87
+ "data",
88
+ "id",
89
+ "lastModifiedDateTime",
90
+ "operationType",
91
+ "status",
92
+ ];
93
+ // Every relationship-request property this slice may request or display,
94
+ // matching the reviewed delegatedAdminRelationshipRequest resource.
95
+ export const KNOWN_DELEGATED_ADMIN_REQUEST_FIELDS = [
96
+ "action",
97
+ "createdDateTime",
98
+ "id",
99
+ "lastModifiedDateTime",
100
+ "status",
101
+ ];
102
+ // Every service-management-detail property this slice may display, matching
103
+ // the reviewed delegatedAdminServiceManagementDetail resource. Graph
104
+ // documents no query parameters here, so rows always arrive whole and
105
+ // --fields projects them locally.
106
+ export const KNOWN_DELEGATED_ADMIN_SERVICE_MANAGEMENT_DETAIL_FIELDS = [
107
+ "id",
108
+ "serviceManagementUrl",
109
+ "serviceName",
110
+ ];
111
+ const KNOWN_ACCESS_ASSIGNMENTS = new Set(KNOWN_DELEGATED_ADMIN_ACCESS_ASSIGNMENT_FIELDS);
112
+ const KNOWN_OPERATIONS = new Set(KNOWN_DELEGATED_ADMIN_OPERATION_FIELDS);
113
+ const KNOWN_REQUESTS = new Set(KNOWN_DELEGATED_ADMIN_REQUEST_FIELDS);
114
+ const KNOWN_SERVICE_MANAGEMENT_DETAILS = new Set(KNOWN_DELEGATED_ADMIN_SERVICE_MANAGEMENT_DETAIL_FIELDS);
115
+ // Compact customer rows: the customer identifier, the tenant name and the
116
+ // tenant id that names the same customer.
117
+ const DEFAULT_CUSTOMER_LIST_SELECT = ["id", "displayName", "tenantId"];
118
+ // Show rows: the full reviewed customer set.
119
+ const DEFAULT_CUSTOMER_SHOW_SELECT = [...KNOWN_DELEGATED_ADMIN_CUSTOMER_FIELDS];
120
+ // Compact relationship rows: the relationship identifier and name, the
121
+ // lifecycle status, the customer participant and the expiry that decides
122
+ // renewal attention.
123
+ const DEFAULT_RELATIONSHIP_LIST_SELECT = ["id", "displayName", "status", "customer", "endDateTime"];
124
+ // Show rows: the full reviewed relationship set.
125
+ const DEFAULT_RELATIONSHIP_SHOW_SELECT = [...KNOWN_DELEGATED_ADMIN_RELATIONSHIP_FIELDS];
126
+ // Delegated defaults are operation-specific; application profiles use their
127
+ // configured .default audience and reject --scopes.
128
+ export const DEFAULT_DELEGATED_ADMIN_SCOPES = ["https://graph.microsoft.com/DelegatedAdminRelationship.Read.All"];
129
+ const TRUNCATE_AT = 500;
130
+ function fieldList(raw, known, knownList, flag, help) {
131
+ const parts = String(raw)
132
+ .split(",")
133
+ .map(part => part.trim())
134
+ .filter(part => part.length > 0);
135
+ if (!parts.length)
136
+ throw new AxiError(`--${flag} needs at least one property`, "VALIDATION_ERROR", [help]);
137
+ const fields = [];
138
+ for (const part of parts) {
139
+ if (!known.has(part)) {
140
+ throw new AxiError(`Unknown delegated-admin property ${part} in --${flag}`, "VALIDATION_ERROR", [
141
+ help,
142
+ `Known properties: ${knownList.join(", ")}`,
143
+ ]);
144
+ }
145
+ if (!fields.includes(part))
146
+ fields.push(part);
147
+ }
148
+ return fields;
149
+ }
150
+ function scopesFor(flags, defaults, profile, help) {
151
+ if (profile.mode === "application") {
152
+ if (flags.scopes !== undefined) {
153
+ throw new AxiError("Application profiles use the configured Graph .default audience; delegated scopes are unavailable", "VALIDATION_ERROR", [help, "mg-axi profile show --profile <name>"]);
154
+ }
155
+ return undefined;
156
+ }
157
+ if (flags.scopes === undefined)
158
+ return [...defaults];
159
+ const scopes = String(flags.scopes)
160
+ .split(",")
161
+ .map(scope => scope.trim())
162
+ .filter(scope => scope.length > 0);
163
+ return scopes;
164
+ }
165
+ function truncateValue(value, full) {
166
+ const truncate = (text) => full || text.length <= TRUNCATE_AT
167
+ ? { value: text, truncated: false }
168
+ : { value: `${text.slice(0, TRUNCATE_AT)}... (truncated, ${text.length} chars total)`, truncated: true };
169
+ if (typeof value === "string")
170
+ return truncate(value);
171
+ if (Array.isArray(value) && value.every(entry => typeof entry === "string")) {
172
+ const projected = value.map(truncate);
173
+ return { value: projected.map(entry => entry.value), truncated: projected.some(entry => entry.truncated) };
174
+ }
175
+ return { value, truncated: false };
176
+ }
177
+ // Local projection preserves Graph's null/missing distinction: an explicit
178
+ // null stays null, an absent property stays absent and is never synthesized.
179
+ function project(row, fields, full) {
180
+ const source = row !== null && typeof row === "object" && !Array.isArray(row) ? row : {};
181
+ const projected = {};
182
+ let truncated = false;
183
+ for (const field of fields) {
184
+ if (!Object.hasOwn(source, field))
185
+ continue;
186
+ const result = truncateValue(source[field], full);
187
+ projected[field] = result.value;
188
+ truncated = truncated || result.truncated;
189
+ }
190
+ return { row: projected, truncated };
191
+ }
192
+ function selectedFields(flags, known, knownList, defaults, help) {
193
+ const select = flags.select === undefined ? [...defaults] : fieldList(flags.select, known, knownList, "select", help);
194
+ const fields = flags.fields === undefined ? [...select] : fieldList(flags.fields, known, knownList, "fields", help);
195
+ const missing = fields.find(field => !select.includes(field));
196
+ if (missing) {
197
+ throw new AxiError(`--fields ${missing} was not fetched; request it with --select`, "VALIDATION_ERROR", [help]);
198
+ }
199
+ return { select, fields };
200
+ }
201
+ function profileHint(profileName) {
202
+ return `--profile ${shellValue(profileName)}`;
203
+ }
204
+ function shellValue(value) {
205
+ return /^[A-Za-z0-9_.,:/@=-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
206
+ }
207
+ function fullHint(command, flags, profileName) {
208
+ const args = Object.entries({ ...flags, profile: profileName, full: true })
209
+ .map(([name, value]) => value === true ? `--${name}` : `--${name} ${shellValue(String(value))}`);
210
+ return `mg-axi ${command} ${args.join(" ")}`;
211
+ }
212
+ // Denials carry the session's generic grant/role/licence cause; each
213
+ // operation adds the scope and partner prerequisite that actually unlock it,
214
+ // because a 403 alone never says which prerequisite is missing.
215
+ function withGuidance(hints, run) {
216
+ return run().catch(error => {
217
+ if (error instanceof AxiError && error.code === "GRAPH_ERROR" && /\(403\)/.test(error.message)) {
218
+ throw new AxiError(error.message, "GRAPH_ERROR", [...hints, ...error.suggestions]);
219
+ }
220
+ throw error;
221
+ });
222
+ }
223
+ const DELEGATED_ADMIN_DENIAL_HINTS = [
224
+ "Delegated-admin reads need DelegatedAdminRelationship.Read.All, passed as --scopes for delegated access or admin-consented for application access",
225
+ "Delegated-admin reads run in the partner tenant; a non-partner tenant reads empty, which is an answer rather than an error",
226
+ "Personal Microsoft accounts are not supported for delegated-admin reads",
227
+ "No P1/P2 prerequisite is stated for delegated-admin reads; never diagnose licence solely from HTTP 403",
228
+ ];
229
+ const CUSTOMER = {
230
+ noun: "customer",
231
+ command: "entra delegated-admin-customer list",
232
+ known: KNOWN_CUSTOMERS,
233
+ knownList: KNOWN_DELEGATED_ADMIN_CUSTOMER_FIELDS,
234
+ listSelect: DEFAULT_CUSTOMER_LIST_SELECT,
235
+ showSelect: DEFAULT_CUSTOMER_SHOW_SELECT,
236
+ idFlag: "id",
237
+ idDescription: "Delegated-admin customer identifier",
238
+ showPlaceholder: "delegatedAdminCustomer-id",
239
+ };
240
+ const RELATIONSHIP = {
241
+ noun: "relationship",
242
+ command: "entra delegated-admin-relationship list",
243
+ known: KNOWN_RELATIONSHIPS,
244
+ knownList: KNOWN_DELEGATED_ADMIN_RELATIONSHIP_FIELDS,
245
+ listSelect: DEFAULT_RELATIONSHIP_LIST_SELECT,
246
+ showSelect: DEFAULT_RELATIONSHIP_SHOW_SELECT,
247
+ idFlag: "id",
248
+ idDescription: "Delegated-admin relationship identifier",
249
+ showPlaceholder: "delegatedAdminRelationship-id",
250
+ };
251
+ const RELATIONSHIP_PARENT = { flag: "id", placeholder: "delegatedAdminRelationship-id", label: "relationship" };
252
+ const CUSTOMER_PARENT = { flag: "id", placeholder: "delegatedAdminCustomer-id", label: "customer" };
253
+ const ACCESS_ASSIGNMENT = {
254
+ noun: "access assignment",
255
+ command: "entra delegated-admin-relationship list-access-assignments",
256
+ known: KNOWN_ACCESS_ASSIGNMENTS,
257
+ knownList: KNOWN_DELEGATED_ADMIN_ACCESS_ASSIGNMENT_FIELDS,
258
+ listSelect: ["id", "status", "accessContainer", "accessDetails"],
259
+ showSelect: [...KNOWN_DELEGATED_ADMIN_ACCESS_ASSIGNMENT_FIELDS],
260
+ idFlag: "assignment-id",
261
+ idDescription: "Delegated-admin access-assignment identifier",
262
+ showPlaceholder: "delegatedAdminAccessAssignment-id",
263
+ parent: RELATIONSHIP_PARENT,
264
+ };
265
+ const OPERATION = {
266
+ noun: "relationship operation",
267
+ command: "entra delegated-admin-relationship list-operations",
268
+ known: KNOWN_OPERATIONS,
269
+ knownList: KNOWN_DELEGATED_ADMIN_OPERATION_FIELDS,
270
+ listSelect: ["id", "operationType", "status", "lastModifiedDateTime"],
271
+ showSelect: [...KNOWN_DELEGATED_ADMIN_OPERATION_FIELDS],
272
+ idFlag: "operation-id",
273
+ idDescription: "Delegated-admin relationship-operation identifier",
274
+ showPlaceholder: "delegatedAdminRelationshipOperation-id",
275
+ parent: RELATIONSHIP_PARENT,
276
+ };
277
+ const REQUEST = {
278
+ noun: "relationship request",
279
+ command: "entra delegated-admin-relationship list-requests",
280
+ known: KNOWN_REQUESTS,
281
+ knownList: KNOWN_DELEGATED_ADMIN_REQUEST_FIELDS,
282
+ listSelect: ["id", "action", "status", "lastModifiedDateTime"],
283
+ showSelect: [...KNOWN_DELEGATED_ADMIN_REQUEST_FIELDS],
284
+ idFlag: "request-id",
285
+ idDescription: "Delegated-admin relationship-request identifier",
286
+ showPlaceholder: "delegatedAdminRelationshipRequest-id",
287
+ parent: RELATIONSHIP_PARENT,
288
+ };
289
+ const SERVICE_MANAGEMENT_DETAIL = {
290
+ noun: "service-management detail",
291
+ command: "entra delegated-admin-customer list-service-management-details",
292
+ known: KNOWN_SERVICE_MANAGEMENT_DETAILS,
293
+ knownList: KNOWN_DELEGATED_ADMIN_SERVICE_MANAGEMENT_DETAIL_FIELDS,
294
+ listSelect: [...KNOWN_DELEGATED_ADMIN_SERVICE_MANAGEMENT_DETAIL_FIELDS],
295
+ showSelect: [...KNOWN_DELEGATED_ADMIN_SERVICE_MANAGEMENT_DETAIL_FIELDS],
296
+ idFlag: "detail-id",
297
+ idDescription: "Delegated-admin service-management-detail identifier",
298
+ showPlaceholder: "delegatedAdminServiceManagementDetail-id",
299
+ parent: CUSTOMER_PARENT,
300
+ queryless: true,
301
+ };
302
+ function partnerNote(resource) {
303
+ return `Delegated-admin reads run in the partner tenant; a non-partner tenant lists zero ${resource.noun}s, which is an answer rather than an error`;
304
+ }
305
+ function parentBinding(flags, resource, help) {
306
+ if (!resource.parent)
307
+ return {};
308
+ const id = String(flags[resource.parent.flag]);
309
+ if (!id.trim())
310
+ throw new AxiError(`--${resource.parent.flag} needs the delegated-admin ${resource.parent.label} identifier`, "VALIDATION_ERROR", [help]);
311
+ return { [resource.parent.placeholder]: id };
312
+ }
313
+ // Queryless navigations take no --select or --filter: Graph documents no
314
+ // query parameters for them, so the catalogue declares no such flags and
315
+ // this refusal is the backstop before credentials.
316
+ function refuseQuery(flags, resource, command, help) {
317
+ if (resource.queryless === true && (flags.select !== undefined || flags.filter !== undefined)) {
318
+ throw new AxiError(`${command} takes no --select or --filter; Graph documents no query parameters for this navigation`, "VALIDATION_ERROR", [help]);
319
+ }
320
+ }
321
+ // Restoring the saved select/filter keeps cursor resumes lossless when
322
+ // --select or --filter is omitted. Both lists pass --filter through as plain
323
+ // $filter with no $count or ConsistencyLevel contract.
324
+ function collectionCommon(session, flags, resource, operation, help, profile) {
325
+ const cursor = flags.cursor === undefined ? undefined : String(flags.cursor);
326
+ if (cursor !== undefined && !cursor.trim())
327
+ throw new AxiError("--cursor needs the opaque cursor from a partial result", "VALIDATION_ERROR", [help]);
328
+ const saved = cursor === undefined ? undefined : session.cursorQuery(operation, cursor);
329
+ const params = parentBinding(flags, resource, help);
330
+ if (resource.queryless === true) {
331
+ refuseQuery(flags, resource, resource.command, help);
332
+ // Rows arrive whole; --fields projects them locally from the full set.
333
+ const fields = flags.fields === undefined ? [...resource.listSelect] : fieldList(flags.fields, resource.known, resource.knownList, "fields", help);
334
+ return { cursor, select: [...resource.listSelect], fields, scopes: scopesFor(flags, DEFAULT_DELEGATED_ADMIN_SCOPES, profile, help), full: flags.full === true, filter: undefined, params, bare: true };
335
+ }
336
+ const { select, fields } = selectedFields(flags, resource.known, resource.knownList, saved?.["$select"] === undefined ? resource.listSelect : fieldList(saved["$select"], resource.known, resource.knownList, "select", help), help);
337
+ const filter = flags.filter === undefined ? saved?.["$filter"] : String(flags.filter);
338
+ return { cursor, select, fields, scopes: scopesFor(flags, DEFAULT_DELEGATED_ADMIN_SCOPES, profile, help), full: flags.full === true, filter, params, bare: false };
339
+ }
340
+ function collectArgs(profile, operation, common, flags, help) {
341
+ // Queryless navigations send no query parameters at all: Graph documents
342
+ // none for them, so rows arrive whole and project locally.
343
+ const query = common.bare ? {} : { $select: common.select.join(",") };
344
+ if (!common.bare && common.filter !== undefined)
345
+ query.$filter = common.filter;
346
+ const args = { profile, operation, params: common.params, query, scopes: common.scopes };
347
+ if (common.cursor !== undefined)
348
+ args.cursor = common.cursor;
349
+ if (flags.all === true) {
350
+ if (flags.limit !== undefined)
351
+ throw new AxiError("--limit and --all cannot be combined", "VALIDATION_ERROR", [help]);
352
+ }
353
+ else {
354
+ args.limit = flags.limit === undefined ? 100 : Number(flags.limit);
355
+ }
356
+ return args;
357
+ }
358
+ function resumeHint(profileName) {
359
+ return `Resume losslessly with the same flags plus --cursor <cursor-from-output> ${profileHint(profileName)}`;
360
+ }
361
+ function resourceId(flags, resource, help) {
362
+ const id = String(flags[resource.idFlag]);
363
+ if (!id.trim())
364
+ throw new AxiError(`--${resource.idFlag} needs the delegated-admin ${resource.noun} identifier`, "VALIDATION_ERROR", [help]);
365
+ return id;
366
+ }
367
+ async function listResource(session, flags, profile, operation, help, profileName, resource, collectionKey, showCommand) {
368
+ const common = collectionCommon(session, flags, resource, operation, help, profile);
369
+ const result = await withGuidance(DELEGATED_ADMIN_DENIAL_HINTS, () => session.collect(collectArgs(profile, operation, common, flags, help)));
370
+ // Queryless hints never echo --select/--filter: those flags are refused.
371
+ const effectiveFlags = { ...flags };
372
+ if (!common.bare) {
373
+ effectiveFlags.select = result.query.$select ?? resource.listSelect.join(",");
374
+ if (result.query.$filter !== undefined)
375
+ effectiveFlags.filter = result.query.$filter;
376
+ }
377
+ const rows = [];
378
+ let truncated = false;
379
+ for (const row of result.value) {
380
+ const projected = project(row, common.fields, common.full);
381
+ rows.push(projected.row);
382
+ truncated = truncated || projected.truncated;
383
+ }
384
+ const showArgs = resource.parent === undefined
385
+ ? `--id <${resource.noun}-id>`
386
+ : `--${resource.parent.flag} <${resource.parent.label}-id> --${resource.idFlag} <${resource.idFlag}>`;
387
+ const showHint = `mg-axi ${showCommand} ${showArgs} ${profileHint(profileName)}`;
388
+ const truncationHints = truncated ? [fullHint(resource.command, effectiveFlags, profileName)] : [];
389
+ if (!result.complete) {
390
+ return {
391
+ [collectionKey]: rows,
392
+ count: { returned: rows.length, complete: false, reason: result.reason },
393
+ cursor: result.cursor,
394
+ help: [...truncationHints, resumeHint(profileName), showHint, partnerNote(resource)],
395
+ };
396
+ }
397
+ const count = { returned: rows.length, complete: true };
398
+ if (!rows.length) {
399
+ return {
400
+ [collectionKey]: rows,
401
+ count,
402
+ help: [`0 ${resource.noun}s matched; the absence of results is the answer, not an error`, partnerNote(resource)],
403
+ };
404
+ }
405
+ return { [collectionKey]: rows, count, help: [...truncationHints, showHint, partnerNote(resource)] };
406
+ }
407
+ async function showResource(session, flags, profile, operation, help, profileName, resource, rowKey, showCommand) {
408
+ const queryless = resource.queryless === true;
409
+ if (queryless)
410
+ refuseQuery(flags, resource, showCommand, help);
411
+ const { select, fields } = queryless
412
+ ? { select: [...resource.showSelect], fields: flags.fields === undefined ? [...resource.showSelect] : fieldList(flags.fields, resource.known, resource.knownList, "fields", help) }
413
+ : selectedFields(flags, resource.known, resource.knownList, resource.showSelect, help);
414
+ const scopes = scopesFor(flags, DEFAULT_DELEGATED_ADMIN_SCOPES, profile, help);
415
+ const full = flags.full === true;
416
+ const raw = await withGuidance(DELEGATED_ADMIN_DENIAL_HINTS, () => session.execute({
417
+ profile,
418
+ operation,
419
+ params: { ...parentBinding(flags, resource, help), [resource.showPlaceholder]: resourceId(flags, resource, help) },
420
+ ...(queryless ? {} : { query: { $select: select.join(",") } }),
421
+ scopes,
422
+ }));
423
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
424
+ throw new AxiError(`Graph returned a malformed delegated-admin ${resource.noun} body`, "GRAPH_ERROR", [
425
+ `Single-${resource.noun} reads carry one ${resource.noun} object; treat anything else as unknown, not empty`,
426
+ ]);
427
+ }
428
+ const { row, truncated } = project(raw, fields, full);
429
+ if (truncated)
430
+ return { [rowKey]: row, help: [fullHint(showCommand, flags, profileName)] };
431
+ return { [rowKey]: row };
432
+ }
433
+ export async function listDelegatedAdminCustomers(session, flags, profile, operation, help, profileName) {
434
+ return listResource(session, flags, profile, operation, help, profileName, CUSTOMER, "delegatedAdminCustomers", "entra delegated-admin-customer show");
435
+ }
436
+ export async function showDelegatedAdminCustomer(session, flags, profile, operation, help, profileName) {
437
+ return showResource(session, flags, profile, operation, help, profileName, CUSTOMER, "delegatedAdminCustomer", "entra delegated-admin-customer show");
438
+ }
439
+ export async function listDelegatedAdminRelationships(session, flags, profile, operation, help, profileName) {
440
+ return listResource(session, flags, profile, operation, help, profileName, RELATIONSHIP, "delegatedAdminRelationships", "entra delegated-admin-relationship show");
441
+ }
442
+ export async function showDelegatedAdminRelationship(session, flags, profile, operation, help, profileName) {
443
+ return showResource(session, flags, profile, operation, help, profileName, RELATIONSHIP, "delegatedAdminRelationship", "entra delegated-admin-relationship show");
444
+ }
445
+ export async function listDelegatedAdminAccessAssignments(session, flags, profile, operation, help, profileName) {
446
+ return listResource(session, flags, profile, operation, help, profileName, ACCESS_ASSIGNMENT, "delegatedAdminAccessAssignments", "entra delegated-admin-relationship show-access-assignment");
447
+ }
448
+ export async function showDelegatedAdminAccessAssignment(session, flags, profile, operation, help, profileName) {
449
+ return showResource(session, flags, profile, operation, help, profileName, ACCESS_ASSIGNMENT, "delegatedAdminAccessAssignment", "entra delegated-admin-relationship show-access-assignment");
450
+ }
451
+ export async function listDelegatedAdminOperations(session, flags, profile, operation, help, profileName) {
452
+ return listResource(session, flags, profile, operation, help, profileName, OPERATION, "delegatedAdminRelationshipOperations", "entra delegated-admin-relationship show-operation");
453
+ }
454
+ export async function showDelegatedAdminOperation(session, flags, profile, operation, help, profileName) {
455
+ return showResource(session, flags, profile, operation, help, profileName, OPERATION, "delegatedAdminRelationshipOperation", "entra delegated-admin-relationship show-operation");
456
+ }
457
+ export async function listDelegatedAdminRequests(session, flags, profile, operation, help, profileName) {
458
+ return listResource(session, flags, profile, operation, help, profileName, REQUEST, "delegatedAdminRelationshipRequests", "entra delegated-admin-relationship show-request");
459
+ }
460
+ export async function showDelegatedAdminRequest(session, flags, profile, operation, help, profileName) {
461
+ return showResource(session, flags, profile, operation, help, profileName, REQUEST, "delegatedAdminRelationshipRequest", "entra delegated-admin-relationship show-request");
462
+ }
463
+ export async function listDelegatedAdminServiceManagementDetails(session, flags, profile, operation, help, profileName) {
464
+ return listResource(session, flags, profile, operation, help, profileName, SERVICE_MANAGEMENT_DETAIL, "delegatedAdminServiceManagementDetails", "entra delegated-admin-customer show-service-management-detail");
465
+ }
466
+ export async function showDelegatedAdminServiceManagementDetail(session, flags, profile, operation, help, profileName) {
467
+ return showResource(session, flags, profile, operation, help, profileName, SERVICE_MANAGEMENT_DETAIL, "delegatedAdminServiceManagementDetail", "entra delegated-admin-customer show-service-management-detail");
468
+ }