@knowttl/mg-axi 0.1.1 → 0.1.2

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 (40) hide show
  1. package/README.md +149 -38
  2. package/dist/api.js +134 -8
  3. package/dist/bin/mg-axi-hook.js +31 -0
  4. package/dist/catalogue.js +390 -2
  5. package/dist/cli.js +116 -8
  6. package/dist/docs.js +45 -5
  7. package/dist/doctor.js +2 -2
  8. package/dist/entra-access-reviews.js +11 -4
  9. package/dist/entra-apps.js +13 -8
  10. package/dist/entra-audit-logs.js +8 -4
  11. package/dist/entra-auth-methods.js +8 -4
  12. package/dist/entra-certificate-auth.js +12 -8
  13. package/dist/entra-conditional-access.js +9 -4
  14. package/dist/entra-contacts.js +19 -12
  15. package/dist/entra-contracts.js +7 -4
  16. package/dist/entra-custom-security-attributes.js +19 -12
  17. package/dist/entra-data-policy-operations.js +7 -4
  18. package/dist/entra-delegated-admin.js +55 -6
  19. package/dist/entra-deleted-items.js +6 -4
  20. package/dist/entra-directory-objects.js +6 -4
  21. package/dist/entra-directory.js +19 -12
  22. package/dist/entra-domains.js +13 -8
  23. package/dist/entra-entitlement-management.js +12 -4
  24. package/dist/entra-group-pim.js +431 -0
  25. package/dist/entra-groups.js +13 -8
  26. package/dist/entra-lifecycle-workflows.js +136 -9
  27. package/dist/entra-organization.js +13 -8
  28. package/dist/entra-risk-prevention.js +436 -0
  29. package/dist/entra-risk.js +213 -5
  30. package/dist/entra-roles.js +10 -4
  31. package/dist/entra-subscriptions.js +6 -4
  32. package/dist/entra-users.js +7 -4
  33. package/dist/graph-session.js +29 -5
  34. package/dist/hook.js +84 -0
  35. package/dist/list-totals.js +52 -0
  36. package/dist/setup.js +5 -0
  37. package/docs/coverage.md +79 -13
  38. package/inventory/operations.json +2 -2
  39. package/package.json +4 -3
  40. package/skills/mg-axi/SKILL.md +56 -1
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-01 certificate-auth subfamily: the read mapping behind
3
4
  // `mg-axi entra certificate-auth-pki list/show/count` and
4
5
  // `mg-axi entra certificate-authority list/show/count`. Operation
@@ -242,16 +243,17 @@ export async function listCertificateAuthPkis(session, flags, profile, operation
242
243
  if (!result.complete) {
243
244
  return {
244
245
  certificateAuthPkis: pkis,
245
- count: { returned: pkis.length, complete: false, reason: result.reason },
246
+ ...listTotals(pkis.length, result.total, "PKI configurations", false),
247
+ complete: false,
248
+ reason: result.reason,
246
249
  cursor: result.cursor,
247
250
  help: [...truncationHints, resumeHint(profileName), showHint, EMPTY_PKI_NOTE],
248
251
  };
249
252
  }
250
- const count = { returned: pkis.length, complete: true };
251
253
  if (!pkis.length) {
252
- return { certificateAuthPkis: pkis, count, help: [EMPTY_PKI_NOTE] };
254
+ return { certificateAuthPkis: pkis, ...listTotals(pkis.length, result.total, "PKI configurations", true), complete: true, help: [EMPTY_PKI_NOTE] };
253
255
  }
254
- return { certificateAuthPkis: pkis, count, help: [...truncationHints, showHint, EMPTY_PKI_NOTE] };
256
+ return { certificateAuthPkis: pkis, ...listTotals(pkis.length, result.total, "PKI configurations", true), complete: true, help: [...truncationHints, showHint, EMPTY_PKI_NOTE] };
255
257
  }
