diff --git a/src/TALXIS.CLI.Core/Contracts/Dataverse/ComponentTypeResolver.cs b/src/TALXIS.CLI.Core/Contracts/Dataverse/ComponentTypeResolver.cs
deleted file mode 100644
index 1c705da..0000000
--- a/src/TALXIS.CLI.Core/Contracts/Dataverse/ComponentTypeResolver.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-namespace TALXIS.CLI.Core.Contracts.Dataverse;
-
-///
-/// Maps between integer component-type codes and human-friendly names.
-/// Supports both directions: code → name and name → code.
-/// Platform types are hardcoded; SCF types can be loaded dynamically from the environment.
-///
-public sealed class ComponentTypeResolver
-{
- private readonly Dictionary _codeToName;
- private readonly Dictionary _nameToCode;
-
- public ComponentTypeResolver()
- {
- _codeToName = new Dictionary(PlatformTypes);
- _nameToCode = new Dictionary(StringComparer.OrdinalIgnoreCase);
- foreach (var (code, name) in PlatformTypes)
- _nameToCode[name] = code;
- // Register common aliases
- foreach (var (alias, code) in PlatformAliases)
- _nameToCode[alias] = code;
- }
-
- /// Resolves a friendly name to its integer type code.
- public bool TryResolveCode(string nameOrCode, out int code)
- {
- if (int.TryParse(nameOrCode, out code))
- return _codeToName.ContainsKey(code) || code > 0;
- return _nameToCode.TryGetValue(nameOrCode, out code);
- }
-
- /// Returns all known friendly names for use in error messages.
- public IEnumerable GetKnownNames() => _nameToCode.Keys.Order();
-
- /// Resolves an integer type code to a friendly name.
- public string ResolveName(int code)
- => _codeToName.TryGetValue(code, out var name) ? name : code.ToString();
-
- /// Well-known platform component types (static, same across all environments).
- private static readonly Dictionary PlatformTypes = new()
- {
- [1] = "Entity",
- [2] = "Attribute",
- [3] = "Relationship",
- [9] = "OptionSet",
- [10] = "EntityRelationship",
- [14] = "EntityKey",
- [16] = "Privilege",
- [20] = "Role",
- [26] = "SavedQuery",
- [29] = "Workflow",
- [31] = "Report",
- [36] = "EmailTemplate",
- [59] = "SavedQueryVisualization",
- [60] = "SystemForm",
- [61] = "WebResource",
- [62] = "SiteMap",
- [63] = "ConnectionRole",
- [66] = "CustomControl",
- [70] = "FieldSecurityProfile",
- [80] = "AppModule",
- [91] = "PluginAssembly",
- [92] = "SdkMessageProcessingStep",
- [95] = "ServiceEndpoint",
- [300] = "CanvasApp",
- [371] = "Connector",
- [380] = "EnvironmentVariableDefinition",
- [381] = "EnvironmentVariableValue",
- };
-
- /// Common aliases developers might use on the command line.
- private static readonly Dictionary PlatformAliases = new(StringComparer.OrdinalIgnoreCase)
- {
- ["Table"] = 1,
- ["Column"] = 2,
- ["Choice"] = 9,
- ["View"] = 26,
- ["Chart"] = 59,
- ["Form"] = 60,
- ["Dashboard"] = 60,
- ["SecurityRole"] = 20,
- ["Process"] = 29,
- ["PluginStep"] = 92,
- ["EnvironmentVariable"] = 380,
- };
-}
diff --git a/src/TALXIS.CLI.Core/Shared/BrowserLauncher.cs b/src/TALXIS.CLI.Core/Shared/BrowserLauncher.cs
new file mode 100644
index 0000000..781e4e3
--- /dev/null
+++ b/src/TALXIS.CLI.Core/Shared/BrowserLauncher.cs
@@ -0,0 +1,48 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Microsoft.Extensions.Logging;
+using TALXIS.CLI.Core.Abstractions;
+using TALXIS.CLI.Core.DependencyInjection;
+
+namespace TALXIS.CLI.Core;
+
+///
+/// Cross-platform utility to open a URL in the default browser.
+/// Headless-aware: skips browser launch in CI/non-interactive environments.
+///
+public static class BrowserLauncher
+{
+ ///
+ /// Opens in the default browser.
+ /// In headless/CI mode, logs a warning and returns without opening.
+ ///
+ public static void Open(Uri url, ILogger logger)
+ {
+ var detector = TxcServices.Get();
+ if (detector.IsHeadless)
+ {
+ logger.LogWarning("Headless mode ({Reason}) — browser not opened.", detector.Reason);
+ return;
+ }
+
+ try
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ Process.Start(new ProcessStartInfo(url.AbsoluteUri) { UseShellExecute = true });
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ Process.Start("open", url.AbsoluteUri);
+ }
+ else
+ {
+ Process.Start("xdg-open", url.AbsoluteUri);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogWarning("Could not open browser: {Error}", ex.Message);
+ }
+ }
+}
diff --git a/src/TALXIS.CLI.Core/TALXIS.CLI.Core.csproj b/src/TALXIS.CLI.Core/TALXIS.CLI.Core.csproj
index c63cc97..49d08f7 100644
--- a/src/TALXIS.CLI.Core/TALXIS.CLI.Core.csproj
+++ b/src/TALXIS.CLI.Core/TALXIS.CLI.Core.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Browse/BrowseUrlConstants.cs b/src/TALXIS.CLI.Features.Environment/Component/Browse/BrowseUrlConstants.cs
new file mode 100644
index 0000000..f326ce2
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/Component/Browse/BrowseUrlConstants.cs
@@ -0,0 +1,22 @@
+namespace TALXIS.CLI.Features.Environment.Component.Browse;
+
+///
+/// Shared constants for browse URL construction.
+///
+public static class BrowseUrlConstants
+{
+ /// Default Solution GUID — used when no solution context is specified for form/view/securityrole.
+ public const string DefaultSolutionId = "fd140aaf-4df4-11dd-bd17-0019b9312238";
+
+ ///
+ /// Extracts the hostname from an org URL for use in URL construction.
+ /// Accepts either a full URL or a bare hostname.
+ ///
+ public static string NormalizeOrgUrl(string orgUrl)
+ {
+ if (Uri.TryCreate(orgUrl, UriKind.Absolute, out var uri))
+ return uri.Host;
+ // Already a bare hostname
+ return orgUrl.TrimEnd('/');
+ }
+}
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Browse/CanvasAppUrls.cs b/src/TALXIS.CLI.Features.Environment/Component/Browse/CanvasAppUrls.cs
new file mode 100644
index 0000000..8c3bfea
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/Component/Browse/CanvasAppUrls.cs
@@ -0,0 +1,31 @@
+namespace TALXIS.CLI.Features.Environment.Component.Browse;
+
+///
+/// URL builders for the Power Apps canvas app player (apps.powerapps.com/play).
+/// Supports screen navigation, custom parameters, and hidden navbar.
+///
+public static class CanvasAppUrls
+{
+ private const string Base = "https://apps.powerapps.com/play";
+
+ ///
+ /// Open a canvas app in the Power Apps player.
+ ///
+ public static Uri Play(Guid environmentId, Guid appId, string? tenantId,
+ string? screenName = null, IDictionary? customParams = null, bool hideNavbar = false)
+ {
+ var qs = new List();
+ if (!string.IsNullOrWhiteSpace(tenantId))
+ qs.Add($"tenantId={Uri.EscapeDataString(tenantId)}");
+ if (!string.IsNullOrWhiteSpace(screenName))
+ qs.Add($"screenName={Uri.EscapeDataString(screenName)}");
+ if (hideNavbar)
+ qs.Add("hidenavbar=true");
+ if (customParams != null)
+ foreach (var (key, value) in customParams)
+ qs.Add($"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(value)}");
+
+ var query = qs.Count > 0 ? "?" + string.Join("&", qs) : "";
+ return new Uri($"{Base}/e/{environmentId}/a/{appId}{query}");
+ }
+}
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Browse/ComponentBrowseCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Component/Browse/ComponentBrowseCliCommand.cs
new file mode 100644
index 0000000..d94bb99
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/Component/Browse/ComponentBrowseCliCommand.cs
@@ -0,0 +1,364 @@
+using DotMake.CommandLine;
+using Microsoft.Extensions.Logging;
+using TALXIS.CLI.Core;
+using TALXIS.CLI.Core.Abstractions;
+using TALXIS.CLI.Core.Contracts.Dataverse;
+using TALXIS.CLI.Core.DependencyInjection;
+using TALXIS.CLI.Logging;
+using TALXIS.Platform.Metadata;
+
+namespace TALXIS.CLI.Features.Environment.Component.Browse;
+
+///
+/// Opens the Power Platform web editor for a component instance.
+/// Resolves the appropriate URL based on component type and opens it in the default browser.
+/// In headless mode, only prints the URL without opening the browser.
+///
+[CliReadOnly]
+[CliCommand(
+ Name = "browse",
+ Description = "Open the web editor for a component in the connected live environment. Requires an active profile."
+)]
+public class ComponentBrowseCliCommand : ProfiledCliCommand
+{
+ protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger();
+
+ [CliOption(Name = "--type", Description = "Component type (name, alias, or integer code). Run 'txc component type list' to see available types.", Required = true)]
+ public string Type { get; set; } = null!;
+
+ [CliOption(Name = "--id", Description = "Component GUID. Mutually exclusive with --name.", Required = false)]
+ public string? Id { get; set; }
+
+ [CliOption(Name = "--name", Description = "Component friendly name (resolved to GUID). Mutually exclusive with --id. Supported for: solution (unique name), entity (logical name).", Required = false)]
+ public string? Name { get; set; }
+
+ [CliOption(Name = "--entity", Description = "Entity logical name. Required for form/view types. Also provides the backing entity name for SCF types.", Required = false)]
+ public string? Entity { get; set; }
+
+ [CliOption(Name = "--solution", Description = "Solution unique name for solution-scoped URLs. Resolved to GUID.", Required = false)]
+ public string? Solution { get; set; }
+
+ // ── App deep-link options (model-driven apps) ──
+
+ [CliOption(Name = "--pagetype", Description = "UCI page type for deep-linking within an app: entityrecord, entitylist, dashboard, webresource, control, custom, inlinedialog, genux, search.", Required = false)]
+ public string? PageType { get; set; }
+
+ [CliOption(Name = "--record", Description = "Record GUID to open in an entity form (pagetype=entityrecord).", Required = false)]
+ public string? Record { get; set; }
+
+ [CliOption(Name = "--formid", Description = "Specific form GUID to use for entity record.", Required = false)]
+ public string? FormId { get; set; }
+
+ [CliOption(Name = "--viewid", Description = "View GUID for entity list (pagetype=entitylist).", Required = false)]
+ public string? ViewId { get; set; }
+
+ [CliOption(Name = "--dashboard", Description = "Dashboard GUID (pagetype=dashboard).", Required = false)]
+ public string? Dashboard { get; set; }
+
+ [CliOption(Name = "--custom-page", Description = "Custom page logical name (pagetype=custom).", Required = false)]
+ public string? CustomPage { get; set; }
+
+ [CliOption(Name = "--control", Description = "Full-page PCF control fully-qualified name (pagetype=control).", Required = false)]
+ public string? Control { get; set; }
+
+ [CliOption(Name = "--webresource", Description = "Web resource logical name (pagetype=webresource).", Required = false)]
+ public string? WebResource { get; set; }
+
+ [CliOption(Name = "--genux", Description = "Generative/Copilot AI page ID (pagetype=genux).", Required = false)]
+ public string? Genux { get; set; }
+
+ [CliOption(Name = "--dialog-name", Description = "Inline dialog name (pagetype=inlinedialog).", Required = false)]
+ public string? DialogName { get; set; }
+
+ [CliOption(Name = "--dialog-options", Description = "Inline dialog options JSON (pagetype=inlinedialog).", Required = false)]
+ public string? DialogOptions { get; set; }
+
+ [CliOption(Name = "--data", Description = "Data parameter for control, genux, or webresource page types.", Required = false)]
+ public string? Data { get; set; }
+
+ [CliOption(Name = "--extraqs", Description = "Pre-populate form fields as key=value pairs (URL-encoded automatically).", Required = false)]
+ public string? ExtraQs { get; set; }
+
+ [CliOption(Name = "--navbar", Description = "Navigation bar mode: on, off, entity.", Required = false)]
+ public string? Navbar { get; set; }
+
+ [CliOption(Name = "--cmdbar", Description = "Show command bar: true or false.", Required = false)]
+ public string? Cmdbar { get; set; }
+
+ // ── Canvas app options ──
+
+ [CliOption(Name = "--screen", Description = "Canvas app screen name to navigate to on launch.", Required = false)]
+ public string? Screen { get; set; }
+
+ [CliOption(Name = "--param", Description = "Canvas app custom parameter (key=value). Can be specified multiple times.", Required = false)]
+ public List Param { get; set; } = new();
+
+ [CliOption(Name = "--hidenavbar", Description = "Canvas app: hide the Power Apps navigation bar.", Required = false)]
+ public bool HideNavbar { get; set; }
+
+ // ── Power Automate options ──
+
+ [CliOption(Name = "--flow-view", Description = "Flow page to open: editor (default), details, or runs.", Required = false)]
+ public string? FlowView { get; set; }
+
+ [CliOption(Name = "--run", Description = "Specific flow run ID to open.", Required = false)]
+ public string? Run { get; set; }
+
+ // ── Report options ──
+
+ [CliOption(Name = "--report-action", Description = "Report viewer action: run (default) or filter.", Required = false)]
+ public string? ReportAction { get; set; }
+
+ protected override async Task ExecuteAsync()
+ {
+ // Resolve component type
+ var def = ComponentDefinitionRegistry.GetByName(Type);
+ ComponentType? typeCode = def?.TypeCode;
+ if (typeCode is null && int.TryParse(Type, out var rawCode) && rawCode > 0)
+ typeCode = (ComponentType)rawCode;
+ if (typeCode is null)
+ {
+ Logger.LogError("Unknown component type '{Type}'. Run 'txc component type list' to see available types.", Type);
+ return ExitValidationError;
+ }
+
+ // Resolve profile + connection
+ var configResolver = TxcServices.Get();
+ var ctx = await configResolver.ResolveAsync(Profile, CancellationToken.None).ConfigureAwait(false);
+ var connection = ctx.Connection;
+
+ if (connection.EnvironmentId is null)
+ {
+ Logger.LogError("Environment ID is not set on the connection. Run 'txc config connection check' to populate it.");
+ return ExitValidationError;
+ }
+ var environmentId = connection.EnvironmentId.Value;
+
+ // Validate EnvironmentUrl for types that require it (UCI, reports, SCF record forms)
+ var needsOrgUrl = typeCode.Value is ComponentType.AppModule or ComponentType.Report
+ || (typeCode.Value is not ComponentType.CanvasApp and not ComponentType.Workflow);
+ string? orgUrl = null;
+ if (!string.IsNullOrWhiteSpace(connection.EnvironmentUrl)
+ && Uri.TryCreate(connection.EnvironmentUrl, UriKind.Absolute, out var orgUri))
+ {
+ orgUrl = orgUri.Host;
+ }
+ else if (needsOrgUrl)
+ {
+ Logger.LogError("Environment URL is not set or invalid on the connection. Run 'txc config connection check' to populate it.");
+ return ExitValidationError;
+ }
+
+ // Dispatch by component type
+ Uri? url = typeCode.Value switch
+ {
+ ComponentType.AppModule => BuildAppModuleUrl(orgUrl!),
+ ComponentType.CanvasApp => BuildCanvasAppUrl(environmentId, connection.TenantId),
+ ComponentType.Report => BuildReportUrl(orgUrl!),
+ ComponentType.Workflow => await BuildFlowUrlAsync(environmentId).ConfigureAwait(false),
+ _ => await BuildMakerEditorUrlAsync(typeCode.Value, environmentId, orgUrl).ConfigureAwait(false)
+ };
+
+ if (url is null)
+ return ExitValidationError; // error already logged
+
+ OutputFormatter.WriteData(new { url = url.AbsoluteUri, type = def?.Name ?? typeCode.Value.ToString() },
+ _ => OutputWriter.WriteLine(url.AbsoluteUri));
+
+ BrowserLauncher.Open(url, Logger);
+ return ExitSuccess;
+ }
+
+ /// Build URL for model-driven app (shell or deep-link via pagetype).
+ private Uri? BuildAppModuleUrl(string orgUrl)
+ {
+ if (string.IsNullOrWhiteSpace(PageType))
+ {
+ if (!string.IsNullOrWhiteSpace(Name))
+ return PowerAppsUciUrls.AppByName(orgUrl, Name);
+ if (!string.IsNullOrWhiteSpace(Id) && Guid.TryParse(Id, out var appId))
+ return PowerAppsUciUrls.AppById(orgUrl, appId);
+ Logger.LogError("Provide --name or --id for the app module.");
+ return null;
+ }
+
+ var queryParams = new Dictionary();
+ if (!string.IsNullOrWhiteSpace(Entity)) queryParams["etn"] = Entity;
+ if (!string.IsNullOrWhiteSpace(Record)) queryParams["id"] = Record;
+ if (!string.IsNullOrWhiteSpace(FormId)) queryParams["formid"] = FormId;
+ if (!string.IsNullOrWhiteSpace(ViewId)) { queryParams["viewid"] = ViewId; queryParams["viewtype"] = "1039"; }
+ if (!string.IsNullOrWhiteSpace(Dashboard)) queryParams["id"] = Dashboard;
+ if (!string.IsNullOrWhiteSpace(CustomPage)) queryParams["name"] = CustomPage;
+ if (!string.IsNullOrWhiteSpace(Control)) queryParams["controlName"] = Control;
+ if (!string.IsNullOrWhiteSpace(WebResource)) queryParams["webresourceName"] = WebResource;
+ if (!string.IsNullOrWhiteSpace(Genux)) queryParams["id"] = Genux;
+ if (!string.IsNullOrWhiteSpace(DialogName)) queryParams["name"] = DialogName;
+ if (!string.IsNullOrWhiteSpace(DialogOptions)) queryParams["dialogOptions"] = DialogOptions;
+ if (!string.IsNullOrWhiteSpace(Data)) queryParams["data"] = Data;
+ if (!string.IsNullOrWhiteSpace(ExtraQs)) queryParams["extraqs"] = ExtraQs;
+ if (!string.IsNullOrWhiteSpace(Navbar)) queryParams["navbar"] = Navbar;
+ if (!string.IsNullOrWhiteSpace(Cmdbar)) queryParams["cmdbar"] = Cmdbar;
+
+ Guid? appId2 = null;
+ if (!string.IsNullOrWhiteSpace(Id) && Guid.TryParse(Id, out var parsed))
+ appId2 = parsed;
+
+ return PowerAppsUciUrls.DeepLink(orgUrl, Name, appId2, PageType, queryParams);
+ }
+
+ /// Build URL for Power Automate flow — editor, details, runs, or specific run.
+ private async Task BuildFlowUrlAsync(Guid environmentId)
+ {
+ if (string.IsNullOrWhiteSpace(Id) || !Guid.TryParse(Id, out var flowId))
+ {
+ Logger.LogError("--id is required for flows.");
+ return null;
+ }
+
+ Guid? solutionId = null;
+ if (!string.IsNullOrWhiteSpace(Solution))
+ {
+ var slnService = TxcServices.Get();
+ var (sln, _) = await slnService.ShowAsync(Profile, Solution, CancellationToken.None).ConfigureAwait(false);
+ solutionId = sln.Id;
+ }
+
+ if (!string.IsNullOrWhiteSpace(Run))
+ return PowerAutomateUrls.FlowRun(environmentId, flowId, Run, solutionId);
+
+ return (FlowView?.ToLowerInvariant()) switch
+ {
+ "details" => PowerAutomateUrls.FlowDetails(environmentId, flowId, solutionId),
+ "runs" => PowerAutomateUrls.FlowRuns(environmentId, flowId, solutionId),
+ _ => PowerAutomateUrls.FlowEditor(environmentId, flowId, solutionId)
+ };
+ }
+
+ /// Build URL for canvas app player.
+ private Uri? BuildCanvasAppUrl(Guid environmentId, string? tenantId)
+ {
+ if (string.IsNullOrWhiteSpace(Id) || !Guid.TryParse(Id, out var appId))
+ {
+ Logger.LogError("--id is required for canvas apps.");
+ return null;
+ }
+
+ var customParams = new Dictionary();
+ foreach (var p in Param)
+ {
+ var idx = p.IndexOf('=');
+ if (idx > 0 && idx < p.Length - 1)
+ customParams[p[..idx]] = p[(idx + 1)..];
+ }
+
+ return CanvasAppUrls.Play(environmentId, appId, tenantId, Screen,
+ customParams.Count > 0 ? customParams : null, HideNavbar);
+ }
+
+ /// Build URL for report viewer.
+ private Uri? BuildReportUrl(string orgUrl)
+ {
+ if (string.IsNullOrWhiteSpace(Id) || !Guid.TryParse(Id, out var reportId))
+ {
+ Logger.LogError("--id is required for reports.");
+ return null;
+ }
+ return PowerAppsUciUrls.Report(orgUrl, reportId, ReportAction ?? "run");
+ }
+
+ /// Build URL for maker portal editor (existing component types).
+ private async Task BuildMakerEditorUrlAsync(ComponentType typeCode, Guid environmentId, string? orgUrl)
+ {
+ // Validate --id / --name
+ if (string.IsNullOrWhiteSpace(Id) && string.IsNullOrWhiteSpace(Name))
+ {
+ Logger.LogError("Provide --id or --name .");
+ return null;
+ }
+ if (!string.IsNullOrWhiteSpace(Id) && !string.IsNullOrWhiteSpace(Name))
+ {
+ Logger.LogError("--id and --name are mutually exclusive.");
+ return null;
+ }
+
+ Guid componentId;
+ if (!string.IsNullOrWhiteSpace(Id))
+ {
+ if (!Guid.TryParse(Id, out componentId))
+ {
+ Logger.LogError("Invalid GUID: '{Id}'.", Id);
+ return null;
+ }
+ }
+ else
+ {
+ var resolved = await ResolveNameToGuidAsync(typeCode, Name!).ConfigureAwait(false);
+ if (resolved is null) return null;
+ componentId = resolved.Value;
+ }
+
+ // Resolve --solution
+ Guid? solutionId = null;
+ if (!string.IsNullOrWhiteSpace(Solution))
+ {
+ var slnService = TxcServices.Get();
+ var (sln, _) = await slnService.ShowAsync(Profile, Solution, CancellationToken.None).ConfigureAwait(false);
+ solutionId = sln.Id;
+ }
+
+ if (typeCode is ComponentType.SystemForm or ComponentType.Form or ComponentType.SavedQuery
+ && string.IsNullOrWhiteSpace(Entity))
+ {
+ Logger.LogError("--entity is required for form/view types.");
+ return null;
+ }
+
+ var url = BuildEditorUrl(typeCode, environmentId, orgUrl, componentId, Entity, solutionId);
+ if (url is null)
+ Logger.LogError("Cannot build URL for type '{Type}' (code {Code}). For SCF types, provide --entity.", Type, (int)typeCode);
+ return url;
+ }
+
+ /// Dispatches to the appropriate URL builder based on component type.
+ private static Uri? BuildEditorUrl(ComponentType typeCode, Guid envId, string? orgUrl, Guid componentId, string? entity, Guid? solutionId)
+ {
+ return typeCode switch
+ {
+ ComponentType.Solution => MakerPortalUrls.Solution(envId, componentId),
+ ComponentType.Entity => MakerPortalUrls.Entity(envId, componentId, solutionId),
+ ComponentType.SystemForm when entity != null => MakerPortalUrls.FormDesigner(envId, entity, componentId, solutionId),
+ ComponentType.Form when entity != null => MakerPortalUrls.FormDesigner(envId, entity, componentId, solutionId),
+ ComponentType.SavedQuery when entity != null => MakerPortalUrls.ViewDesigner(envId, entity, componentId, solutionId),
+ ComponentType.Bot => CopilotStudioUrls.BotEditor(envId, componentId, solutionId),
+ ComponentType.Dataflow => MakerPortalUrls.DataflowEditor(envId, componentId),
+ ComponentType.Role => MakerPortalUrls.SecurityRoleEditor(envId, componentId, solutionId),
+ // SCF / unknown — fallback to UCI record form
+ _ when orgUrl != null && entity != null => PowerAppsUciUrls.RecordForm(orgUrl, entity, componentId),
+ _ => null
+ };
+ }
+
+ private async Task ResolveNameToGuidAsync(ComponentType typeCode, string name)
+ {
+ switch (typeCode)
+ {
+ case ComponentType.Solution:
+ var slnService = TxcServices.Get();
+ var (sln, _) = await slnService.ShowAsync(Profile, name, CancellationToken.None).ConfigureAwait(false);
+ return sln.Id;
+
+ case ComponentType.Entity:
+ var metadataResolver = TxcServices.Get();
+ return await metadataResolver.ResolveEntityIdAsync(Profile, name, CancellationToken.None).ConfigureAwait(false);
+
+ case ComponentType.AppModule:
+ // AppModule name resolution handled in BuildAppModuleUrl
+ Logger.LogError("AppModule name resolution is handled via --name directly.");
+ return null;
+
+ default:
+ Logger.LogError("--name is not supported for type '{Type}'. Use --id instead.", Type);
+ return null;
+ }
+ }
+}
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Browse/CopilotStudioUrls.cs b/src/TALXIS.CLI.Features.Environment/Component/Browse/CopilotStudioUrls.cs
new file mode 100644
index 0000000..6ad8902
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/Component/Browse/CopilotStudioUrls.cs
@@ -0,0 +1,16 @@
+namespace TALXIS.CLI.Features.Environment.Component.Browse;
+
+///
+/// URL builders for Copilot Studio (copilotstudio.microsoft.com).
+/// Covers bot/agent editor.
+///
+public static class CopilotStudioUrls
+{
+ private const string Base = "https://copilotstudio.microsoft.com";
+
+ /// Open the bot/agent editor in Copilot Studio.
+ public static Uri BotEditor(Guid environmentId, Guid botId, Guid? solutionId = null)
+ => solutionId.HasValue
+ ? new($"{Base}/environments/{environmentId}/bots/{botId}?solutionId={solutionId}")
+ : new($"{Base}/environments/{environmentId}/bots/{botId}");
+}
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Browse/MakerPortalUrls.cs b/src/TALXIS.CLI.Features.Environment/Component/Browse/MakerPortalUrls.cs
new file mode 100644
index 0000000..8d9a731
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/Component/Browse/MakerPortalUrls.cs
@@ -0,0 +1,40 @@
+namespace TALXIS.CLI.Features.Environment.Component.Browse;
+
+///
+/// URL builders for the Power Apps maker portal (make.powerapps.com).
+/// Covers solution editor, entity fields, form designer, view designer,
+/// security role editor, and dataflow editor.
+///
+public static class MakerPortalUrls
+{
+ private const string Base = "https://make.powerapps.com";
+
+ public static Uri Solution(Guid environmentId, Guid solutionId)
+ => new($"{Base}/environments/{environmentId}/solutions/{solutionId}");
+
+ public static Uri Entity(Guid environmentId, Guid metadataId, Guid? solutionId = null)
+ => solutionId.HasValue
+ ? new($"{Base}/environments/{environmentId}/solutions/{solutionId}/entities/{metadataId}/fields")
+ : new($"{Base}/environments/{environmentId}/entities/{metadataId}/fields");
+
+ public static Uri FormDesigner(Guid environmentId, string entityLogicalName, Guid formId, Guid? solutionId = null)
+ {
+ var slnId = solutionId ?? Guid.Parse(BrowseUrlConstants.DefaultSolutionId);
+ return new($"{Base}/e/{environmentId}/s/{slnId}/entity/{entityLogicalName}/form/edit/{formId}");
+ }
+
+ public static Uri ViewDesigner(Guid environmentId, string entityLogicalName, Guid viewId, Guid? solutionId = null)
+ {
+ var slnId = solutionId ?? Guid.Parse(BrowseUrlConstants.DefaultSolutionId);
+ return new($"{Base}/e/{environmentId}/s/{slnId}/entity/{entityLogicalName}/view/{viewId}");
+ }
+
+ public static Uri SecurityRoleEditor(Guid environmentId, Guid roleId, Guid? solutionId = null)
+ {
+ var slnId = solutionId ?? Guid.Parse(BrowseUrlConstants.DefaultSolutionId);
+ return new($"{Base}/e/{environmentId}/s/{slnId}/securityroles/{roleId}/roleeditor");
+ }
+
+ public static Uri DataflowEditor(Guid environmentId, Guid dataflowId)
+ => new($"{Base}/environments/{environmentId}/dataintegration/list/{dataflowId}/edit");
+}
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Browse/PowerAppsUciUrls.cs b/src/TALXIS.CLI.Features.Environment/Component/Browse/PowerAppsUciUrls.cs
new file mode 100644
index 0000000..838a031
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/Component/Browse/PowerAppsUciUrls.cs
@@ -0,0 +1,51 @@
+namespace TALXIS.CLI.Features.Environment.Component.Browse;
+
+///
+/// URL builders for the Power Apps UCI runtime ({org}.crm{N}.dynamics.com/main.aspx).
+/// Covers model-driven app shell, all UCI page types (entityrecord, entitylist, dashboard,
+/// webresource, control, custom, inlinedialog, genux, search), SCF record forms, and reports.
+///
+public static class PowerAppsUciUrls
+{
+ /// Open a model-driven app by its unique name.
+ public static Uri AppByName(string orgUrl, string uniqueName)
+ => new($"https://{BrowseUrlConstants.NormalizeOrgUrl(orgUrl)}/main.aspx?appname={Uri.EscapeDataString(uniqueName)}");
+
+ /// Open a model-driven app by its AppModuleId GUID.
+ public static Uri AppById(string orgUrl, Guid appModuleId)
+ => new($"https://{BrowseUrlConstants.NormalizeOrgUrl(orgUrl)}/main.aspx?appid={appModuleId}");
+
+ ///
+ /// Generic deep-link into a model-driven app via main.aspx.
+ /// Builds the URL from app identity + pagetype + arbitrary query parameters.
+ /// Supports all UCI page types: entityrecord, entitylist, dashboard, webresource,
+ /// control, custom, inlinedialog, genux, search, apps.
+ ///
+ public static Uri DeepLink(string orgUrl, string? appName, Guid? appId, string pageType, IDictionary queryParams)
+ {
+ var org = BrowseUrlConstants.NormalizeOrgUrl(orgUrl);
+ var qs = new List();
+
+ if (!string.IsNullOrWhiteSpace(appName))
+ qs.Add($"appname={Uri.EscapeDataString(appName)}");
+ else if (appId.HasValue)
+ qs.Add($"appid={appId.Value}");
+
+ qs.Add($"pagetype={Uri.EscapeDataString(pageType)}");
+
+ foreach (var (key, value) in queryParams)
+ qs.Add($"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(value)}");
+
+ return new Uri($"https://{org}/main.aspx?{string.Join("&", qs)}");
+ }
+
+ ///
+ /// Open an SCF or unrecognized component type's backing entity record form.
+ ///
+ public static Uri RecordForm(string orgUrl, string entityLogicalName, Guid recordId)
+ => new($"https://{BrowseUrlConstants.NormalizeOrgUrl(orgUrl)}/main.aspx?forceUCI=1&newWindow=true&pagetype=entityrecord&etn={entityLogicalName}&id={recordId}");
+
+ /// Open a report in the Dynamics report viewer.
+ public static Uri Report(string orgUrl, Guid reportId, string action = "run")
+ => new($"https://{BrowseUrlConstants.NormalizeOrgUrl(orgUrl)}/crmreports/viewer/viewer.aspx?action={Uri.EscapeDataString(action)}&id=%7b{reportId}%7d");
+}
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Browse/PowerAutomateUrls.cs b/src/TALXIS.CLI.Features.Environment/Component/Browse/PowerAutomateUrls.cs
new file mode 100644
index 0000000..b590885
--- /dev/null
+++ b/src/TALXIS.CLI.Features.Environment/Component/Browse/PowerAutomateUrls.cs
@@ -0,0 +1,34 @@
+namespace TALXIS.CLI.Features.Environment.Component.Browse;
+
+///
+/// URL builders for the Power Automate maker portal (make.powerautomate.com).
+/// Covers flow editor, flow details, run history, and specific run inspection.
+///
+public static class PowerAutomateUrls
+{
+ private const string Base = "https://make.powerautomate.com";
+
+ /// Flow base path — editor view (default landing page for a flow).
+ public static Uri FlowEditor(Guid environmentId, Guid flowId, Guid? solutionId = null)
+ => solutionId.HasValue
+ ? new($"{Base}/environments/{environmentId}/solutions/{solutionId}/flows/{flowId}")
+ : new($"{Base}/environments/{environmentId}/flows/{flowId}");
+
+ /// Flow details page — shows connection references, owners, and metadata.
+ public static Uri FlowDetails(Guid environmentId, Guid flowId, Guid? solutionId = null)
+ => solutionId.HasValue
+ ? new($"{Base}/environments/{environmentId}/solutions/{solutionId}/flows/{flowId}/details")
+ : new($"{Base}/environments/{environmentId}/flows/{flowId}/details");
+
+ /// Flow run history — lists all runs with status.
+ public static Uri FlowRuns(Guid environmentId, Guid flowId, Guid? solutionId = null)
+ => solutionId.HasValue
+ ? new($"{Base}/environments/{environmentId}/solutions/{solutionId}/flows/{flowId}/runs")
+ : new($"{Base}/environments/{environmentId}/flows/{flowId}/runs");
+
+ /// Specific flow run — shows the run's step-by-step execution details.
+ public static Uri FlowRun(Guid environmentId, Guid flowId, string runId, Guid? solutionId = null)
+ => solutionId.HasValue
+ ? new($"{Base}/environments/{environmentId}/solutions/{solutionId}/flows/{flowId}/runs/{Uri.EscapeDataString(runId)}")
+ : new($"{Base}/environments/{environmentId}/flows/{flowId}/runs/{Uri.EscapeDataString(runId)}");
+}
diff --git a/src/TALXIS.CLI.Features.Environment/Component/ComponentCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Component/ComponentCliCommand.cs
index 94f582f..6e69e4c 100644
--- a/src/TALXIS.CLI.Features.Environment/Component/ComponentCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Component/ComponentCliCommand.cs
@@ -5,9 +5,10 @@ namespace TALXIS.CLI.Features.Environment.Component;
[CliCommand(
Name = "component",
Alias = "comp",
- Description = "Inspect components independent of a specific solution (layers, dependencies).",
+ Description = "Inspect and navigate components in the live environment (layers, dependencies, browser editor).",
Children = new[]
{
+ typeof(Browse.ComponentBrowseCliCommand),
typeof(Layer.ComponentLayerCliCommand),
typeof(Dependency.ComponentDependencyCliCommand),
},
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyDeleteCheckCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyDeleteCheckCliCommand.cs
index e2740cd..42c1cf3 100644
--- a/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyDeleteCheckCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyDeleteCheckCliCommand.cs
@@ -4,6 +4,7 @@
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
+using TALXIS.Platform.Metadata;
namespace TALXIS.CLI.Features.Environment.Component.Dependency;
@@ -41,13 +42,16 @@ protected override async Task ExecuteAsync()
return ExitValidationError;
}
- var resolver = new ComponentTypeResolver();
- if (!resolver.TryResolveCode(typeName, out var typeCode))
+ var def = ComponentDefinitionRegistry.GetByName(typeName);
+ if (def is null && int.TryParse(typeName, out var parsedCode))
+ def = ComponentDefinitionRegistry.GetByType((ComponentType)parsedCode);
+ if (def is null)
{
- var known = string.Join(", ", resolver.GetKnownNames().Take(15));
+ var known = string.Join(", ", ComponentDefinitionRegistry.GetAll().Select(d => d.Name).Take(15));
Logger.LogError("Unknown component type '{Type}'. Available types: {Known}.", typeName, known);
return ExitValidationError;
}
+ var typeCode = (int)def.TypeCode;
var service = TxcServices.Get();
var deps = await service.CheckDeleteAsync(Profile, id, typeCode, CancellationToken.None).ConfigureAwait(false);
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyListCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyListCliCommand.cs
index ae167f8..996edd4 100644
--- a/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyListCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyListCliCommand.cs
@@ -4,6 +4,7 @@
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
+using TALXIS.Platform.Metadata;
namespace TALXIS.CLI.Features.Environment.Component.Dependency;
@@ -41,13 +42,16 @@ protected override async Task ExecuteAsync()
return ExitValidationError;
}
- var resolver = new ComponentTypeResolver();
- if (!resolver.TryResolveCode(typeName, out var typeCode))
+ var def = ComponentDefinitionRegistry.GetByName(typeName);
+ if (def is null && int.TryParse(typeName, out var parsedCode))
+ def = ComponentDefinitionRegistry.GetByType((ComponentType)parsedCode);
+ if (def is null)
{
- var known = string.Join(", ", resolver.GetKnownNames().Take(15));
+ var known = string.Join(", ", ComponentDefinitionRegistry.GetAll().Select(d => d.Name).Take(15));
Logger.LogError("Unknown component type '{Type}'. Available types: {Known}. Or use an integer code.", typeName, known);
return ExitValidationError;
}
+ var typeCode = (int)def.TypeCode;
var service = TxcServices.Get();
var deps = await service.GetDependentsAsync(Profile, id, typeCode, CancellationToken.None).ConfigureAwait(false);
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyRequiredCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyRequiredCliCommand.cs
index 091eee2..fd05824 100644
--- a/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyRequiredCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Component/Dependency/ComponentDependencyRequiredCliCommand.cs
@@ -4,6 +4,7 @@
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
+using TALXIS.Platform.Metadata;
namespace TALXIS.CLI.Features.Environment.Component.Dependency;
@@ -41,13 +42,16 @@ protected override async Task ExecuteAsync()
return ExitValidationError;
}
- var resolver = new ComponentTypeResolver();
- if (!resolver.TryResolveCode(typeName, out var typeCode))
+ var def = ComponentDefinitionRegistry.GetByName(typeName);
+ if (def is null && int.TryParse(typeName, out var parsedCode))
+ def = ComponentDefinitionRegistry.GetByType((ComponentType)parsedCode);
+ if (def is null)
{
- var known = string.Join(", ", resolver.GetKnownNames().Take(15));
+ var known = string.Join(", ", ComponentDefinitionRegistry.GetAll().Select(d => d.Name).Take(15));
Logger.LogError("Unknown component type '{Type}'. Available types: {Known}.", typeName, known);
return ExitValidationError;
}
+ var typeCode = (int)def.TypeCode;
var service = TxcServices.Get();
var deps = await service.GetRequiredAsync(Profile, id, typeCode, CancellationToken.None).ConfigureAwait(false);
diff --git a/src/TALXIS.CLI.Features.Environment/Component/Dependency/DependencyOutputHelper.cs b/src/TALXIS.CLI.Features.Environment/Component/Dependency/DependencyOutputHelper.cs
index 5561671..851e201 100644
--- a/src/TALXIS.CLI.Features.Environment/Component/Dependency/DependencyOutputHelper.cs
+++ b/src/TALXIS.CLI.Features.Environment/Component/Dependency/DependencyOutputHelper.cs
@@ -1,5 +1,6 @@
using TALXIS.CLI.Core;
using TALXIS.CLI.Core.Contracts.Dataverse;
+using TALXIS.Platform.Metadata;
namespace TALXIS.CLI.Features.Environment.Component.Dependency;
@@ -8,8 +9,6 @@ namespace TALXIS.CLI.Features.Environment.Component.Dependency;
///
internal static class DependencyOutputHelper
{
- private static readonly ComponentTypeResolver Resolver = new();
-
// OutputWriter usage is intentional — called from text-renderer callbacks.
#pragma warning disable TXC003
public static void PrintDependencyTable(
@@ -33,8 +32,8 @@ public static void PrintDependencyTable(
foreach (var d in rows)
{
- var depType = Resolver.ResolveName(d.DependentComponentType);
- var reqType = Resolver.ResolveName(d.RequiredComponentType);
+ var depType = ComponentDefinitionRegistry.GetByType((ComponentType)d.DependentComponentType)?.Name ?? d.DependentComponentType.ToString();
+ var reqType = ComponentDefinitionRegistry.GetByType((ComponentType)d.RequiredComponentType)?.Name ?? d.RequiredComponentType.ToString();
var depKind = d.DependencyType switch
{
1 => "Internal",
diff --git a/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentAddCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentAddCliCommand.cs
index ab357b6..d1c0757 100644
--- a/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentAddCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentAddCliCommand.cs
@@ -4,6 +4,7 @@
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
+using TALXIS.Platform.Metadata;
namespace TALXIS.CLI.Features.Environment.Solution.Component;
@@ -39,13 +40,16 @@ protected override async Task ExecuteAsync()
return ExitValidationError;
}
- var resolver = new ComponentTypeResolver();
- if (!resolver.TryResolveCode(Type, out var typeCode))
+ var def = ComponentDefinitionRegistry.GetByName(Type);
+ if (def is null && int.TryParse(Type, out var parsedCode))
+ def = ComponentDefinitionRegistry.GetByType((ComponentType)parsedCode);
+ if (def is null)
{
- var known = string.Join(", ", resolver.GetKnownNames().Take(15));
+ var known = string.Join(", ", ComponentDefinitionRegistry.GetAll().Select(d => d.Name).Take(15));
Logger.LogError("Unknown component type '{Type}'. Available types: {Known}. Or use an integer code.", Type, known);
return ExitValidationError;
}
+ var typeCode = (int)def.TypeCode;
// Pre-check: reject managed solutions (can't add components to managed)
var detailService = TxcServices.Get();
@@ -60,7 +64,7 @@ protected override async Task ExecuteAsync()
var service = TxcServices.Get();
await service.AddAsync(Profile, options, CancellationToken.None).ConfigureAwait(false);
- var typeName = resolver.ResolveName(typeCode);
+ var typeName = def.Name;
OutputFormatter.WriteData(
new { status = "added", solution = SolutionName, componentId = ComponentId, componentType = typeName },
_ =>
diff --git a/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentListCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentListCliCommand.cs
index f824fc0..abe3883 100644
--- a/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentListCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentListCliCommand.cs
@@ -4,6 +4,7 @@
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
+using TALXIS.Platform.Metadata;
namespace TALXIS.CLI.Features.Environment.Solution.Component;
@@ -33,14 +34,16 @@ protected override async Task ExecuteAsync()
int? typeFilter = null;
if (!string.IsNullOrWhiteSpace(Type))
{
- var resolver = new ComponentTypeResolver();
- if (!resolver.TryResolveCode(Type, out var code))
+ var def = ComponentDefinitionRegistry.GetByName(Type);
+ if (def is null && int.TryParse(Type, out var parsedCode))
+ def = ComponentDefinitionRegistry.GetByType((ComponentType)parsedCode);
+ if (def is null)
{
- var known = string.Join(", ", resolver.GetKnownNames().Take(15));
- Logger.LogError("Unknown component type '{Type}'. Available types: {Known}. Or use an integer code.", Type, known);
+ var known = string.Join(", ", ComponentDefinitionRegistry.GetAll().Select(d => d.Name).Take(15));
+ Logger.LogError("Unknown component type '{Type}'. Available types: {Known}. Or use an integer code.", Type, known);
return ExitValidationError;
}
- typeFilter = code;
+ typeFilter = (int)def.TypeCode;
}
var service = TxcServices.Get();
diff --git a/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentRemoveCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentRemoveCliCommand.cs
index bab5fcc..47f8928 100644
--- a/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentRemoveCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Solution/Component/SolutionComponentRemoveCliCommand.cs
@@ -5,6 +5,7 @@
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
+using TALXIS.Platform.Metadata;
namespace TALXIS.CLI.Features.Environment.Solution.Component;
@@ -37,13 +38,16 @@ protected override async Task ExecuteAsync()
return ExitValidationError;
}
- var resolver = new ComponentTypeResolver();
- if (!resolver.TryResolveCode(Type, out var typeCode))
+ var def = ComponentDefinitionRegistry.GetByName(Type);
+ if (def is null && int.TryParse(Type, out var parsedCode))
+ def = ComponentDefinitionRegistry.GetByType((ComponentType)parsedCode);
+ if (def is null)
{
- var known = string.Join(", ", resolver.GetKnownNames().Take(15));
+ var known = string.Join(", ", ComponentDefinitionRegistry.GetAll().Select(d => d.Name).Take(15));
Logger.LogError("Unknown component type '{Type}'. Available types: {Known}. Or use an integer code.", Type, known);
return ExitValidationError;
}
+ var typeCode = (int)def.TypeCode;
// Pre-check: reject managed solutions (can't remove components from managed)
var detailService = TxcServices.Get();
@@ -58,7 +62,7 @@ protected override async Task ExecuteAsync()
var service = TxcServices.Get();
await service.RemoveAsync(Profile, options, CancellationToken.None).ConfigureAwait(false);
- var typeName = resolver.ResolveName(typeCode);
+ var typeName = def.Name;
OutputFormatter.WriteData(
new { status = "removed", solution = SolutionName, componentId = ComponentId, componentType = typeName },
_ =>
diff --git a/src/TALXIS.CLI.Features.Environment/Solution/SolutionUninstallCheckCliCommand.cs b/src/TALXIS.CLI.Features.Environment/Solution/SolutionUninstallCheckCliCommand.cs
index 2507641..e8696ea 100644
--- a/src/TALXIS.CLI.Features.Environment/Solution/SolutionUninstallCheckCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Environment/Solution/SolutionUninstallCheckCliCommand.cs
@@ -4,6 +4,7 @@
using TALXIS.CLI.Core.Contracts.Dataverse;
using TALXIS.CLI.Core.DependencyInjection;
using TALXIS.CLI.Logging;
+using TALXIS.Platform.Metadata;
namespace TALXIS.CLI.Features.Environment.Solution;
@@ -50,7 +51,6 @@ private void PrintSafe()
private void PrintBlocked(IReadOnlyList deps)
{
- var resolver = new ComponentTypeResolver();
OutputWriter.WriteLine($"Solution '{Name}' has {deps.Count} blocking dependency(ies):\n");
string header = $"{"Required Type",-25} | {"Required ID",-36} | {"Dependent Type",-25} | {"Dependent ID",-36} | Dep.Type";
@@ -59,8 +59,8 @@ private void PrintBlocked(IReadOnlyList deps)
foreach (var d in deps)
{
- var reqType = resolver.ResolveName(d.RequiredComponentType);
- var depType = resolver.ResolveName(d.DependentComponentType);
+ var reqType = ComponentDefinitionRegistry.GetByType((ComponentType)d.RequiredComponentType)?.Name ?? d.RequiredComponentType.ToString();
+ var depType = ComponentDefinitionRegistry.GetByType((ComponentType)d.DependentComponentType)?.Name ?? d.DependentComponentType.ToString();
var depKind = d.DependencyType switch
{
1 => "Internal",
diff --git a/src/TALXIS.CLI.Features.Workspace/ComponentCliCommand.cs b/src/TALXIS.CLI.Features.Workspace/ComponentCliCommand.cs
index 731ac0b..8fc8e64 100644
--- a/src/TALXIS.CLI.Features.Workspace/ComponentCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Workspace/ComponentCliCommand.cs
@@ -2,13 +2,18 @@
namespace TALXIS.CLI.Features.Workspace;
+///
+/// Workspace component operations — create and inspect components in the local repository.
+/// Component type introspection (list, explain) is at the top level: txc component type list/explain.
+///
[CliCommand(
- Description = "Create or modify components of your solution",
+ Description = "Create and inspect components in your local workspace",
Name = "component",
Alias = "comp",
Children = new[]
{
- typeof(ComponentCreateCliCommand)
+ typeof(ComponentCreateCliCommand),
+ typeof(ComponentParameterListCliCommand),
},
ShortFormAutoGenerate = CliNameAutoGenerate.None)]
public class ComponentCliCommand
@@ -17,30 +22,4 @@ public void Run(CliContext context)
{
context.ShowHelp();
}
-
- [CliCommand(
- Description = "Parameters for a specific component",
- Children = new[] { typeof(ComponentParameterListCliCommand) },
- Name = "parameter",
- ShortFormAutoGenerate = CliNameAutoGenerate.None)]
- public class ComponentParameterCliCommand
- {
- public void Run(CliContext context)
- {
- context.ShowHelp();
- }
- }
-
- [CliCommand(
- Description = "Types of available components",
- Children = new[] { typeof(ComponentTypeListCliCommand), typeof(ComponentTypeExplainCliCommand) },
- Name = "type",
- ShortFormAutoGenerate = CliNameAutoGenerate.None)]
- public class ComponentTypeCliCommand
- {
- public void Run(CliContext context)
- {
- context.ShowHelp();
- }
- }
}
diff --git a/src/TALXIS.CLI.Features.Workspace/ComponentParameterListCliCommand.cs b/src/TALXIS.CLI.Features.Workspace/ComponentParameterListCliCommand.cs
index ce45726..d0c0c19 100644
--- a/src/TALXIS.CLI.Features.Workspace/ComponentParameterListCliCommand.cs
+++ b/src/TALXIS.CLI.Features.Workspace/ComponentParameterListCliCommand.cs
@@ -13,8 +13,8 @@ namespace TALXIS.CLI.Features.Workspace;
///
[CliReadOnly]
[CliCommand(
- Description = "Lists parameters for a specific component",
- Name = "list"
+ Description = "Lists parameters for a specific component template. Use 'txc component type explain ' for type metadata.",
+ Name = "parameter-list"
)]
public class ComponentParameterListCliCommand : TxcLeafCommand
{
diff --git a/src/TALXIS.CLI.Features.Workspace/ComponentTypeExplainCliCommand.cs b/src/TALXIS.CLI.Features.Workspace/ComponentTypeExplainCliCommand.cs
deleted file mode 100644
index 026efca..0000000
--- a/src/TALXIS.CLI.Features.Workspace/ComponentTypeExplainCliCommand.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using DotMake.CommandLine;
-using Microsoft.Extensions.Logging;
-using TALXIS.CLI.Logging;
-using TALXIS.CLI.Core;
-using TALXIS.CLI.Features.Workspace.TemplateEngine;
-
-namespace TALXIS.CLI.Features.Workspace;
-
-[CliReadOnly]
-[CliCommand(
- Description = "Explains a solution component type. Use names returned by 'component type list' command",
- Name = "explain"
-)]
-public class ComponentTypeExplainCliCommand : TxcLeafCommand
-{
- protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(ComponentTypeExplainCliCommand));
-
- [CliArgument(Description = "Type of the component to explain")]
- public required string Type { get; set; }
-
- protected override async Task ExecuteAsync()
- {
- if (string.IsNullOrWhiteSpace(Type))
- {
- Logger.LogError("Please provide a component type");
- return ExitValidationError;
- }
-
- using var scaffolder = new TemplateInvoker();
- var templates = await scaffolder.ListTemplatesAsync();
- var template = templates?.FirstOrDefault(t => string.Equals(t.Name, Type, StringComparison.OrdinalIgnoreCase)
- || t.ShortNameList.Any(sn => string.Equals(sn, Type, StringComparison.OrdinalIgnoreCase)));
-
- if (template == null)
- {
- Logger.LogError("Component template {Type} not found", Type);
- return ExitValidationError;
- }
-
- var data = new
- {
- type = template.ShortNameList.FirstOrDefault(),
- description = template.Description
- };
-
- OutputFormatter.WriteData(data, d =>
- {
- OutputWriter.WriteLine($"Type: {d.type}");
- OutputWriter.WriteLine($"Description: {d.description}");
- });
-
- return ExitSuccess;
- }
-}
diff --git a/src/TALXIS.CLI.Features.Workspace/ComponentTypeListCliCommand.cs b/src/TALXIS.CLI.Features.Workspace/ComponentTypeListCliCommand.cs
deleted file mode 100644
index da7367c..0000000
--- a/src/TALXIS.CLI.Features.Workspace/ComponentTypeListCliCommand.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using DotMake.CommandLine;
-using Microsoft.Extensions.Logging;
-using TALXIS.CLI.Core;
-using TALXIS.CLI.Logging;
-using TALXIS.CLI.Features.Workspace.TemplateEngine;
-
-namespace TALXIS.CLI.Features.Workspace;
-
-[CliReadOnly]
-[CliCommand(
- Description = "Lists available solution components",
- Name = "list"
-)]
-public class ComponentTypeListCliCommand : TxcLeafCommand
-{
- protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(ComponentTypeListCliCommand));
-
- protected override async Task ExecuteAsync()
- {
- using var scaffolder = new TemplateInvoker();
- var templates = await scaffolder.ListTemplatesAsync();
- if (templates == null || !templates.Any())
- {
- OutputFormatter.WriteList(Array.Empty