private/Split-WtwOnHostArgs.ps1

function Split-WtwOnHostArgs {
    <#
    .SYNOPSIS
        Extract the target host from a raw wtw argument list.
    .DESCRIPTION
        Invoke-Wtw dispatches on $args[0], so a leading `--on` would be read as
        the subcommand. This runs first and strips the host selector wherever it
        appears, leaving a normal argument list behind.

        Two accepted forms:
          wtw --on at cursor auth explicit, position-independent
          wtw at cursor auth shorthand — only when the first token is a
                                      known host AND something follows it

        The shorthand is deliberately the most conservative: it requires an exact
        host name or alias, never a prefix, because a prefix could shadow a repo
        alias and silently send you to the wrong machine. `--on` itself does
        allow prefix resolution, since there the intent is unambiguous.

        There is deliberately no `@host` form: PowerShell's argument mode reads a
        bare `@at` as splatting of `$at`, so an undefined variable makes the token
        vanish before wtw is even called — the request would silently run locally.
    .PARAMETER ArgList
        Raw arguments as received by Invoke-Wtw.
    .PARAMETER KnownHosts
        Flat list of host names and aliases (Get-WtwHostNames). Passed in rather
        than read from config so this stays a pure function.
    .OUTPUTS
        @{ Host = <string|$null>; Args = <object[]> }
    #>

    [CmdletBinding()]
    param(
        [AllowNull()] [object[]] $ArgList,
        [AllowNull()] [string[]] $KnownHosts
    )

    $remaining = @()
    $targetHost = $null
    $items = @($ArgList)
    $i = 0

    while ($i -lt $items.Count) {
        $arg = "$($items[$i])"

        if ($arg -ieq '--on' -or $arg -ieq '-on') {
            if (($i + 1) -lt $items.Count) {
                $targetHost = "$($items[$i + 1])"
                $i += 2
                continue
            }
            # Dangling `--on` — drop it and let the caller report a missing host.
            $i++
            continue
        }

        # Comma operator, not a bare +=. PowerShell argument mode turns
        # `--alias at,troll` into ONE array-valued argument, and `+=` would
        # splice it into two tokens — the flag would take 'at' and 'troll' would
        # silently become a stray positional. This pre-scan must be transparent
        # to every argument shape it does not consume.
        $remaining += , $items[$i]
        $i++
    }

    # Leading bare host: `wtw at cursor auth`. Exact match only.
    if (-not $targetHost -and $remaining.Count -ge 2 -and $KnownHosts) {
        $first = "$($remaining[0])"
        if ($KnownHosts -contains $first) {
            $targetHost = $first
            $remaining = @($remaining[1..($remaining.Count - 1)])
        }
    }

    return @{ Host = $targetHost; Args = @($remaining) }
}

function Remove-WtwFlagWithValue {
    <#
    .SYNOPSIS
        Drop a `--flag value` pair from a raw argument list.
    .DESCRIPTION
        Local-only flags must not reach the remote CLI. `wtw --on at list --via lan`
        forwards its flags verbatim so the remote's own parser handles
        `--detailed` and friends — but `--via` is consumed here, and the remote
        `wtw list` has no such parameter, so leaving it in makes the whole command
        fail on the far side.
    .PARAMETER ArgList
        Raw tokens.
    .PARAMETER Flag
        Flag name without dashes, matched case-insensitively.
    #>

    [CmdletBinding()]
    param(
        [AllowNull()] [object[]] $ArgList,
        [Parameter(Mandatory)] [string] $Flag
    )

    $items = @($ArgList)
    $kept = @()
    $i = 0
    while ($i -lt $items.Count) {
        $token = "$($items[$i])"
        if ($token -ieq "--$Flag" -or $token -ieq "-$Flag") {
            # Skip the flag and its value, if the next token is not another flag.
            $i++
            if ($i -lt $items.Count -and "$($items[$i])" -notmatch '^-') { $i++ }
            continue
        }
        $kept += , $items[$i]
        $i++
    }
    return , @($kept)
}

# Commands that mutate the remote's own registry. They are not refused — they
# are executed ON the remote, which is what keeps its registry authoritative.
$script:WtwRemoteExecCommands = @(
    'run', 'create', 'add', 'remove', 'rm', 'delete', 'del', 'sync', 'color',
    'clean', 'init', 'workspace', 'ws', 'copy', 'unregister', 'unreg', 'install', 'update'
)

function Get-WtwRemoteCommandMode {
    <#
    .SYNOPSIS
        How a subcommand should be handled when `--on` is present.
    .DESCRIPTION
        Four outcomes:

          local interpreted here — a read whose result we render, or a VS Code
                   editor launch where the window is local and the files remote.
          exec forwarded verbatim and executed on the remote machine.
          connect an interactive ssh session inside the remote worktree.
          none refused, because no reading of it makes sense.

        `go` maps to connect rather than being refused: the verb means "be in
        that worktree", and over ssh that is exactly what it can do. The
        app-launchers (t3, cmux, claudecode, chatgpt…) stay refused because they
        are ambiguous rather than impossible — "register that project over there"
        is meaningful — so they are reachable through the explicit `run`, where
        the intent is stated rather than guessed.
    .OUTPUTS
        'local' | 'exec' | 'connect' | 'none'
    #>

    [CmdletBinding()]
    param([AllowNull()] [string] $Command)

    if (-not $Command) { return 'none' }
    if ($Command -in @('go', 'connect', 'conn', 'ssh')) { return 'connect' }
    if ($Command -in @('open', 'list', 'ls', 'info', 'show')) { return 'local' }
    if (Resolve-WtwEditorFamilyMember -Name $Command) { return 'local' }
    if ($Command -in $script:WtwRemoteExecCommands) { return 'exec' }
    return 'none'
}

function Test-WtwRemoteCapableCommand {
    <#
    .SYNOPSIS
        Can this subcommand be used with --on at all?
    #>

    [CmdletBinding()]
    param([AllowNull()] [string] $Command)

    return ((Get-WtwRemoteCommandMode -Command $Command) -ne 'none')
}