256
258
  export async function showCertificateAuthPki(session, flags, profile, operation, help, profileName) {
257
259
  const { select, fields } = selectedFields(flags, KNOWN_PKIS, KNOWN_PKI_FIELDS, DEFAULT_PKI_SHOW_SELECT, help, "PKI");
@@ -324,20 +326,22 @@ export async function listCertificateAuthorities(session, flags, profile, operat
324
326
  if (!result.complete) {
325
327
  return {
326
328
  certificateAuthorities: authorities,
327
- count: { returned: authorities.length, complete: false, reason: result.reason },
329
+ ...listTotals(authorities.length, result.total, "certificate authorities", false),
330
+ complete: false,
331
+ reason: result.reason,
328
332
  cursor: result.cursor,
329
333
  help: [...truncationHints, resumeHint(profileName), showHint, CERT_BLOB_NOTE],
330
334
  };
331
335
  }
332
- const count = { returned: authorities.length, complete: true };
333
336
  if (!authorities.length) {
334
337
  return {
335
338
  certificateAuthorities: authorities,
336
- count,
339
+ ...listTotals(authorities.length, result.total, "certificate authorities", true),
340
+ complete: true,
337
341
  help: ["0 certificate authorities matched; the absence of results is the answer, not an error", CERT_BLOB_NOTE],
338
342
  };
339
343
  }
340
- return { certificateAuthorities: authorities, count, help: [...truncationHints, showHint, CERT_BLOB_NOTE] };
344
+ return { certificateAuthorities: authorities, ...listTotals(authorities.length, result.total, "certificate authorities", true), complete: true, help: [...truncationHints, showHint, CERT_BLOB_NOTE] };
341
345
  }
342
346
  export async function showCertificateAuthority(session, flags, profile, operation, help, profileName) {
343
347
  const { select, fields } = selectedFields(flags, KNOWN_CAS, KNOWN_CA_FIELDS, DEFAULT_CA_SHOW_SELECT, help, "certificate-authority");
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // READ-03: the Conditional Access read mapping behind
3
4
  // `mg-axi entra conditional-access policy list/show` and
4
5
  // `mg-axi entra conditional-access named-location list/show` as separate
@@ -183,6 +184,7 @@ const POLICY_LIST = {
183
184
  command: "entra conditional-access policy list",
184
185
  key: "policies",
185
186
  noun: "policy",
187
+ plural: "policies",
186
188
  known: POLICY_KNOWN,
187
189
  knownList: KNOWN_POLICY_FIELDS,
188
190
  defaultSelect: DEFAULT_POLICY_LIST_SELECT,
@@ -195,6 +197,7 @@ const LOCATION_LIST = {
195
197
  command: "entra conditional-access named-location list",
196
198
  key: "namedLocations",
197
199
  noun: "named-location",
200
+ plural: "named locations",
198
201
  known: LOCATION_KNOWN,
199
202
  knownList: KNOWN_LOCATION_FIELDS,
200
203
  defaultSelect: DEFAULT_LOCATION_LIST_SELECT,
@@ -242,23 +245,25 @@ async function listCollection(shape, session, flags, profile, operation, help, p
242
245
  if (!result.complete) {
243
246
  return {
244
247
  [shape.key]: rows,
245
- count: { returned: rows.length, complete: false, reason: result.reason },
248
+ ...listTotals(rows.length, result.total, shape.plural, false),
249
+ complete: false,
250
+ reason: result.reason,
246
251
  cursor: result.cursor,
247
252
  help: [...truncationHints, `Resume losslessly with the same flags plus --cursor - ${profileHint(profileName)} and supply the returned cursor on stdin`, showHint],
248
253
  };
249
254
  }
250
- const count = { returned: rows.length, complete: true };
251
255
  if (!rows.length) {
252
256
  return {
253
257
  [shape.key]: rows,
254
- count,
258
+ ...listTotals(rows.length, result.total, shape.plural, true),
259
+ complete: true,
255
260
  help: [
256
261
  `mg-axi ${shape.command} --filter <odata-filter> ${profileHint(profileName)}`,
257
262
  shape.emptyHint,
258
263
  ],
259
264
  };
260
265
  }
261
- return { [shape.key]: rows, count, help: [...truncationHints, showHint] };
266
+ return { [shape.key]: rows, ...listTotals(rows.length, result.total, shape.plural, true), complete: true, help: [...truncationHints, showHint] };
262
267
  }
263
268
  const POLICY_SHOW = {
264
269
  command: "entra conditional-access policy show",
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-01 contacts subfamily plus EXT-01m/EXT-01n navigation: the read mapping
3
4
  // behind `mg-axi entra contact list/show/count`, the manager/directReports
4
5
  // navigation reads (`show-manager`, `list/show/count-direct-reports`) and the
@@ -290,23 +291,25 @@ export async function listContacts(session, flags, profile, operation, help, pro
290
291
  if (!result.complete) {
291
292
  return {
292
293
  contacts,
293
- count: { returned: contacts.length, complete: false, reason: result.reason },
294
+ ...listTotals(contacts.length, result.total, "contacts", false),
295
+ complete: false,
296
+ reason: result.reason,
294
297
  cursor: result.cursor,
295
298
  help: [...truncationHints, resumeHint(profileName), showHint],
296
299
  };
297
300
  }
298
- const count = { returned: contacts.length, complete: true };
299
301
  if (!contacts.length) {
300
302
  return {
301
303
  contacts,
302
- count,
304
+ ...listTotals(contacts.length, result.total, "contacts", true),
305
+ complete: true,
303
306
  help: [
304
307
  `mg-axi entra contact list --filter <odata-filter> ${profileHint(profileName)}`,
305
308
  "0 contacts matched; the absence of results is the answer, not an error",
306
309
  ],
307
310
  };
308
311
  }
309
- return { contacts, count, help: [...truncationHints, showHint] };
312
+ return { contacts, ...listTotals(contacts.length, result.total, "contacts", true), complete: true, help: [...truncationHints, showHint] };
310
313
  }
311
314
  export async function showContact(session, flags, profile, operation, help, profileName) {
312
315
  checkReadOperation(operation, help);
@@ -512,20 +515,22 @@ export async function listContactDirectReports(session, flags, profile, operatio
512
515
  if (!result.complete) {
513
516
  return {
514
517
  directReports,
515
- count: { returned: directReports.length, complete: false, reason: result.reason },
518
+ ...listTotals(directReports.length, result.total, "direct reports", false),
519
+ complete: false,
520
+ reason: result.reason,
516
521
  cursor: result.cursor,
517
522
  help: [...truncationHints, ...limitedHints, resumeHint(profileName), showHint],
518
523
  };
519
524
  }
520
- const count = { returned: directReports.length, complete: true };
521
525
  if (!directReports.length) {
522
526
  return {
523
527
  directReports,
524
- count,
528
+ ...listTotals(directReports.length, result.total, "direct reports", true),
529
+ complete: true,
525
530
  help: [...limitedHints, "0 direct reports matched; the absence of results is the answer, not an error", showHint],
526
531
  };
527
532
  }
528
- return { directReports, count, help: [...truncationHints, ...limitedHints, showHint] };
533
+ return { directReports, ...listTotals(directReports.length, result.total, "direct reports", true), complete: true, help: [...truncationHints, ...limitedHints, showHint] };
529
534
  }
530
535
  export async function showContactDirectReport(session, flags, profile, operation, help, profileName) {
531
536
  checkReadOperation(operation, help);
@@ -767,20 +772,22 @@ export async function listContactMemberOf(session, flags, profile, operation, he
767
772
  if (!result.complete) {
768
773
  return {
769
774
  memberOf,
770
- count: { returned: memberOf.length, complete: false, reason: result.reason },
775
+ ...listTotals(memberOf.length, result.total, "memberships", false),
776
+ complete: false,
777
+ reason: result.reason,
771
778
  cursor: result.cursor,
772
779
  help: [...truncationHints, ...limitedHints, resumeHint(profileName), scopeHint, showHint],
773
780
  };
774
781
  }
775
- const count = { returned: memberOf.length, complete: true };
776
782
  if (!memberOf.length) {
777
783
  return {
778
784
  memberOf,
779
- count,
785
+ ...listTotals(memberOf.length, result.total, "memberships", true),
786
+ complete: true,
780
787
  help: [...limitedHints, `0 ${noun} matched; the absence of results is the answer, not an error`, scopeHint, showHint],
781
788
  };
782
789
  }
783
- return { memberOf, count, help: [...truncationHints, ...limitedHints, scopeHint, showHint] };
790
+ return { memberOf, ...listTotals(memberOf.length, result.total, "memberships", true), complete: true, help: [...truncationHints, ...limitedHints, scopeHint, showHint] };
784
791
  }
785
792
  export async function showContactMemberOf(session, flags, profile, operation, help, profileName) {
786
793
  checkReadOperation(operation, help);
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-04 partner contracts subfamily: the read mapping behind `mg-axi entra
3
4
  // contract list/show/count`. Operation construction stays beside its command;
4
5
  // the shared session owns URLs, credentials, paging, retries and error
@@ -198,20 +199,22 @@ export async function listContracts(session, flags, profile, operation, help, pr
198
199
  if (!result.complete) {
199
200
  return {
200
201
  contracts,
201
- count: { returned: contracts.length, complete: false, reason: result.reason },
202
+ ...listTotals(contracts.length, result.total, "contracts", false),
203
+ complete: false,
204
+ reason: result.reason,
202
205
  cursor: result.cursor,
203
206
  help: [...truncationHints, resumeHint(profileName), showHint, PARTNER_NOTE],
204
207
  };
205
208
  }
206
- const count = { returned: contracts.length, complete: true };
207
209
  if (!contracts.length) {
208
210
  return {
209
211
  contracts,
210
- count,
212
+ ...listTotals(contracts.length, result.total, "contracts", true),
213
+ complete: true,
211
214
  help: ["0 contracts matched; the absence of results is the answer, not an error", PARTNER_NOTE],
212
215
  };
213
216
  }
214
- return { contracts, count, help: [...truncationHints, showHint, PARTNER_NOTE] };
217
+ return { contracts, ...listTotals(contracts.length, result.total, "contracts", true), complete: true, help: [...truncationHints, showHint, PARTNER_NOTE] };
215
218
  }
216
219
  export async function showContract(session, flags, profile, operation, help, profileName) {
217
220
  const { select, fields } = selectedFields(flags, KNOWN_CONTRACTS, KNOWN_CONTRACT_FIELDS, DEFAULT_CONTRACT_SHOW_SELECT, help);
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-01 custom-security-attributes subfamily: the read mapping behind
3
4
  // `mg-axi entra attribute-set list/show/count`,
4
5
  // `mg-axi entra custom-security-attribute-definition list/show/count` and
@@ -265,20 +266,22 @@ export async function listAttributeSets(session, flags, profile, operation, help
265
266
  if (!result.complete) {
266
267
  return {
267
268
  attributeSets: sets,
268
- count: { returned: sets.length, complete: false, reason: result.reason },
269
+ ...listTotals(sets.length, result.total, "attribute sets", false),
270
+ complete: false,
271
+ reason: result.reason,
269
272
  cursor: result.cursor,
270
273
  help: [...truncationHints, resumeHint(profileName), showHint, filterNote],
271
274
  };
272
275
  }
273
- const count = { returned: sets.length, complete: true };
274
276
  if (!sets.length) {
275
277
  return {
276
278
  attributeSets: sets,
277
- count,
279
+ ...listTotals(sets.length, result.total, "attribute sets", true),
280
+ complete: true,
278
281
  help: [filterNote, "0 attribute sets matched; the absence of results is the answer, not an error"],
279
282
  };
280
283
  }
281
- return { attributeSets: sets, count, help: [...truncationHints, showHint, filterNote] };
284
+ return { attributeSets: sets, ...listTotals(sets.length, result.total, "attribute sets", true), complete: true, help: [...truncationHints, showHint, filterNote] };
282
285
  }
283
286
  export async function showAttributeSet(session, flags, profile, operation, help, profileName) {
284
287
  const { select, fields } = selectedFields(flags, KNOWN_ATTRIBUTE_SETS, KNOWN_ATTRIBUTE_SET_FIELDS, DEFAULT_ATTRIBUTE_SET_SHOW_SELECT, help, "attribute-set");
@@ -331,20 +334,22 @@ export async function listCustomSecurityAttributeDefinitions(session, flags, pro
331
334
  if (!result.complete) {
332
335
  return {
333
336
  customSecurityAttributeDefinitions: definitions,
334
- count: { returned: definitions.length, complete: false, reason: result.reason },
337
+ ...listTotals(definitions.length, result.total, "custom security attribute definitions", false),
338
+ complete: false,
339
+ reason: result.reason,
335
340
  cursor: result.cursor,
336
341
  help: [...truncationHints, resumeHint(profileName), showHint],
337
342
  };
338
343
  }
339
- const count = { returned: definitions.length, complete: true };
340
344
  if (!definitions.length) {
341
345
  return {
342
346
  customSecurityAttributeDefinitions: definitions,
343
- count,
347
+ ...listTotals(definitions.length, result.total, "custom security attribute definitions", true),
348
+ complete: true,
344
349
  help: ["0 custom security attribute definitions matched; the absence of results is the answer, not an error"],
345
350
  };
346
351
  }
347
- return { customSecurityAttributeDefinitions: definitions, count, help: [...truncationHints, showHint] };
352
+ return { customSecurityAttributeDefinitions: definitions, ...listTotals(definitions.length, result.total, "custom security attribute definitions", true), complete: true, help: [...truncationHints, showHint] };
348
353
  }
349
354
  export async function showCustomSecurityAttributeDefinition(session, flags, profile, operation, help, profileName) {
350
355
  const { select, fields } = selectedFields(flags, KNOWN_DEFINITIONS, KNOWN_CUSTOM_SECURITY_DEFINITION_FIELDS, DEFAULT_DEFINITION_SHOW_SELECT, help, "definition");
@@ -407,20 +412,22 @@ export async function listAllowedValues(session, flags, profile, operation, help
407
412
  if (!result.complete) {
408
413
  return {
409
414
  allowedValues: values,
410
- count: { returned: values.length, complete: false, reason: result.reason },
415
+ ...listTotals(values.length, result.total, "allowed values", false),
416
+ complete: false,
417
+ reason: result.reason,
411
418
  cursor: result.cursor,
412
419
  help: [...truncationHints, resumeHint(profileName), showHint, filterNote],
413
420
  };
414
421
  }
415
- const count = { returned: values.length, complete: true };
416
422
  if (!values.length) {
417
423
  return {
418
424
  allowedValues: values,
419
- count,
425
+ ...listTotals(values.length, result.total, "allowed values", true),
426
+ complete: true,
420
427
  help: [filterNote, "0 allowed values matched; the definition may allow free-form values instead of predefined ones"],
421
428
  };
422
429
  }
423
- return { allowedValues: values, count, help: [...truncationHints, showHint, filterNote] };
430
+ return { allowedValues: values, ...listTotals(values.length, result.total, "allowed values", true), complete: true, help: [...truncationHints, showHint, filterNote] };
424
431
  }
425
432
  export async function showAllowedValue(session, flags, profile, operation, help, profileName) {
426
433
  const { select, fields } = selectedFields(flags, KNOWN_ALLOWED_VALUES, KNOWN_ALLOWED_VALUE_FIELDS, DEFAULT_ALLOWED_VALUE_SHOW_SELECT, help, "allowed-value");
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  import { REDACTED } from "./graph-session.js";
3
4
  // EXT-03 data-policy-operations subfamily: the read mapping behind
4
5
  // `mg-axi entra data-policy-operation list/show/count`. Operation
@@ -224,20 +225,22 @@ export async function listDataPolicyOperations(session, flags, profile, operatio
224
225
  if (!result.complete) {
225
226
  return {
226
227
  dataPolicyOperations: operations,
227
- count: { returned: operations.length, complete: false, reason: result.reason },
228
+ ...listTotals(operations.length, result.total, "data-policy operations", false),
229
+ complete: false,
230
+ reason: result.reason,
228
231
  cursor: result.cursor,
229
232
  help: [...truncationHints, resumeHint(profileName), showHint, WORKFORCE_NOTE],
230
233
  };
231
234
  }
232
- const count = { returned: operations.length, complete: true };
233
235
  if (!operations.length) {
234
236
  return {
235
237
  dataPolicyOperations: operations,
236
- count,
238
+ ...listTotals(operations.length, result.total, "data-policy operations", true),
239
+ complete: true,
237
240
  help: ["0 data-policy operations matched; the absence of results is the answer, not an error", WORKFORCE_NOTE],
238
241
  };
239
242
  }
240
- return { dataPolicyOperations: operations, count, help: [...truncationHints, showHint, WORKFORCE_NOTE] };
243
+ return { dataPolicyOperations: operations, ...listTotals(operations.length, result.total, "data-policy operations", true), complete: true, help: [...truncationHints, showHint, WORKFORCE_NOTE] };
241
244
  }
242
245
  export async function showDataPolicyOperation(session, flags, profile, operation, help, profileName) {
243
246
  const { select, fields } = selectedFields(flags, DEFAULT_SHOW_SELECT, help);
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-04 delegated-admin subfamily: the read mapping behind
3
4
  // `mg-axi entra delegated-admin-customer list/show`,
4
5
  // `mg-axi entra delegated-admin-relationship list/show` and the navigation
@@ -35,8 +36,8 @@ import { AxiError } from "axi-sdk-js";
35
36
  // --filter passes through as plain $filter with no $count or ConsistencyLevel
36
37
  // contract. serviceManagementDetails documents no query parameters, so both
37
38
  // 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
39
+ // six $count scalars ship in this module; the tenantRelationship container
40
+ // root stays scheduled for a later EXT-04 subfamily, as do the multi-tenant-organization reads
40
41
  // (shipped separately) and the tenant-lookup functions (shipped as
41
42
  // tenant-information show under mg-ext-04e). Beta stays out. No delegated-admin mutation exists in this slice:
42
43
  // relationship creation, approval and termination are writes.
@@ -389,20 +390,22 @@ async function listResource(session, flags, profile, operation, help, profileNam
389
390
  if (!result.complete) {
390
391
  return {
391
392
  [collectionKey]: rows,
392
- count: { returned: rows.length, complete: false, reason: result.reason },
393
+ ...listTotals(rows.length, result.total, `${resource.noun}s`, false),
394
+ complete: false,
395
+ reason: result.reason,
393
396
  cursor: result.cursor,
394
397
  help: [...truncationHints, resumeHint(profileName), showHint, partnerNote(resource)],
395
398
  };
396
399
  }
397
- const count = { returned: rows.length, complete: true };
398
400
  if (!rows.length) {
399
401
  return {
400
402
  [collectionKey]: rows,
401
- count,
403
+ ...listTotals(rows.length, result.total, `${resource.noun}s`, true),
404
+ complete: true,
402
405
  help: [`0 ${resource.noun}s matched; the absence of results is the answer, not an error`, partnerNote(resource)],
403
406
  };
404
407
  }
405
- return { [collectionKey]: rows, count, help: [...truncationHints, showHint, partnerNote(resource)] };
408
+ return { [collectionKey]: rows, ...listTotals(rows.length, result.total, `${resource.noun}s`, true), complete: true, help: [...truncationHints, showHint, partnerNote(resource)] };
406
409
  }
407
410
  async function showResource(session, flags, profile, operation, help, profileName, resource, rowKey, showCommand) {
408
411
  const queryless = resource.queryless === true;
@@ -466,3 +469,49 @@ export async function listDelegatedAdminServiceManagementDetails(session, flags,
466
469
  export async function showDelegatedAdminServiceManagementDetail(session, flags, profile, operation, help, profileName) {
467
470
  return showResource(session, flags, profile, operation, help, profileName, SERVICE_MANAGEMENT_DETAIL, "delegatedAdminServiceManagementDetail", "entra delegated-admin-customer show-service-management-detail");
468
471
  }
472
+ // The $count routes return a text/plain integer scalar rather than a JSON
473
+ // collection, so each leaf reads it through session.execute and accepts only
474
+ // a non-negative integer. There is no --filter/--select/--limit contract on
475
+ // the counts: the catalogue declares no such flags and strict input
476
+ // validation refuses them before credentials. All six share the parent
477
+ // list/show read scope DelegatedAdminRelationship.Read.All, so no new
478
+ // consent is needed.
479
+ function countParentId(flags, label, help) {
480
+ const id = String(flags.id);
481
+ if (!id.trim())
482
+ throw new AxiError(`--id needs the delegated-admin ${label} identifier`, "VALIDATION_ERROR", [help]);
483
+ return id;
484
+ }
485
+ async function countScalar(session, flags, profile, operation, help, profileName, params, rowKey, noun) {
486
+ void profileName;
487
+ const scopes = scopesFor(flags, DEFAULT_DELEGATED_ADMIN_SCOPES, profile, help);
488
+ const raw = await withGuidance(DELEGATED_ADMIN_DENIAL_HINTS, () => session.execute({ profile, operation, params, scopes }));
489
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw < 0) {
490
+ throw new AxiError(`Graph returned a malformed delegated-admin ${noun} count body`, "GRAPH_ERROR", [
491
+ "Delegated-admin counts carry one non-negative integer scalar; treat anything else as unknown, not empty",
492
+ ]);
493
+ }
494
+ return { [rowKey]: raw };
495
+ }
496
+ export async function countDelegatedAdminCustomers(session, flags, profile, operation, help, profileName) {
497
+ return countScalar(session, flags, profile, operation, help, profileName, {}, "delegatedAdminCustomerCount", "customer");
498
+ }
499
+ export async function countDelegatedAdminServiceManagementDetails(session, flags, profile, operation, help, profileName) {
500
+ const customer = countParentId(flags, "customer", help);
501
+ return countScalar(session, flags, profile, operation, help, profileName, { "delegatedAdminCustomer-id": customer }, "delegatedAdminServiceManagementDetailCount", "service-management detail");
502
+ }
503
+ export async function countDelegatedAdminRelationships(session, flags, profile, operation, help, profileName) {
504
+ return countScalar(session, flags, profile, operation, help, profileName, {}, "delegatedAdminRelationshipCount", "relationship");
505
+ }
506
+ export async function countDelegatedAdminAccessAssignments(session, flags, profile, operation, help, profileName) {
507
+ const relationship = countParentId(flags, "relationship", help);
508
+ return countScalar(session, flags, profile, operation, help, profileName, { "delegatedAdminRelationship-id": relationship }, "delegatedAdminAccessAssignmentCount", "access assignment");
509
+ }
510
+ export async function countDelegatedAdminOperations(session, flags, profile, operation, help, profileName) {
511
+ const relationship = countParentId(flags, "relationship", help);
512
+ return countScalar(session, flags, profile, operation, help, profileName, { "delegatedAdminRelationship-id": relationship }, "delegatedAdminRelationshipOperationCount", "relationship operation");
513
+ }
514
+ export async function countDelegatedAdminRequests(session, flags, profile, operation, help, profileName) {
515
+ const relationship = countParentId(flags, "relationship", help);
516
+ return countScalar(session, flags, profile, operation, help, profileName, { "delegatedAdminRelationship-id": relationship }, "delegatedAdminRelationshipRequestCount", "relationship request");
517
+ }
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-01 deleted-items subfamily: the read mapping behind
3
4
  // `mg-axi entra deleted-user|deleted-group|deleted-application|
4
5
  // deleted-service-principal|deleted-administrative-unit list/count` and
@@ -402,16 +403,17 @@ export async function listDeletedItems(session, flags, profile, operation, help,
402
403
  if (!result.complete) {
403
404
  return {
404
405
  deletedItems: rows,
405
- count: { returned: rows.length, complete: false, reason: result.reason },
406
+ ...listTotals(rows.length, result.total, "deleted items", false),
407
+ complete: false,
408
+ reason: result.reason,
406
409
  cursor: result.cursor,
407
410
  help: [...truncationHints, resumeHint(profileName), showHint, ...groupNote, ...appNote],
408
411
  };
409
412
  }
410
- const count = { returned: rows.length, complete: true };
411
413
  if (!rows.length) {
412
- return { deletedItems: rows, count, help: [emptyNote, ...groupNote, ...appNote] };
414
+ return { deletedItems: rows, ...listTotals(rows.length, result.total, "deleted items", true), complete: true, help: [emptyNote, ...groupNote, ...appNote] };
413
415
  }
414
- return { deletedItems: rows, count, help: [...truncationHints, showHint, ...groupNote, ...appNote] };
416
+ return { deletedItems: rows, ...listTotals(rows.length, result.total, "deleted items", true), complete: true, help: [...truncationHints, showHint, ...groupNote, ...appNote] };
415
417
  }
416
418
  export async function showDeletedItem(session, flags, profile, operation, help, profileName) {
417
419
  checkShowOperation(operation, help);
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // EXT-01 directory-objects subfamily: the read mapping behind
3
4
  // `mg-axi entra directory-object list/show/count`. Operation construction
4
5
  // stays beside its command; the shared session owns URLs, credentials,
@@ -225,16 +226,17 @@ export async function listDirectoryObjects(session, flags, profile, operation, h
225
226
  if (!result.complete) {
226
227
  return {
227
228
  directoryObjects,
228
- count: { returned: directoryObjects.length, complete: false, reason: result.reason },
229
+ ...listTotals(directoryObjects.length, result.total, "directory objects", false),
230
+ complete: false,
231
+ reason: result.reason,
229
232
  cursor: result.cursor,
230
233
  help: [...truncationHints, resumeHint(profileName), showHint],
231
234
  };
232
235
  }
233
- const count = { returned: directoryObjects.length, complete: true };
234
236
  if (!directoryObjects.length) {
235
- return { directoryObjects, count, help: [EMPTY_NOTE] };
237
+ return { directoryObjects, ...listTotals(directoryObjects.length, result.total, "directory objects", true), complete: true, help: [EMPTY_NOTE] };
236
238
  }
237
- return { directoryObjects, count, help: [...truncationHints, showHint] };
239
+ return { directoryObjects, ...listTotals(directoryObjects.length, result.total, "directory objects", true), complete: true, help: [...truncationHints, showHint] };
238
240
  }
239
241
  export async function showDirectoryObject(session, flags, profile, operation, help, profileName) {
240
242
  checkReadOperation(operation, help);
@@ -1,4 +1,5 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
+ import { listTotals } from "./list-totals.js";
2
3
  // READ-10: the directory-device and administrative-unit read mapping behind
3
4
  // `mg-axi entra device list/show`, `mg-axi entra administrative-unit
4
5
  // list/show` and `mg-axi entra administrative-unit member list`. Operation
@@ -260,16 +261,18 @@ export async function listDevices(session, flags, profile, operation, help, prof
260
261
  if (!result.complete) {
261
262
  return {
262
263
  devices,
263
- count: { returned: devices.length, complete: false, reason: result.reason },
264
+ ...listTotals(devices.length, result.total, "devices", false),
265
+ complete: false,
266
+ reason: result.reason,
264
267
  cursor: result.cursor,
265
268
  help: [...truncationHints, resumeHint(profileName), showHint, scopeHint],
266
269
  };
267
270
  }
268
- const count = { returned: devices.length, complete: true };
269
271
  if (!devices.length) {
270
272
  return {
271
273
  devices,
272
- count,
274
+ ...listTotals(devices.length, result.total, "devices", true),
275
+ complete: true,
273
276
  help: [
274
277
  `mg-axi entra device list --filter <odata-filter> ${profileHint(profileName)}`,
275
278
  "0 devices matched; the absence of results is the answer, not an error",
@@ -277,7 +280,7 @@ export async function listDevices(session, flags, profile, operation, help, prof
277
280
  ],
278
281
  };
279
282
  }
280
- return { devices, count, help: [...truncationHints, showHint, scopeHint] };
283
+ return { devices, ...listTotals(devices.length, result.total, "devices", true), complete: true, help: [...truncationHints, showHint, scopeHint] };
281
284
  }
282
285
  export async function showDevice(session, flags, profile, operation, help, profileName) {
283
286
  const { select, fields } = selectedFields(flags, KNOWN_DEVICES, KNOWN_DEVICE_FIELDS, DEFAULT_DEVICE_SHOW_SELECT, help);
@@ -318,23 +321,25 @@ export async function listAdministrativeUnits(session, flags, profile, operation
318
321
  if (!result.complete) {
319
322
  return {
320
323
  administrativeUnits: units,
321
- count: { returned: units.length, complete: false, reason: result.reason },
324
+ ...listTotals(units.length, result.total, "administrative units", false),
325
+ complete: false,
326
+ reason: result.reason,
322
327
  cursor: result.cursor,
323
328
  help: [...truncationHints, resumeHint(profileName), showHint],
324
329
  };
325
330
  }
326
- const count = { returned: units.length, complete: true };
327
331
  if (!units.length) {
328
332
  return {
329
333
  administrativeUnits: units,
330
- count,
334
+ ...listTotals(units.length, result.total, "administrative units", true),
335
+ complete: true,
331
336
  help: [
332
337
  `mg-axi entra administrative-unit list --filter <odata-filter> ${profileHint(profileName)}`,
333
338
  "0 administrative units matched; the absence of results is the answer, not an error",
334
339
  ],
335
340
  };
336
341
  }
337
- return { administrativeUnits: units, count, help: [...truncationHints, showHint] };
342
+ return { administrativeUnits: units, ...listTotals(units.length, result.total, "administrative units", true), complete: true, help: [...truncationHints, showHint] };
338
343
  }
339
344
  export async function showAdministrativeUnit(session, flags, profile, operation, help, profileName) {
340
345
  const { select, fields } = selectedFields(flags, KNOWN_AUS, KNOWN_AU_FIELDS, DEFAULT_AU_SHOW_SELECT, help);
@@ -390,16 +395,18 @@ export async function listAdministrativeUnitMembers(session, flags, profile, ope
390
395
  if (!result.complete) {
391
396
  return {
392
397
  members: rows,
393
- count: { returned: rows.length, complete: false, reason: result.reason },
398
+ ...listTotals(rows.length, result.total, "members", false),
399
+ complete: false,
400
+ reason: result.reason,
394
401
  cursor: result.cursor,
395
402
  help: [...truncationHints, ...limitedHints, hiddenHint, resumeHint(profileName)],
396
403
  };
397
404
  }
398
- const count = { returned: rows.length, complete: true };
399
405
  if (!rows.length) {
400
406
  return {
401
407
  members: rows,
402
- count,
408
+ ...listTotals(rows.length, result.total, "members", true),
409
+ complete: true,
403
410
  help: [
404
411
  ...limitedHints,
405
412
  hiddenHint,
@@ -407,5 +414,5 @@ export async function listAdministrativeUnitMembers(session, flags, profile, ope
407
414
  ],
408
415
  };
409
416
  }
410
- return { members: rows, count, help: [...truncationHints, ...limitedHints, hiddenHint] };
417
+ return { members: rows, ...listTotals(rows.length, result.total, "members", true), complete: true, help: [...truncationHints, ...limitedHints, hiddenHint] };
411
418
  }