@knowttl/mg-axi 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +251 -77
  2. package/dist/api.js +253 -8
  3. package/dist/bin/mg-axi-hook.js +31 -0
  4. package/dist/catalogue.js +693 -2
  5. package/dist/cli.js +155 -17
  6. package/dist/docs.js +175 -46
  7. package/dist/doctor.js +2 -2
  8. package/dist/entra-access-reviews.js +11 -4
  9. package/dist/entra-apps.js +171 -14
  10. package/dist/entra-audit-logs.js +101 -16
  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 +425 -15
  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-federation-configurations.js +7 -4
  25. package/dist/entra-grants.js +8 -4
  26. package/dist/entra-group-lifecycle.js +13 -8
  27. package/dist/entra-group-pim.js +431 -0
  28. package/dist/entra-groups.js +13 -8
  29. package/dist/entra-identity-providers.js +7 -4
  30. package/dist/entra-lifecycle-workflows.js +152 -13
  31. package/dist/entra-multi-tenant-organization.js +7 -4
  32. package/dist/entra-on-premises-synchronization.js +6 -4
  33. package/dist/entra-organization.js +13 -8
  34. package/dist/entra-risk-prevention.js +442 -0
  35. package/dist/entra-risk.js +224 -9
  36. package/dist/entra-roles.js +204 -12
  37. package/dist/entra-subscriptions.js +6 -4
  38. package/dist/entra-terms-of-use.js +13 -8
  39. package/dist/entra-users.js +7 -4
  40. package/dist/graph-session.js +30 -5
  41. package/dist/hook.js +84 -0
  42. package/dist/list-totals.js +52 -0
  43. package/dist/setup.js +9 -1
  44. package/docs/coverage.md +743 -610
  45. package/inventory/operations.json +2 -2
  46. package/inventory/schema.json +1 -1
  47. package/package.json +4 -3
  48. package/skills/mg-axi/SKILL.md +86 -1
@@ -1,8 +1,13 @@
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
5
- // az-style grammar. Operation construction stays beside its command; the
6
+ // az-style grammar, plus the remaining v1.0 named collections under
7
+ // `identity/conditionalAccess`: authentication-strength policies, each
8
+ // policy's combination configurations, authentication method modes,
9
+ // templates, authentication context class references and the deleted
10
+ // policy and named-location collections. Operation construction stays beside its command; the
6
11
  // shared session owns URLs, credentials, paging, retries and error
7
12
  // translation, and the SDK owns TOON rendering. This module only maps flags
8
13
  // to session calls and projects rows for compact output. No policy mutation
@@ -10,13 +15,45 @@ import { AxiError } from "axi-sdk-js";
10
15
  //
11
16
  // Reviewed against the v1.0 conditionalaccessroot-list-policies,
12
17
  // conditionalaccesspolicy-get, conditionalaccessroot-list-namedlocations and
13
- // countrynamedlocation-get operation documentation on 2026-10-04. All four
14
- // read D/A Policy.Read.All; delegated callers additionally need a supported
15
- // directory role (Conditional Access Administrator, Global Reader, Global
16
- // Secure Access Administrator, Security Administrator or Security Reader).
18
+ // countrynamedlocation-get operation documentation on 2026-10-04, and the
19
+ // authenticationstrengthroot-list-policies,
20
+ // authenticationstrengthpolicy-get,
21
+ // authenticationstrengthpolicy-list-combinationconfigurations,
22
+ // authenticationcombinationconfiguration-get,
23
+ // authenticationstrengthroot-list-authenticationmethodmodes,
24
+ // authenticationmethodmodedetail-get, conditionalaccessroot-list-templates,
25
+ // conditionalaccesstemplate-get, conditionalaccessroot-list-
26
+ // authenticationcontextclassreferences and authenticationcontextclassreference-
27
+ // get operation documentation on 2026-10-06. The deleted policy and
28
+ // named-location collections carry no v1.0 operation-level documentation
29
+ // page; their review follows the beta policyDeletableItem list/get contract
30
+ // for conditional-access objects with the conditionalAccessPolicy,
31
+ // namedLocation and policyDeletableItem resource references.
32
+ // Policy and named-location reads need D/A Policy.Read.All; the
33
+ // authentication-strength family (policies, combination configurations and
34
+ // authentication method modes) reads D/A Policy.Read.AuthenticationMethod
35
+ // instead, and template reads D/A Policy.Read.All. Delegated callers
36
+ // additionally need a supported directory role (Conditional Access
37
+ // Administrator, Security Administrator or Security Reader for the
38
+ // strength family; those plus Global Reader and Global Secure Access
39
+ // Administrator for policies, named locations and templates). The
40
+ // authentication context class references read D/A
41
+ // AuthenticationContext.Read.All with the same broad delegated role set.
42
+ // Deleted policies and named locations read D/A Policy.Read.All with the
43
+ // same broad delegated role set; restore is a beta-only mutation and stays
44
+ // out of scope.
17
45
  // Conditional Access needs P1; risk-based Conditional Access needs P2. The
