diff --git a/Commands/Get-Htmx.ps1 b/Commands/Get-Htmx.ps1
new file mode 100644
index 0000000..42e67b3
--- /dev/null
+++ b/Commands/Get-Htmx.ps1
@@ -0,0 +1,196 @@
+function Get-HTMX {
+ <#
+ .SYNOPSIS
+ Creates HTMX tags in PowerShell.
+ .DESCRIPTION
+ Get-Htmx is a freeform function that creates HTML tags in PowerShell.
+
+ The function has no explicit parameters.
+
+ Instead, broadly speaking, arguments become attributes (unless it appears to be a tag) and inputs become children.
+ .NOTES
+ Ideally, this command is very forgiving in its input and helps you write HTMX tag in PowerShell.
+
+ If this proves not to be the case, feel free to open an issue.
+ .EXAMPLE
+ Get-Htmx div class=container "Hello, World!"
+
Hello, World!
+ .EXAMPLE
+ htmx button "Click Me" hx-get=api/endpoint hx-trigger=click
+ #>
+ [ArgumentCompleter({
+ param(
+ $wordToComplete,
+ $commandAst,
+ $cursorPosition
+ )
+ })]
+
+ param()
+
+ # Collect all input
+ $allInput = @($input)
+ # and any arguments
+ $allArguments = @($args)
+ # Unroll any arguments: any collections are expanded to their elements
+ $unrolledArguments = @($allArguments | . { process { $_ } })
+ # Create a list of child elements
+ $moreChildren = [Collections.Generic.List[Object]]::new()
+ # Add all input to the list of child elements
+ $moreChildren.AddRange($allInput)
+
+ # Create a collection of attributes
+ $attributes = [Ordered]@{}
+
+ # And a list of all future content.
+ $allContent = [Collections.Generic.List[Object]]::new()
+ $allContent.AddRange($allInput)
+
+ # Capture the invocation information, as it will be easier to debug with it.
+ $myInv = $MyInvocation
+
+ # If this function is called any other name, we will use that name as the tag name.
+ # To do this, we use a relatively simple pattern.
+ # Put in english, instead of Regex, it is:
+ # * Optionally Match Get, followed by one or more punctuation characters
+ # * Match HTMX, followed by zero or more punctuation characters
+ $getHtmxPrefixPattern = '(?>Get\p{P}+)?HTMX[\p{P}]{0,}'
+ $myName = $MyInvocation.InvocationName -replace $getHtmxPrefixPattern
+
+ # Now we walk over each argument and determine if it is an attribute or content.
+ foreach ($argument in $unrolledArguments) {
+
+ # Starting easy, if the argument is a dictionary, we will treat it as attributes.
+ if ($argument -is [Collections.IDictionary]) {
+ foreach ($key in $argument.Keys) {
+ $attributes[$key] = $argument[$key]
+ }
+ continue
+ }
+
+ # If the argument is a string, and we have not yet determined the tag name, we will use it.
+ if (
+ -not $myName -and
+ $argument -is [string] -and
+ # provided, of course, that it does not look like a tag or an attribute
+ $argument -notmatch '[\p{P}\<\>\s-[\-]]' -and $argument -notmatch '^hx-'
+ ) {
+ $myName = $argument
+ continue
+ }
+
+
+ # If the argument is _only_ whitespace and a colon or equals sign, we will skip it.
+ if ($argument -is [string] -and
+ $argument -match '^\s{0,}[=:]\s{0,}$') {
+ continue
+ }
+
+ # If the argument has a equals sign, surrounded by content, we will treat it as an attribute.
+ if ($argument -is [string] -and $argument -match '^.+?=.+?$') {
+ $key, $value = $argument -split '=', 2
+ $attributes[$key] = $value
+ continue
+ }
+
+ # If the argument is a tag, we will add it to the content.
+ if ($argument -match '[\<\>]') {
+ $allContent.Add($argument)
+ continue
+ }
+
+ # If we have any attribute without a value, we will treat the next argument as the value.
+ if ($attributes.Count -and $null -eq $attributes[-1]) {
+ $lastKey = @($attributes.Keys)[-1]
+ # If the last key is content, child, or children, we will treat the argument as a child.
+ if ($lastKey -in 'content', 'child', 'children') {
+ $moreChildren.Add($argument)
+ $attributes.RemoveAt($lastKey)
+ } else {
+ # Otherwise, we will treat it as a value.
+ $attributes[@($attributes.Keys)[-1]] = $argument
+ }
+
+ } else {
+ # Otherwise, if the argument is whitespace, we will add it to the content.
+ if ($argument -match '[\s\r\n]') {
+ $allContent.Add($argument)
+ } else {
+ # and if it does not we will treat it as an attribute name.
+ $attributes[$argument -replace '^[\p{P}]+'] = $null
+ }
+ }
+ }
+
+
+ # If we have no attributes and no children, we will return the module.
+ if (-not $attributes.Count -and -not $moreChildren.Count) {
+ return $HtmxPS
+ }
+
+ # Otherwise, we will create the tag.
+ $ElementName = if ($myName) { $myName } else { "html" }
+
+ @(
+ "<$ElementName"
+ # We will walk over each attribute and create the attribute string.
+ foreach ($attr in $attributes.GetEnumerator()) {
+ if (-not $attr.Key) { continue }
+ if (-not [String]::IsNullOrEmpty($attr.Value)) {
+ " $($attr.Key)=`"$([Web.HttpUtility]::HtmlAttributeEncode($attr.Value))`""
+ } else {
+ " $($attr.Key)"
+ }
+ }
+
+ # If we do not have children, we can close the tag now.
+ if (-not $allContent) { ' />'}
+ # Otherwise, we will close the tag after the children.
+ else { '>'}
+
+ if ($allContent) {
+ @(
+ # We will walk over each child and create the child string.
+ foreach ($contentItem in $allContent) {
+ # If the content has a `ToHtml` method, we will use it.
+ if ($contentItem.ToHtml.Invoke) {
+ $contentItem.ToHtml()
+ } elseif ($contentItem.Html) {
+ # Otherwise, we will look for an `Html` property.
+ $contentItem.Html
+ }
+ elseif ($contentItem.outerHTML) {
+ # Or an `outerHTML` property.
+ $contentItem.outerHTML
+ } elseif ($contentItem.OuterXML) {
+ # Or an `OuterXML` property.
+ $contentItem.OuterXML
+ } else {
+ # If none of those are available, we will use the content as is.
+ $contentItem
+ }
+ }
+ "$ElementName>"
+ ) -join ''
+ }
+ ) -join ''
+}
+
+# We have to register argument completers for the command and it's noun.
+$commandName = 'Get-HTMX'
+$CommandNameAndAliases = @(
+ $commandName
+ if ($commandName -match '^Get\p{P}+') {
+ $($commandName -replace 'Get\p{P}+')
+ }
+)
+# we do this by taking the argument completer attribute on ourself
+foreach ($attributes in $ExecutionContext.SessionState.InvokeCommand.GetCommand($commandName,'Function').ScriptBlock.Attributes) {
+ if ($attributes -is [ArgumentCompleter]) {
+ # and registering it for each command name and alias.
+ foreach ($commandToComplete in $CommandNameAndAliases) {
+ Register-ArgumentCompleter -CommandName $commandToComplete -ScriptBlock $attributes.ScriptBlock
+ }
+ break
+ }
+}