Module: Lich::Main::ArgNormalization

Defined in:
documented/main/arg_normalization.rb

Overview

Normalizes user-facing CLI aliases into the existing lower-level argument forms consumed elsewhere in startup.

Constant Summary collapse

HEADLESS_PATTERN =

Matches --headless CLI arguments with optional inline port or target.

Captures the port/target token if specified inline (e.g., --headless=8080), or nil if the token follows as the next argument (e.g., --headless 8080).

Examples:

"--headless=8080".match?(HEADLESS_PATTERN) #=> true
"--headless=auto".match?(HEADLESS_PATTERN) #=> true
"--headless".match?(HEADLESS_PATTERN) #=> true
"--headless=".match?(HEADLESS_PATTERN) #=> true

See Also:

/^--headless(?:=(.+))?$/i.freeze
DETACHABLE_CLIENT_PREFIX =

Prefix for the --detachable-client CLI argument.

Used to construct arguments like --detachable-client=8080 or --detachable-client=127.0.0.1:8080 that enable headless operation with a remote frontend client.

'--detachable-client='.freeze

Class Method Summary collapse

Class Method Details

.normalize!(argv) ⇒ Array<String>

Rewrites high-level aliases in-place on the provided argv array.

Current rules:

  • --headless PORT => --without-frontend --detachable-client=PORT
  • --headless auto => --without-frontend --detachable-client=0
  • --headless HOST:PORT => --without-frontend --detachable-client=HOST:PORT (HOST may be tailscale, lan, any, an IP address, or a hostname)

Bare --headless is rejected because an unattached fully headless login is not a supported public workflow.

Parameters:

  • argv (Array<String>)

    mutable argument vector

Returns:

  • (Array<String>)

    the normalized argv array

Raises:

  • (ArgumentError)

    when --headless is missing a port or conflicts with an explicit detachable-client flag



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'documented/main/arg_normalization.rb', line 48

def self.normalize!(argv)
  headless_indices = argv.each_index.select { |index| argv[index].match?(HEADLESS_PATTERN) }
  return argv if headless_indices.empty?
  raise ArgumentError, '--headless may only be specified once' if headless_indices.length > 1

  if argv.any? { |arg| arg.start_with?(DETACHABLE_CLIENT_PREFIX) }
    raise ArgumentError, '--headless cannot be combined with --detachable-client'
  end

  headless_index = headless_indices.first
  headless_arg = argv[headless_index]
  inline_match = HEADLESS_PATTERN.match(headless_arg)
  port_token = inline_match[1]

  if port_token.nil?
    next_arg = argv[headless_index + 1]
    if next_arg.nil? || next_arg.start_with?('--')
      raise ArgumentError, '--headless requires a port number or auto'
    end

    port_token = next_arg
    argv.delete_at(headless_index + 1)
  end

  argv[headless_index] = '--without-frontend'
  argv.insert(headless_index + 1, "#{DETACHABLE_CLIENT_PREFIX}#{normalize_headless_target(port_token)}")
  argv
end

.normalize_headless_target(token) ⇒ String

Resolves the target token accepted by --headless to the canonical form consumed by --detachable-client.

Parameters:

  • token (String)

    user-provided token after --headless

Returns:

  • (String)

    canonical PORT or HOST:PORT (port 0 means OS-assigned auto)

Raises:

  • (ArgumentError)

    when the token is not a valid port, auto, or HOST:PORT form



85
86
87
88
89
90
91
92
93
94
# File 'documented/main/arg_normalization.rb', line 85

def self.normalize_headless_target(token)
  target = DetachableClientTarget.parse(token)
  return target.port.to_s if target.host.nil?

  host = target.host.include?(':') ? "[#{target.host}]" : target.host
  "#{host}:#{target.port}"
rescue DetachableClientTarget::ParseError
  raise ArgumentError, '--headless requires a port number between 1 and 65535, auto, ' \
                       'or HOST:PORT (HOST may be tailscale, lan, any, an IP address, or a hostname)'
end