Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion taskuary/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
from . import __version__, config


def _port(value):
try: return config.port_number(value)
except ValueError as e: raise argparse.ArgumentTypeError(str(e)) from None

def public_url(host, port) -> str:
"""0.0.0.0 / :: are bind addresses, not a place a browser can go."""
shown = '127.0.0.1' if host in ('0.0.0.0', '::') else host
Expand Down Expand Up @@ -47,7 +51,7 @@ def open_when_ready(url: str, wait, open_it=None):
def main():
ap = argparse.ArgumentParser(prog='taskuary', description='Your work AI assistant - the local-first agent work hub.')
ap.add_argument('--host', help='override [server].host (0.0.0.0 to listen on all interfaces)')
ap.add_argument('--port', type=int, help='override [server].port')
ap.add_argument('--port', type=_port, help='override [server].port')
ap.add_argument('--no-browser', action='store_true', help="don't open a browser tab when the server starts")
ap.add_argument('--debug', action='store_true', help='verbose console logging (requests, report runs, errors)')
ap.add_argument('--version', action='version', version=f'taskuary {__version__}')
Expand Down
12 changes: 11 additions & 1 deletion taskuary/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
a token stored on the volume.
"""
import json, os
from loguru import logger
try: import tomllib
except ImportError: import tomli as tomllib # py3.10
from pathlib import Path
Expand Down Expand Up @@ -73,12 +74,21 @@ def _write(d: dict):
try: p.chmod(0o600)
except OSError: pass

def port_number(value) -> int:
"""A TCP port accepted by sockets and server runners."""
try: port = int(value)
except (TypeError, ValueError): raise ValueError('port must be a number from 1 to 65535') from None
if not 1 <= port <= 65535: raise ValueError('port must be a number from 1 to 65535')
return port

def _env_server() -> dict:
"""Non-empty TASKUARY_* overlays. Empty is unset — an injected '' must not disable a stored token."""
out = {}
h, p, t = os.getenv('TASKUARY_HOST'), os.getenv('TASKUARY_PORT'), os.getenv('TASKUARY_TOKEN')
if h: out['host'] = h
if p: out['port'] = int(p)
if p:
try: out['port'] = port_number(p)
except ValueError as e: logger.warning(f'ignoring TASKUARY_PORT={p!r}: {e}')
if t: out['token'] = t
return out

Expand Down
11 changes: 11 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ def test_env_overrides_server_bind(self):
self.assertEqual((cfg['server']['host'], cfg['server']['port'], cfg['server']['token']),
('0.0.0.0', 9000, 'abc'))

def test_invalid_env_port_is_ignored(self):
for value in ('abc', '0', '70000'):
with self.subTest(value=value), mock.patch.dict(os.environ, {'TASKUARY_PORT': value}, clear=True):
self.assertEqual(config._env_server(), {})

def test_cli_rejects_port_outside_socket_range(self):
with mock.patch('sys.argv', ['taskuary', '--port', '70000']):
with self.assertRaises(SystemExit) as stopped:
cli.main()
self.assertEqual(stopped.exception.code, 2)

def test_env_overrides_do_not_persist_on_agent_save(self):
"""Runtime overlays must not round-trip through save() — that's how Docker was
writing host = 0.0.0.0 and token = None onto the volume."""
Expand Down
Loading