From d6bcca6dcba08ecd2221d5e01c8fb0ff79b375b1 Mon Sep 17 00:00:00 2001 From: Martin Machacek Date: Fri, 5 Jun 2026 12:29:05 +0200 Subject: [PATCH] Adds 'entra agent blueprint list' command. Closes #7252 --- .../cmd/entra/agent/agent-blueprint-list.mdx | 164 ++++++++++++++++ docs/src/config/sidebars.ts | 9 + eslint.config.mjs | 2 + src/config.ts | 1 + src/m365/entra/commands.ts | 1 + .../agent/agent-blueprint-list.spec.ts | 185 ++++++++++++++++++ .../commands/agent/agent-blueprint-list.ts | 61 ++++++ 7 files changed, 423 insertions(+) create mode 100644 docs/docs/cmd/entra/agent/agent-blueprint-list.mdx create mode 100644 src/m365/entra/commands/agent/agent-blueprint-list.spec.ts create mode 100644 src/m365/entra/commands/agent/agent-blueprint-list.ts diff --git a/docs/docs/cmd/entra/agent/agent-blueprint-list.mdx b/docs/docs/cmd/entra/agent/agent-blueprint-list.mdx new file mode 100644 index 00000000000..7818fd83cb8 --- /dev/null +++ b/docs/docs/cmd/entra/agent/agent-blueprint-list.mdx @@ -0,0 +1,164 @@ +import Global from '../../_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# entra agent blueprint list + +Retrieves a list of templates defining the agent identity type + +## Usage + +```sh +m365 entra agent blueprint list [options] +``` + +## Options + +```md definition-list +`-p, --properties [properties]` +: Comma-separated list of properties to retrieve. +``` + + + +## Remarks + +Using the `--properties` option, you can specify a comma-separated list of agent blueprint properties to retrieve from the Microsoft Graph. If you don't specify any properties, the command will output the default properties returned by Graph. + +## Permissions + + + + + | Resource | Permissions | + |-----------------|---------------------------------| + | Microsoft Graph | AgentIdentityBlueprint.Read.All | + + + + + | Resource | Permissions | + |-----------------|---------------------------------| + | Microsoft Graph | AgentIdentityBlueprint.Read.All | + + + + +## Examples + +Retrieve all agent blueprint templates + +```sh +m365 entra agent blueprint list +``` + +Retrieve id and name of agent blueprint templates + +```sh +m365 entra agent blueprint list --properties 'id,displayName' +``` + +## Response + + + + + ```json + [ + { + "appId": "0e81af7f-b058-470a-84be-3a4f5a8014ca", + "createdByAppId": "14d82eec-204b-4c2f-b7e8-296a70dab67e", + "createdDateTime": "2025-11-20T07:47:39Z", + "defaultRedirectUri": null, + "description": null, + "disabledByMicrosoftStatus": null, + "displayName": "OcnAgentBluePrint02", + "groupMembershipClaims": null, + "identifierUris": [], + "managerApplications": [], + "publisherDomain": "contoso.onmicrosoft.com", + "serviceManagementReference": null, + "signInAudience": "AzureADMyOrg", + "tags": [], + "tokenEncryptionKeyId": null, + "uniqueName": null, + "id": "0e81af7f-b058-470a-84be-3a4f5a8014ca", + "certification": null, + "optionalClaims": null, + "api": { + "acceptMappedClaims": null, + "knownClientApplications": [], + "requestedAccessTokenVersion": 2, + "oauth2PermissionScopes": [], + "preAuthorizedApplications": [] + }, + "appRoles": [], + "info": { + "logoUrl": null, + "marketingUrl": null, + "privacyStatementUrl": null, + "supportUrl": null, + "termsOfServiceUrl": null + }, + "keyCredentials": [], + "passwordCredentials": [], + "requiredResourceAccess": [], + "verifiedPublisher": { + "displayName": null, + "verifiedPublisherId": null, + "addedDateTime": null + }, + "web": { + "homePageUrl": null, + "logoutUrl": null, + "redirectUris": [], + "implicitGrantSettings": { + "enableAccessTokenIssuance": false, + "enableIdTokenIssuance": false + }, + "redirectUriSettings": [] + } + } + ] + ``` + + + + + ```text + appId : 0e81af7f-b058-470a-84be-3a4f5a8014ca + displayName: OcnAgentBluePrint02 + id : 0e81af7f-b058-470a-84be-3a4f5a8014ca + ``` + + + + + ```csv + appId,createdByAppId,createdDateTime,defaultRedirectUri,description,disabledByMicrosoftStatus,displayName,groupMembershipClaims,publisherDomain,serviceManagementReference,signInAudience,tokenEncryptionKeyId,uniqueName,id,certification,optionalClaims + 0e81af7f-b058-470a-84be-3a4f5a8014ca,14d82eec-204b-4c2f-b7e8-296a70dab67e,2025-11-20T07:47:39Z,,,,OcnAgentBluePrint02,,contoso.onmicrosoft.com,,AzureADMyOrg,,,0e81af7f-b058-470a-84be-3a4f5a8014ca,, + ``` + + + + + ```md + # entra agent blueprint list --debug "false" --verbose "false" + + Date: 6/5/2026 + + ## OcnAgentBluePrint02 (0e81af7f-b058-470a-84be-3a4f5a8014ca) + + Property | Value + ---------|------- + appId | 0e81af7f-b058-470a-84be-3a4f5a8014ca + createdByAppId | 14d82eec-204b-4c2f-b7e8-296a70dab67e + createdDateTime | 2025-11-20T07:47:39Z + displayName | OcnAgentBluePrint02 + publisherDomain | contoso.onmicrosoft.com + signInAudience | AzureADMyOrg + id | 0e81af7f-b058-470a-84be-3a4f5a8014ca + ``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index ec1463235f8..f1c84074253 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -249,6 +249,15 @@ const sidebars: SidebarsConfig = { } ] }, + { + agent: [ + { + type: 'doc', + label: 'agent blueprint list', + id: 'cmd/entra/agent/agent-blueprint-list' + } + ] + }, { app: [ { diff --git a/eslint.config.mjs b/eslint.config.mjs index 925703e197b..c124b625fd8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -14,6 +14,7 @@ const dictionary = [ 'activations', 'adaptive', 'administrative', + 'agent', 'ai', 'app', 'application', @@ -26,6 +27,7 @@ const dictionary = [ 'autofill', 'azure', 'bin', + 'blueprint', 'brand', 'builder', 'calendar', diff --git a/src/config.ts b/src/config.ts index 8218037c14f..007fead247d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,6 +4,7 @@ export default { allScopes: [ 'https://management.azure.com/user_impersonation', 'https://admin.services.crm.dynamics.com/user_impersonation', + 'https://graph.microsoft.com/AgentIdentityBlueprint.Read.All', 'https://graph.microsoft.com/AppCatalog.ReadWrite.All', 'https://graph.microsoft.com/AuditLog.Read.All', 'https://graph.microsoft.com/Bookings.Read.All', diff --git a/src/m365/entra/commands.ts b/src/m365/entra/commands.ts index 1b9476ec605..b9e0eae333a 100644 --- a/src/m365/entra/commands.ts +++ b/src/m365/entra/commands.ts @@ -10,6 +10,7 @@ export default { ADMINISTRATIVEUNIT_MEMBER_LIST: `${prefix} administrativeunit member list`, ADMINISTRATIVEUNIT_MEMBER_REMOVE: `${prefix} administrativeunit member remove`, ADMINISTRATIVEUNIT_ROLEASSIGNMENT_ADD: `${prefix} administrativeunit roleassignment add`, + AGENT_BLUEPRINT_LIST: `${prefix} agent blueprint list`, APP_ADD: `${prefix} app add`, APP_GET: `${prefix} app get`, APP_LIST: `${prefix} app list`, diff --git a/src/m365/entra/commands/agent/agent-blueprint-list.spec.ts b/src/m365/entra/commands/agent/agent-blueprint-list.spec.ts new file mode 100644 index 00000000000..d21d7c16f4d --- /dev/null +++ b/src/m365/entra/commands/agent/agent-blueprint-list.spec.ts @@ -0,0 +1,185 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import auth from '../../../../Auth.js'; +import { cli } from '../../../../cli/cli.js'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { CommandError } from '../../../../Command.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import commands from '../../commands.js'; +import command, { options } from './agent-blueprint-list.js'; + +describe(commands.AGENT_BLUEPRINT_LIST, () => { + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + let commandInfo: CommandInfo; + let commandOptionsSchema: typeof options; + + const response = [ + { + "appId": "0e81af7f-b058-470a-84be-3a4f5a8014ca", + "createdByAppId": "14d82eec-204b-4c2f-b7e8-296a70dab67e", + "createdDateTime": "2025-11-20T07:47:39Z", + "defaultRedirectUri": null, + "description": null, + "disabledByMicrosoftStatus": null, + "displayName": "OcnAgentBluePrint02", + "groupMembershipClaims": null, + "identifierUris": [], + "managerApplications": [], + "publisherDomain": "contoso.onmicrosoft.com", + "serviceManagementReference": null, + "signInAudience": "AzureADMyOrg", + "tags": [], + "tokenEncryptionKeyId": null, + "uniqueName": null, + "id": "0e81af7f-b058-470a-84be-3a4f5a8014ca", + "certification": null, + "optionalClaims": null, + "api": { + "acceptMappedClaims": null, + "knownClientApplications": [], + "requestedAccessTokenVersion": 2, + "oauth2PermissionScopes": [], + "preAuthorizedApplications": [] + }, + "appRoles": [], + "info": { + "logoUrl": null, + "marketingUrl": null, + "privacyStatementUrl": null, + "supportUrl": null, + "termsOfServiceUrl": null + }, + "keyCredentials": [], + "passwordCredentials": [], + "requiredResourceAccess": [], + "verifiedPublisher": { + "displayName": null, + "verifiedPublisherId": null, + "addedDateTime": null + }, + "web": { + "homePageUrl": null, + "logoutUrl": null, + "redirectUris": [], + "implicitGrantSettings": { + "enableAccessTokenIssuance": false, + "enableIdTokenIssuance": false + }, + "redirectUriSettings": [] + } + } + ]; + + const limitedResponse = [ + { + "displayName": "OcnAgentBluePrint02", + "id": "0e81af7f-b058-470a-84be-3a4f5a8014ca" + } + ]; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').resolves(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse() as typeof options; + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + loggerLogSpy = sinon.spy(logger, 'log'); + }); + + afterEach(() => { + sinonUtil.restore([ + request.get + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.AGENT_BLUEPRINT_LIST); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('defines correct properties for the default output', () => { + assert.deepStrictEqual(command.defaultProperties(), ['id', 'displayName', 'appId']); + }); + + it(`should get a list of agent identity blueprints`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/applications/microsoft.graph.agentIdentityBlueprint`) { + return { + value: response + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({}) }); + + assert( + loggerLogSpy.calledWith(response) + ); + }); + + it(`should get a list of administrative units with specified properties`, async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/applications/microsoft.graph.agentIdentityBlueprint?$select=id,displayName`) { + return { + value: limitedResponse + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: commandOptionsSchema.parse({ properties: 'id,displayName' }) }); + + assert( + loggerLogSpy.calledWith(limitedResponse) + ); + }); + + it('handles error when retrieving administrative units list failed', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/applications/microsoft.graph.agentIdentityBlueprint`) { + throw { error: { message: 'An error has occurred' } }; + } + throw `Invalid request`; + }); + + await assert.rejects( + command.action(logger, { options: commandOptionsSchema.parse({}) }), + new CommandError('An error has occurred') + ); + }); +}); diff --git a/src/m365/entra/commands/agent/agent-blueprint-list.ts b/src/m365/entra/commands/agent/agent-blueprint-list.ts new file mode 100644 index 00000000000..f57751229f6 --- /dev/null +++ b/src/m365/entra/commands/agent/agent-blueprint-list.ts @@ -0,0 +1,61 @@ +import { z } from 'zod'; +import { Logger } from '../../../../cli/Logger.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import { odata } from '../../../../utils/odata.js'; +import GraphCommand from '../../../base/GraphCommand.js'; +import commands from '../../commands.js'; + +export const options = z.strictObject({ + ...globalOptionsZod.shape, + properties: z.string().optional().alias('p') +}); +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +class EntraAgentBlueprintListCommand extends GraphCommand { + public get name(): string { + return commands.AGENT_BLUEPRINT_LIST; + } + + public get description(): string { + return 'Retrieves a list of templates defining the agent identity type'; + } + + public get schema(): z.ZodType | undefined { + return options; + } + + public defaultProperties(): string[] | undefined { + return ['id', 'displayName', 'appId']; + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + const queryParameters: string[] = []; + + if (args.options.properties) { + const allProperties = args.options.properties.split(','); + const selectProperties = allProperties.filter(prop => !prop.includes('/')); + + if (selectProperties.length > 0) { + queryParameters.push(`$select=${selectProperties}`); + } + } + + const queryString = queryParameters.length > 0 + ? `?${queryParameters.join('&')}` + : ''; + + try { + const results = await odata.getAllItems(`${this.resource}/v1.0/applications/microsoft.graph.agentIdentityBlueprint${queryString}`); + await logger.log(results); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new EntraAgentBlueprintListCommand(); \ No newline at end of file