diff --git a/src/GitHubActionsVS.csproj b/src/GitHubActionsVS.csproj
index c734762..af97c03 100644
--- a/src/GitHubActionsVS.csproj
+++ b/src/GitHubActionsVS.csproj
@@ -55,10 +55,14 @@
+
+
+
+
@@ -82,6 +86,9 @@
AddEditSecret.xaml
+
+ WorkflowInputsDialog.xaml
+
True
True
@@ -136,6 +143,9 @@
Designer
MSBuild:Compile
+
+ MSBuild:Compile
+
@@ -186,6 +196,9 @@
1.3.3
+
+ 16.3.0
+
diff --git a/src/Helpers/StringHelpers.cs b/src/Helpers/StringHelpers.cs
new file mode 100644
index 0000000..f3ca89f
--- /dev/null
+++ b/src/Helpers/StringHelpers.cs
@@ -0,0 +1,27 @@
+namespace GitHubActionsVS.Helpers
+{
+ internal class StringHelpers
+ {
+ public static bool IsBase64(string input)
+ {
+ if (string.IsNullOrWhiteSpace(input) || input.Length % 4 != 0)
+ return false;
+
+ foreach (char c in input)
+ {
+ if (!(char.IsLetterOrDigit(c) || c == '+' || c == '/' || c == '='))
+ return false;
+ }
+
+ try
+ {
+ _ = Convert.FromBase64String(input);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/Helpers/YamlHelpers.cs b/src/Helpers/YamlHelpers.cs
new file mode 100644
index 0000000..0a7f064
--- /dev/null
+++ b/src/Helpers/YamlHelpers.cs
@@ -0,0 +1,58 @@
+using System.Reflection;
+using YamlDotNet.RepresentationModel;
+
+namespace GitHubActionsVS.Helpers
+{
+ internal static class YamlHelpers
+ {
+ ///
+ /// Follows YamlDotNet alias nodes (&anchor / *alias) to the real node.
+ /// Returns the resolved node if an alias chain exists.
+ ///
+ public static YamlNode Unalias(YamlNode node)
+ {
+ // Use reflection to check for YamlAliasNode and access RealNode
+ var aliasType = node.GetType().FullName == "YamlDotNet.RepresentationModel.YamlAliasNode"
+ ? node.GetType()
+ : null;
+
+ while (aliasType != null)
+ {
+ var realNodeProp = aliasType.GetProperty("RealNode", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+ if (realNodeProp?.GetValue(node) is not YamlNode realNode)
+ break;
+ node = realNode;
+ aliasType = node.GetType().FullName == "YamlDotNet.RepresentationModel.YamlAliasNode"
+ ? node.GetType()
+ : null;
+ }
+
+ return node;
+ }
+
+ ///
+ /// Attempts to retrieve a child value from a mapping node by key name.
+ /// Uses string comparison on scalar node values rather than allocating new nodes.
+ ///
+ /// The mapping node to search.
+ /// The scalar key string to match.
+ /// The value node if found.
+ /// True if a matching key was found, otherwise false.
+ public static bool TryGetScalarKey(YamlMappingNode map, string key, out YamlNode value)
+ {
+ foreach (var kv in map.Children)
+ {
+ if (kv.Key is YamlScalarNode sk &&
+ string.Equals(sk.Value, key, StringComparison.Ordinal))
+ {
+ value = kv.Value;
+ return true;
+ }
+ }
+
+ value = null!;
+ return false;
+ }
+
+ }
+}
diff --git a/src/Models/InputMetadata.cs b/src/Models/InputMetadata.cs
new file mode 100644
index 0000000..0c143b0
--- /dev/null
+++ b/src/Models/InputMetadata.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace GitHubActionsVS.Models
+{
+ public sealed class InputMetadata
+ {
+ public string Name { get; }
+ public string Type { get; }
+ public string Description { get; }
+ public bool Required { get; }
+ public string Default { get; }
+ public string[] Options { get; }
+
+ public InputMetadata(string name, string type, string description, bool required, string @default, string[] options)
+ {
+ Name = name;
+ Type = type;
+ Description = description;
+ Required = required;
+ Default = @default;
+ Options = options;
+ }
+
+ public static List ToInputMeta(IReadOnlyDictionary> inputs)
+ {
+ var list = new List();
+
+ try
+ {
+ foreach (var kv in inputs)
+ {
+ var name = kv.Key;
+ var dict = kv.Value;
+
+ dict.TryGetValue("type", out var typeObj);
+ dict.TryGetValue("description", out var descObj);
+ dict.TryGetValue("required", out var reqObj);
+ dict.TryGetValue("default", out var defObj);
+ dict.TryGetValue("options", out var optsObj);
+
+ var type = (typeObj?.ToString()?.Trim().ToLowerInvariant()) switch
+ {
+ "boolean" => "boolean",
+ "choice" => "choice",
+ "environment" => "environment",
+ _ => "string"
+ };
+
+ string[] options = null;
+
+ if (optsObj is IEnumerable