18
46
  // reviewed raw surface in src/api.ts carries exactly these routes, fields
19
47
  // and access choices; the named commands below reuse that contract.
48
+ //
49
+ // Deferred with reason: the parameterless
50
+ // `authenticationStrength/policies/{id}/usage()` binds no placeholder, so
51
+ // it never passes through the session's validated function-argument
52
+ // binding (FUNCTION_ARGUMENT_BINDINGS covers only named-parameter
53
+ // segments); widening the request-path guard for one function is out of
54
+ // scope. Authentication context class references land here: the former
55
+ // needs an AuthenticationContext.Read.All allowlist addition, now carried
56
+ // in the session's READ_SCOPES set.
20
57
  // Every policy property this slice may request or display, matching the
21
58
  // reviewed raw surface. Anything else fails before credentials.
22
59
  export const KNOWN_POLICY_FIELDS = [
@@ -43,6 +80,61 @@ export const KNOWN_LOCATION_FIELDS = [
43
80
  ];
44
81
  const POLICY_KNOWN = new Set(KNOWN_POLICY_FIELDS);
45
82
  const LOCATION_KNOWN = new Set(KNOWN_LOCATION_FIELDS);
83
+ // Every authentication-strength policy property this slice may request or
84
+ // display, matching the reviewed raw surface.
85
+ export const KNOWN_STRENGTH_FIELDS = [
86
+ "id",
87
+ "displayName",
88
+ "description",
89
+ "policyType",
90
+ "requirementsSatisfied",
91
+ "allowedCombinations",
92
+ "createdDateTime",
93
+ "modifiedDateTime",
94
+ ];
95
+ // Every combination-configuration property this slice may request or
96
+ // display: the base type carries only an identifier and the combinations
97
+ // it applies to; subtype detail rides on @odata.type instead.
98
+ export const KNOWN_COMBO_FIELDS = [
99
+ "id",
100
+ "appliesToCombinations",
101
+ ];
102
+ // Every authentication method mode property this slice may request or
103
+ // display, matching the reviewed raw surface.
104
+ export const KNOWN_MODE_FIELDS = [
105
+ "id",
106
+ "displayName",
107
+ "authenticationMethod",
108
+ ];
109
+ // Every Conditional Access template property this slice may request or
110
+ // display, matching the reviewed raw surface.
111
+ export const KNOWN_TEMPLATE_FIELDS = [
112
+ "id",
113
+ "name",
114
+ "description",
115
+ "scenarios",
116
+ "details",
117
+ ];
118
+ // Every authentication context class reference property this slice may
119
+ // request or display, matching the reviewed v1.0 resource shape.
120
+ export const KNOWN_AUTH_CONTEXT_FIELDS = [
121
+ "id",
122
+ "displayName",
123
+ "description",
124
+ "isAvailable",
125
+ ];
126
+ // Deleted policies and named locations return the underlying policy and
127
+ // location objects with the soft-delete timestamp the policyDeletableItem
128
+ // base type adds, so the reviewed sets extend the live ones.
129
+ export const KNOWN_DELETED_POLICY_FIELDS = [...KNOWN_POLICY_FIELDS, "deletedDateTime"];
130
+ export const KNOWN_DELETED_LOCATION_FIELDS = [...KNOWN_LOCATION_FIELDS, "deletedDateTime"];
131
+ const STRENGTH_KNOWN = new Set(KNOWN_STRENGTH_FIELDS);
132
+ const COMBO_KNOWN = new Set(KNOWN_COMBO_FIELDS);
133
+ const MODE_KNOWN = new Set(KNOWN_MODE_FIELDS);
134
+ const TEMPLATE_KNOWN = new Set(KNOWN_TEMPLATE_FIELDS);
135
+ const AUTH_CONTEXT_KNOWN = new Set(KNOWN_AUTH_CONTEXT_FIELDS);
136
+ const DELETED_POLICY_KNOWN = new Set(KNOWN_DELETED_POLICY_FIELDS);
137
+ const DELETED_LOCATION_KNOWN = new Set(KNOWN_DELETED_LOCATION_FIELDS);
46
138
  // @odata.type is preserved on named-location rows without being selectable:
47
139
  // it names the location kind (ipNamedLocation versus countryNamedLocation).
48
140
  const LOCATION_TYPE_PROPERTY = "@odata.type";
@@ -57,8 +149,44 @@ const DEFAULT_POLICY_SHOW_SELECT = [...KNOWN_POLICY_FIELDS];
57
149
  const DEFAULT_LOCATION_LIST_SELECT = ["id", "displayName"];
58
150
  // Show rows: the full reviewed location set.
59
151
  const DEFAULT_LOCATION_SHOW_SELECT = [...KNOWN_LOCATION_FIELDS];
60
- // Policy.Read.All covers both families in both modes.
152
+ // Compact strength rows: identifier, name and the built-in/custom kind.
153
+ const DEFAULT_STRENGTH_LIST_SELECT = ["id", "displayName", "policyType"];
154
+ // Fetch the full reviewed strength set for inspection.
155
+ const DEFAULT_STRENGTH_SHOW_SELECT = [...KNOWN_STRENGTH_FIELDS];
156
+ // Compact combination rows: the identifier; the kind rides as @odata.type.
157
+ const DEFAULT_COMBO_LIST_SELECT = ["id"];
158
+ // Show rows: the full reviewed base set.
159
+ const DEFAULT_COMBO_SHOW_SELECT = [...KNOWN_COMBO_FIELDS];
160
+ // Compact mode rows: identifier and name.
161
+ const DEFAULT_MODE_LIST_SELECT = ["id", "displayName"];
162
+ // Show rows: the full reviewed mode set.
163
+ const DEFAULT_MODE_SHOW_SELECT = [...KNOWN_MODE_FIELDS];
164
+ // Compact template rows: identifier and name.
165
+ const DEFAULT_TEMPLATE_LIST_SELECT = ["id", "name"];
166
+ // Show rows: the full reviewed template set.
167
+ const DEFAULT_TEMPLATE_SHOW_SELECT = [...KNOWN_TEMPLATE_FIELDS];
168
+ // Compact authentication-context rows: identifier and name.
169
+ const DEFAULT_AUTH_CONTEXT_LIST_SELECT = ["id", "displayName"];
170
+ // Show rows: the full reviewed authentication-context set.
171
+ const DEFAULT_AUTH_CONTEXT_SHOW_SELECT = [...KNOWN_AUTH_CONTEXT_FIELDS];
172
+ // Compact deleted-policy rows: identifier, name, enforcement state and the
173
+ // soft-delete timestamp, which is the point of the deleted collection.
174
+ const DEFAULT_DELETED_POLICY_LIST_SELECT = ["id", "displayName", "state", "deletedDateTime"];
175
+ // Show rows: the full reviewed deleted-policy set.
176
+ const DEFAULT_DELETED_POLICY_SHOW_SELECT = [...KNOWN_DELETED_POLICY_FIELDS];
177
+ // Compact deleted-location rows: identifier, name and deletion timestamp;
178
+ // the kind rides as @odata.type.
179
+ const DEFAULT_DELETED_LOCATION_LIST_SELECT = ["id", "displayName", "deletedDateTime"];
180
+ // Show rows: the full reviewed deleted-location set.
181
+ const DEFAULT_DELETED_LOCATION_SHOW_SELECT = [...KNOWN_DELETED_LOCATION_FIELDS];
182
+ // Policy.Read.All covers policies, named locations and templates in both modes.
61
183
  export const DEFAULT_DELEGATED_SCOPES = ["https://graph.microsoft.com/Policy.Read.All"];
184
+ // Policy.Read.AuthenticationMethod is the least-privileged read for the
185
+ // authentication-strength family in both modes.
186
+ export const DEFAULT_STRENGTH_SCOPES = ["https://graph.microsoft.com/Policy.Read.AuthenticationMethod"];
187
+ // AuthenticationContext.Read.All is the least-privileged read for the
188
+ // authentication context class references in both modes.
189
+ export const DEFAULT_AUTH_CONTEXT_SCOPES = ["https://graph.microsoft.com/AuthenticationContext.Read.All"];
62
190
  const TRUNCATE_AT = 500;
63
191
  function fieldList(raw, flag, known, knownList, noun, help) {
64
192
  const parts = String(raw)
@@ -80,7 +208,7 @@ function fieldList(raw, flag, known, knownList, noun, help) {
80
208
  }
81
209
  return fields;
82
210
  }
83
- function scopesFor(flags, profile, help) {
211
+ function scopesFor(flags, profile, help, defaults = DEFAULT_DELEGATED_SCOPES) {
84
212
  if (profile.mode === "application") {
85
213
  if (flags.scopes !== undefined) {
86
214
  throw new AxiError("Application profiles use the configured Graph .default audience; delegated scopes are unavailable", "VALIDATION_ERROR", [help, "mg-axi profile show --profile <name>"]);
@@ -88,7 +216,7 @@ function scopesFor(flags, profile, help) {
88
216
  return undefined;
89
217
  }
90
218
  if (flags.scopes === undefined)
91
- return [...DEFAULT_DELEGATED_SCOPES];
219
+ return [...defaults];
92
220
  const scopes = String(flags.scopes)
93
221
  .split(",")
94
222
  .map(scope => scope.trim())
@@ -179,13 +307,48 @@ const LOCATION_DENIAL_HINTS = [
179
307
  "Named-location reads need Policy.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.All for application access",
180
308
  "Conditional Access and named locations need P1; risk-based Conditional Access needs P2",
181
309
  ];
310
+ // The authentication-strength family (strength policies, combination
311
+ // configurations, authentication method modes) reads the least-privileged
312
+ // Policy.Read.AuthenticationMethod instead of Policy.Read.All, with the
313
+ // narrower Conditional Access Administrator / Security Administrator /
314
+ // Security Reader role set.
315
+ const STRENGTH_DENIAL_HINTS = [
316
+ "Authentication-strength reads need Policy.Read.AuthenticationMethod plus a supported directory role: Conditional Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.AuthenticationMethod for application access",
317
+ "Conditional Access needs P1",
318
+ ];
319
+ const TEMPLATE_DENIAL_HINTS = [
320
+ "Conditional Access template reads need Policy.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.All for application access",
321
+ "Conditional Access needs P1",
322
+ ];
323
+ // Authentication-context reads carry the least-privileged
324
+ // AuthenticationContext.Read.All with the broad Conditional Access role
325
+ // set shared with policies, named locations and templates.
326
+ const AUTH_CONTEXT_DENIAL_HINTS = [
327
+ "Authentication-context reads need AuthenticationContext.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented AuthenticationContext.Read.All for application access",
328
+ "Conditional Access needs P1",
329
+ ];
330
+ // Deleted policy and named-location reads keep Policy.Read.All with the
331
+ // broad Conditional Access role set; restore is a beta-only mutation, so
332
+ // denial guidance never offers it as a next step.
333
+ const DELETED_POLICY_DENIAL_HINTS = [
334
+ "Deleted-policy reads need Policy.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.All for application access",
335
+ "Deleted policies restore within 30 days; restore is a beta-only mutation outside mg-axi scope",
336
+ "Conditional Access needs P1",
337
+ ];
338
+ const DELETED_LOCATION_DENIAL_HINTS = [
339
+ "Deleted named-location reads need Policy.Read.All plus a supported directory role: Conditional Access Administrator, Global Reader, Global Secure Access Administrator, Security Administrator or Security Reader for delegated access, or admin-consented Policy.Read.All for application access",
340
+ "Deleted named locations restore within 30 days; restore is a beta-only mutation outside mg-axi scope",
341
+ "Conditional Access and named locations need P1",
342
+ ];
182
343
  const POLICY_LIST = {
183
344
  command: "entra conditional-access policy list",
184
345
  key: "policies",
185
346
  noun: "policy",
347
+ plural: "policies",
186
348
  known: POLICY_KNOWN,
187
349
  knownList: KNOWN_POLICY_FIELDS,
188
350
  defaultSelect: DEFAULT_POLICY_LIST_SELECT,
351
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
189
352
  denialHints: POLICY_DENIAL_HINTS,
190
353
  showHint: "mg-axi entra conditional-access policy show --id <policy-id>",
191
354
  emptyHint: "0 conditional-access policies matched; the absence of results is the answer, not an error",
@@ -195,9 +358,11 @@ const LOCATION_LIST = {
195
358
  command: "entra conditional-access named-location list",
196
359
  key: "namedLocations",
197
360
  noun: "named-location",
361
+ plural: "named locations",
198
362
  known: LOCATION_KNOWN,
199
363
  knownList: KNOWN_LOCATION_FIELDS,
200
364
  defaultSelect: DEFAULT_LOCATION_LIST_SELECT,
365
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
201
366
  denialHints: LOCATION_DENIAL_HINTS,
202
367
  showHint: "mg-axi entra conditional-access named-location show --id <named-location-id>",
203
368
  emptyHint: "0 named locations matched; the absence of results is the answer, not an error",
@@ -209,12 +374,19 @@ async function listCollection(shape, session, flags, profile, operation, help, p
209
374
  throw new AxiError("--cursor needs the opaque cursor from a partial result", "VALIDATION_ERROR", [help]);
210
375
  const savedSelect = cursor === undefined ? undefined : session.cursorQuery(operation, cursor).$select;
211
376
  const { select, fields } = selectedFields(flags, savedSelect === undefined ? shape.defaultSelect : fieldList(savedSelect, "select", shape.known, shape.knownList, shape.noun, help), shape.known, shape.knownList, shape.noun, help);
212
- const scopes = scopesFor(flags, profile, help);
377
+ const scopes = scopesFor(flags, profile, help, shape.defaultScopes);
213
378
  const full = flags.full === true;
214
379
  const query = { $select: select.join(",") };
215
380
  if (flags.filter !== undefined)
216
381
  query.$filter = String(flags.filter);
217
382
  const args = { profile, operation, query, scopes };
383
+ if (shape.parent !== undefined) {
384
+ const raw = flags[shape.parent.flag];
385
+ if (raw === undefined || !String(raw).trim()) {
386
+ throw new AxiError(`--${shape.parent.flag} needs the ${shape.parent.label}`, "VALIDATION_ERROR", [help]);
387
+ }
388
+ args.params = { [shape.parent.param]: String(raw) };
389
+ }
218
390
  if (cursor !== undefined)
219
391
  args.cursor = cursor;
220
392
  if (flags.all === true) {
@@ -242,23 +414,25 @@ async function listCollection(shape, session, flags, profile, operation, help, p
242
414
  if (!result.complete) {
243
415
  return {
244
416
  [shape.key]: rows,
245
- count: { returned: rows.length, complete: false, reason: result.reason },
417
+ ...listTotals(rows.length, result.total, shape.plural, false),
418
+ complete: false,
419
+ reason: result.reason,
246
420
  cursor: result.cursor,
247
421
  help: [...truncationHints, `Resume losslessly with the same flags plus --cursor - ${profileHint(profileName)} and supply the returned cursor on stdin`, showHint],
248
422
  };
249
423
  }
250
- const count = { returned: rows.length, complete: true };
251
424
  if (!rows.length) {
252
425
  return {
253
426
  [shape.key]: rows,
254
- count,
427
+ ...listTotals(rows.length, result.total, shape.plural, true),
428
+ complete: true,
255
429
  help: [
256
430
  `mg-axi ${shape.command} --filter <odata-filter> ${profileHint(profileName)}`,
257
431
  shape.emptyHint,
258
432
  ],
259
433
  };
260
434
  }
261
- return { [shape.key]: rows, count, help: [...truncationHints, showHint] };
435
+ return { [shape.key]: rows, ...listTotals(rows.length, result.total, shape.plural, true), complete: true, help: [...truncationHints, showHint] };
262
436
  }
263
437
  const POLICY_SHOW = {
264
438
  command: "entra conditional-access policy show",
@@ -268,6 +442,7 @@ const POLICY_SHOW = {
268
442
  known: POLICY_KNOWN,
269
443
  knownList: KNOWN_POLICY_FIELDS,
270
444
  defaultSelect: DEFAULT_POLICY_SHOW_SELECT,
445
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
271
446
  denialHints: POLICY_DENIAL_HINTS,
272
447
  preserveType: false,
273
448
  };
@@ -279,17 +454,26 @@ const LOCATION_SHOW = {
279
454
  known: LOCATION_KNOWN,
280
455
  knownList: KNOWN_LOCATION_FIELDS,
281
456
  defaultSelect: DEFAULT_LOCATION_SHOW_SELECT,
457
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
282
458
  denialHints: LOCATION_DENIAL_HINTS,
283
459
  preserveType: true,
284
460
  };
285
461
  async function showSingle(shape, session, flags, profile, operation, help, profileName) {
286
462
  const { select, fields } = selectedFields(flags, shape.defaultSelect, shape.known, shape.knownList, shape.noun, help);
287
- const scopes = scopesFor(flags, profile, help);
463
+ const scopes = scopesFor(flags, profile, help, shape.defaultScopes);
288
464
  const full = flags.full === true;
465
+ const params = { [shape.param]: String(flags.id) };
466
+ if (shape.parent !== undefined) {
467
+ const raw = flags[shape.parent.flag];
468
+ if (raw === undefined || !String(raw).trim()) {
469
+ throw new AxiError(`--${shape.parent.flag} needs the ${shape.parent.label}`, "VALIDATION_ERROR", [help]);
470
+ }
471
+ params[shape.parent.param] = String(raw);
472
+ }
289
473
  const raw = await withGuidance(shape.denialHints, () => session.execute({
290
474
  profile,
291
475
  operation,
292
- params: { [shape.param]: String(flags.id) },
476
+ params,
293
477
  query: { $select: select.join(",") },
294
478
  scopes,
295
479
  }));
@@ -315,3 +499,229 @@ export async function listNamedLocations(session, flags, profile, operation, hel
315
499
  export async function showNamedLocation(session, flags, profile, operation, help, profileName) {
316
500
  return showSingle(LOCATION_SHOW, session, flags, profile, operation, help, profileName);
317
501
  }
502
+ const STRENGTH_LIST = {
503
+ command: "entra conditional-access auth-strength-policy list",
504
+ key: "authStrengthPolicies",
505
+ noun: "auth-strength-policy",
506
+ known: STRENGTH_KNOWN,
507
+ knownList: KNOWN_STRENGTH_FIELDS,
508
+ defaultSelect: DEFAULT_STRENGTH_LIST_SELECT,
509
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
510
+ denialHints: STRENGTH_DENIAL_HINTS,
511
+ showHint: "mg-axi entra conditional-access auth-strength-policy show --id <auth-strength-policy-id>",
512
+ emptyHint: "0 authentication-strength policies matched; the absence of results is the answer, not an error",
513
+ preserveType: false,
514
+ plural: "auth-strength policies",
515
+ };
516
+ const COMBO_LIST = {
517
+ command: "entra conditional-access combination-configuration list",
518
+ key: "combinationConfigurations",
519
+ noun: "combination-configuration",
520
+ known: COMBO_KNOWN,
521
+ knownList: KNOWN_COMBO_FIELDS,
522
+ defaultSelect: DEFAULT_COMBO_LIST_SELECT,
523
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
524
+ denialHints: STRENGTH_DENIAL_HINTS,
525
+ showHint: "mg-axi entra conditional-access combination-configuration show --policy <auth-strength-policy-id> --id <combination-configuration-id>",
526
+ emptyHint: "0 combination configurations matched; the absence of results is the answer, not an error",
527
+ preserveType: true,
528
+ parent: { param: "authenticationStrengthPolicy-id", flag: "policy", label: "authentication-strength policy ID owning the combination configurations" },
529
+ plural: "combination configurations",
530
+ };
531
+ const MODE_LIST = {
532
+ command: "entra conditional-access auth-method-mode list",
533
+ key: "authMethodModes",
534
+ noun: "auth-method-mode",
535
+ known: MODE_KNOWN,
536
+ knownList: KNOWN_MODE_FIELDS,
537
+ defaultSelect: DEFAULT_MODE_LIST_SELECT,
538
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
539
+ denialHints: STRENGTH_DENIAL_HINTS,
540
+ showHint: "mg-axi entra conditional-access auth-method-mode show --id <auth-method-mode-id>",
541
+ emptyHint: "0 authentication method modes matched; the absence of results is the answer, not an error",
542
+ preserveType: false,
543
+ plural: "auth-method modes",
544
+ };
545
+ const TEMPLATE_LIST = {
546
+ command: "entra conditional-access template list",
547
+ key: "templates",
548
+ noun: "template",
549
+ known: TEMPLATE_KNOWN,
550
+ knownList: KNOWN_TEMPLATE_FIELDS,
551
+ defaultSelect: DEFAULT_TEMPLATE_LIST_SELECT,
552
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
553
+ denialHints: TEMPLATE_DENIAL_HINTS,
554
+ showHint: "mg-axi entra conditional-access template show --id <template-id>",
555
+ emptyHint: "0 Conditional Access templates matched; the absence of results is the answer, not an error",
556
+ preserveType: false,
557
+ plural: "templates",
558
+ };
559
+ const STRENGTH_SHOW = {
560
+ command: "entra conditional-access auth-strength-policy show",
561
+ key: "authStrengthPolicy",
562
+ param: "authenticationStrengthPolicy-id",
563
+ noun: "auth-strength-policy",
564
+ known: STRENGTH_KNOWN,
565
+ knownList: KNOWN_STRENGTH_FIELDS,
566
+ defaultSelect: DEFAULT_STRENGTH_SHOW_SELECT,
567
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
568
+ denialHints: STRENGTH_DENIAL_HINTS,
569
+ preserveType: false,
570
+ };
571
+ const COMBO_SHOW = {
572
+ command: "entra conditional-access combination-configuration show",
573
+ key: "combinationConfiguration",
574
+ param: "authenticationCombinationConfiguration-id",
575
+ noun: "combination-configuration",
576
+ known: COMBO_KNOWN,
577
+ knownList: KNOWN_COMBO_FIELDS,
578
+ defaultSelect: DEFAULT_COMBO_SHOW_SELECT,
579
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
580
+ denialHints: STRENGTH_DENIAL_HINTS,
581
+ preserveType: true,
582
+ parent: { param: "authenticationStrengthPolicy-id", flag: "policy", label: "authentication-strength policy ID owning the combination configuration" },
583
+ };
584
+ const MODE_SHOW = {
585
+ command: "entra conditional-access auth-method-mode show",
586
+ key: "authMethodMode",
587
+ param: "authenticationMethodModeDetail-id",
588
+ noun: "auth-method-mode",
589
+ known: MODE_KNOWN,
590
+ knownList: KNOWN_MODE_FIELDS,
591
+ defaultSelect: DEFAULT_MODE_SHOW_SELECT,
592
+ defaultScopes: DEFAULT_STRENGTH_SCOPES,
593
+ denialHints: STRENGTH_DENIAL_HINTS,
594
+ preserveType: false,
595
+ };
596
+ const TEMPLATE_SHOW = {
597
+ command: "entra conditional-access template show",
598
+ key: "template",
599
+ param: "conditionalAccessTemplate-id",
600
+ noun: "template",
601
+ known: TEMPLATE_KNOWN,
602
+ knownList: KNOWN_TEMPLATE_FIELDS,
603
+ defaultSelect: DEFAULT_TEMPLATE_SHOW_SELECT,
604
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
605
+ denialHints: TEMPLATE_DENIAL_HINTS,
606
+ preserveType: false,
607
+ };
608
+ export async function listAuthStrengthPolicies(session, flags, profile, operation, help, profileName) {
609
+ return listCollection(STRENGTH_LIST, session, flags, profile, operation, help, profileName);
610
+ }
611
+ export async function showAuthStrengthPolicy(session, flags, profile, operation, help, profileName) {
612
+ return showSingle(STRENGTH_SHOW, session, flags, profile, operation, help, profileName);
613
+ }
614
+ export async function listCombinationConfigurations(session, flags, profile, operation, help, profileName) {
615
+ return listCollection(COMBO_LIST, session, flags, profile, operation, help, profileName);
616
+ }
617
+ export async function showCombinationConfiguration(session, flags, profile, operation, help, profileName) {
618
+ return showSingle(COMBO_SHOW, session, flags, profile, operation, help, profileName);
619
+ }
620
+ export async function listAuthMethodModes(session, flags, profile, operation, help, profileName) {
621
+ return listCollection(MODE_LIST, session, flags, profile, operation, help, profileName);
622
+ }
623
+ export async function showAuthMethodMode(session, flags, profile, operation, help, profileName) {
624
+ return showSingle(MODE_SHOW, session, flags, profile, operation, help, profileName);
625
+ }
626
+ export async function listTemplates(session, flags, profile, operation, help, profileName) {
627
+ return listCollection(TEMPLATE_LIST, session, flags, profile, operation, help, profileName);
628
+ }
629
+ export async function showTemplate(session, flags, profile, operation, help, profileName) {
630
+ return showSingle(TEMPLATE_SHOW, session, flags, profile, operation, help, profileName);
631
+ }
632
+ const AUTH_CONTEXT_LIST = {
633
+ command: "entra conditional-access auth-context list",
634
+ key: "authContexts",
635
+ noun: "auth-context",
636
+ plural: "auth contexts",
637
+ known: AUTH_CONTEXT_KNOWN,
638
+ knownList: KNOWN_AUTH_CONTEXT_FIELDS,
639
+ defaultSelect: DEFAULT_AUTH_CONTEXT_LIST_SELECT,
640
+ defaultScopes: DEFAULT_AUTH_CONTEXT_SCOPES,
641
+ denialHints: AUTH_CONTEXT_DENIAL_HINTS,
642
+ showHint: "mg-axi entra conditional-access auth-context show --id <auth-context-id>",
643
+ emptyHint: "0 authentication context class references matched; the absence of results is the answer, not an error",
644
+ preserveType: false,
645
+ };
646
+ const DELETED_POLICY_LIST = {
647
+ command: "entra conditional-access deleted-policy list",
648
+ key: "deletedPolicies",
649
+ noun: "deleted-policy",
650
+ plural: "deleted policies",
651
+ known: DELETED_POLICY_KNOWN,
652
+ knownList: KNOWN_DELETED_POLICY_FIELDS,
653
+ defaultSelect: DEFAULT_DELETED_POLICY_LIST_SELECT,
654
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
655
+ denialHints: DELETED_POLICY_DENIAL_HINTS,
656
+ showHint: "mg-axi entra conditional-access deleted-policy show --id <deleted-policy-id>",
657
+ emptyHint: "0 deleted policies matched; the absence of results is the answer, not an error",
658
+ preserveType: false,
659
+ };
660
+ const DELETED_LOCATION_LIST = {
661
+ command: "entra conditional-access deleted-named-location list",
662
+ key: "deletedNamedLocations",
663
+ noun: "deleted-named-location",
664
+ plural: "deleted named locations",
665
+ known: DELETED_LOCATION_KNOWN,
666
+ knownList: KNOWN_DELETED_LOCATION_FIELDS,
667
+ defaultSelect: DEFAULT_DELETED_LOCATION_LIST_SELECT,
668
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
669
+ denialHints: DELETED_LOCATION_DENIAL_HINTS,
670
+ showHint: "mg-axi entra conditional-access deleted-named-location show --id <deleted-named-location-id>",
671
+ emptyHint: "0 deleted named locations matched; the absence of results is the answer, not an error",
672
+ preserveType: true,
673
+ };
674
+ const AUTH_CONTEXT_SHOW = {
675
+ command: "entra conditional-access auth-context show",
676
+ key: "authContext",
677
+ param: "authenticationContextClassReference-id",
678
+ noun: "auth-context",
679
+ known: AUTH_CONTEXT_KNOWN,
680
+ knownList: KNOWN_AUTH_CONTEXT_FIELDS,
681
+ defaultSelect: DEFAULT_AUTH_CONTEXT_SHOW_SELECT,
682
+ defaultScopes: DEFAULT_AUTH_CONTEXT_SCOPES,
683
+ denialHints: AUTH_CONTEXT_DENIAL_HINTS,
684
+ preserveType: false,
685
+ };
686
+ const DELETED_POLICY_SHOW = {
687
+ command: "entra conditional-access deleted-policy show",
688
+ key: "deletedPolicy",
689
+ param: "conditionalAccessPolicy-id",
690
+ noun: "deleted-policy",
691
+ known: DELETED_POLICY_KNOWN,
692
+ knownList: KNOWN_DELETED_POLICY_FIELDS,
693
+ defaultSelect: DEFAULT_DELETED_POLICY_SHOW_SELECT,
694
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
695
+ denialHints: DELETED_POLICY_DENIAL_HINTS,
696
+ preserveType: false,
697
+ };
698
+ const DELETED_LOCATION_SHOW = {
699
+ command: "entra conditional-access deleted-named-location show",
700
+ key: "deletedNamedLocation",
701
+ param: "namedLocation-id",
702
+ noun: "deleted-named-location",
703
+ known: DELETED_LOCATION_KNOWN,
704
+ knownList: KNOWN_DELETED_LOCATION_FIELDS,
705
+ defaultSelect: DEFAULT_DELETED_LOCATION_SHOW_SELECT,
706
+ defaultScopes: DEFAULT_DELEGATED_SCOPES,
707
+ denialHints: DELETED_LOCATION_DENIAL_HINTS,
708
+ preserveType: true,
709
+ };
710
+ export async function listAuthContexts(session, flags, profile, operation, help, profileName) {
711
+ return listCollection(AUTH_CONTEXT_LIST, session, flags, profile, operation, help, profileName);
712
+ }
713
+ export async function showAuthContext(session, flags, profile, operation, help, profileName) {
714
+ return showSingle(AUTH_CONTEXT_SHOW, session, flags, profile, operation, help, profileName);
715
+ }
716
+ export async function listDeletedPolicies(session, flags, profile, operation, help, profileName) {
717
+ return listCollection(DELETED_POLICY_LIST, session, flags, profile, operation, help, profileName);
718
+ }
719
+ export async function showDeletedPolicy(session, flags, profile, operation, help, profileName) {
720
+ return showSingle(DELETED_POLICY_SHOW, session, flags, profile, operation, help, profileName);
721
+ }
722
+ export async function listDeletedNamedLocations(session, flags, profile, operation, help, profileName) {
723
+ return listCollection(DELETED_LOCATION_LIST, session, flags, profile, operation, help, profileName);
724
+ }
725
+ export async function showDeletedNamedLocation(session, flags, profile, operation, help, profileName) {
726
+ return showSingle(DELETED_LOCATION_SHOW, session, flags, profile, operation, help, profileName);
727
+ }
@@ -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);