55import argparse
66import asyncio
77import json
8+ import os
89import shlex
910import sys
1011from collections .abc import Callable
3435from .run_log import open_run_log_eagerly
3536from .scan import ScanResult , run_scan
3637from .serve import DEFAULT_HOST , DEFAULT_PORT , ServeConfig , serve
38+
39+
40+ def _env_truthy (name : str ) -> bool :
41+ return os .environ .get (name , "" ).strip ().lower () in ("1" , "true" , "yes" , "on" )
3742from .tasks import create_nl_task
38- from .tools import detect_tools , load_tool_registry , render_tools_detect_text
43+ from .tools import (
44+ build_tool_task_scaffold ,
45+ detect_tools ,
46+ find_tool_entry ,
47+ load_tool_registry ,
48+ render_tools_detect_text ,
49+ )
3950from .watch import watch_planfile_events
4051
4152
@@ -295,6 +306,29 @@ def _build_task_parser() -> argparse.ArgumentParser:
295306 parser .add_argument ("--sprint" , default = "current" , help = "Target planfile sprint." )
296307 parser .add_argument ("--queue-name" , default = None , help = "Execution queue for the new ticket." )
297308 parser .add_argument ("--priority" , default = "normal" , help = "Ticket priority." )
309+ parser .add_argument (
310+ "--tool" ,
311+ dest = "tool_id" ,
312+ default = None ,
313+ help = (
314+ "Build a tool-adapter scaffold ticket for this tool id "
315+ "(from docs/ai-tool-registry-2026.yaml)."
316+ ),
317+ )
318+ parser .add_argument (
319+ "--tool-kind" ,
320+ dest = "tool_kind" ,
321+ choices = ("human" , "shell" , "api" , "llm" ),
322+ default = None ,
323+ help = "Override scaffolded executor hint for --tool." ,
324+ )
325+ parser .add_argument (
326+ "--tool-registry" ,
327+ dest = "tool_registry" ,
328+ type = Path ,
329+ default = None ,
330+ help = "Override tool registry path for --tool lookup." ,
331+ )
298332 return parser
299333
300334
@@ -323,6 +357,14 @@ def _build_serve_parser() -> argparse.ArgumentParser:
323357 default = DEFAULT_PORT ,
324358 help = f"TCP port to listen on (default { DEFAULT_PORT } )." ,
325359 )
360+ parser .add_argument (
361+ "--auto-port" ,
362+ action = "store_true" ,
363+ help = (
364+ "If the port is busy, try the next ports (then an ephemeral port). "
365+ "Also on when KORU_SERVE_AUTO_PORT is 1/true/yes."
366+ ),
367+ )
326368 open_group = parser .add_mutually_exclusive_group ()
327369 open_group .add_argument (
328370 "--open" ,
@@ -876,20 +918,44 @@ def _build_agent_parser() -> argparse.ArgumentParser:
876918
877919def _task_main (argv : list [str ]) -> int :
878920 args = _build_task_parser ().parse_args (argv )
921+ scaffold : dict [str , Any ] | None = None
922+ if args .tool_id :
923+ registry , registry_path = load_tool_registry (args .tool_registry )
924+ if not registry :
925+ print (
926+ "koru task: tool registry is empty or missing. "
927+ "Use --tool-registry PATH or ensure docs/ai-tool-registry-2026.yaml exists."
928+ )
929+ return 2
930+ tool = find_tool_entry (registry , args .tool_id )
931+ if tool is None :
932+ known = ", " .join (sorted (str (t .get ("id" )) for t in registry if t .get ("id" )))
933+ print (f"koru task: unknown --tool '{ args .tool_id } '. Known ids: { known } " )
934+ return 2
935+ scaffold = build_tool_task_scaffold (tool , adapter_kind = args .tool_kind )
936+ if registry_path is not None :
937+ scaffold .setdefault ("source_context" , {})
938+ if isinstance (scaffold .get ("source_context" ), dict ):
939+ scaffold ["source_context" ]["registry" ] = str (registry_path )
940+
879941 try :
880942 created = create_nl_task (
881943 args .project ,
882944 " " .join (args .text ),
883945 sprint = args .sprint ,
884946 queue_name = args .queue_name ,
885947 priority = args .priority ,
948+ scaffold = scaffold ,
886949 )
887950 except ValueError as exc :
888951 print (f"koru task: { exc } " )
889952 return 2
890953 print (f"koru task: ✓ created { created .ticket_id } in { created .path } " )
891954 print (f" name: { created .name } " )
892955 print (f" queue: { args .queue_name or 'default' } " )
956+ if args .tool_id :
957+ print (f" tool: { args .tool_id } " )
958+ print (" note: scaffold ticket created — fill concrete executor inputs before queue run" )
893959 print ("Next: run `koru` to get the LLM prompt, or `koru --queue` to execute one task." )
894960 emit_management_event (
895961 tool = "koru.task" ,
@@ -915,14 +981,7 @@ def _serve_main(argv: list[str]) -> int:
915981 port = args .port ,
916982 open_browser = args .open_browser ,
917983 queue_name = args .queue_name ,
918- )
919- emit_management_event (
920- tool = "koru.serve" ,
921- action = "started" ,
922- status = "running" ,
923- message = f"http://{ config .host } :{ config .port } /" ,
924- queue = config .queue_name ,
925- details = {"project" : str (config .project ), "open_browser" : config .open_browser },
984+ auto_port = bool (args .auto_port ) or _env_truthy ("KORU_SERVE_AUTO_PORT" ),
926985 )
927986 exit_code = serve (config )
928987 emit_management_event (
0 commit comments