4040from pathlib import Path
4141
4242from .config import MempalaceConfig
43+ from .cli_write_routing import (
44+ add_cli_write_routing_flags ,
45+ resolve_cli_write_routing ,
46+ )
47+ from .write_routing import WriteRoutingError
4348from .corpus_origin import detect_origin_heuristic , detect_origin_llm
4449from .llm_client import LLMError , get_provider
4550from .version import __version__
@@ -102,6 +107,21 @@ def _maintenance_requires_chroma(palace_path: str, command_name: str) -> bool:
102107 return False
103108
104109
110+ def _resolve_cli_write_routing_or_exit (args , operation : str ):
111+ """Resolve routine CLI routing and render configuration errors."""
112+ try :
113+ return resolve_cli_write_routing (
114+ args ,
115+ operation = operation ,
116+ )
117+ except WriteRoutingError as exc :
118+ print (
119+ f"mempalace: invalid CLI write routing: { exc } " ,
120+ file = sys .stderr ,
121+ )
122+ raise SystemExit (2 ) from exc
123+
124+
105125def _gather_origin_samples (project_dir ) -> list :
106126 """Collect Tier-1 samples for corpus-origin detection.
107127
@@ -565,6 +585,39 @@ def _maybe_run_mine_after_init(args, cfg) -> None:
565585 return
566586
567587 palace_path = cfg .palace_path
588+ routing = _resolve_cli_write_routing_or_exit (
589+ args ,
590+ "init auto-mine" ,
591+ )
592+
593+ if routing .use_daemon :
594+ payload = {
595+ "source" : project_dir ,
596+ "mode" : "projects" ,
597+ "wing" : None ,
598+ "agent" : "mempalace" ,
599+ "limit" : 0 ,
600+ "dry_run" : False ,
601+ "extract" : "exchange" ,
602+ "no_gitignore" : False ,
603+ "include_ignored" : [],
604+ "max_chunks_per_file" : None ,
605+ "redetect_origin" : False ,
606+ "files" : (
607+ [str (file_path ) for file_path in scanned_files ]
608+ if scanned_files is not None
609+ else None
610+ ),
611+ }
612+ _submit_daemon_cli_job (
613+ "mine" ,
614+ payload ,
615+ args ,
616+ background = False ,
617+ auto_start = routing .decision .auto_start_daemon ,
618+ )
619+ return
620+
568621 try :
569622 mine (
570623 project_dir = project_dir ,
@@ -722,27 +775,43 @@ def cmd_mine(args):
722775 for raw in args .include_ignored or []:
723776 include_ignored .extend (part .strip () for part in raw .split ("," ) if part .strip ())
724777
725- if getattr (args , "background" , False ) and not getattr (args , "daemon" , False ):
726- print ("mempalace: --background requires --daemon" , file = sys .stderr )
727- sys .exit (2 )
778+ payload = {
779+ "source" : args .dir ,
780+ "mode" : mode ,
781+ "wing" : args .wing ,
782+ "agent" : args .agent ,
783+ "limit" : args .limit ,
784+ "dry_run" : args .dry_run ,
785+ "extract" : args .extract ,
786+ "no_gitignore" : args .no_gitignore ,
787+ "include_ignored" : include_ignored ,
788+ "max_chunks_per_file" : getattr (
789+ args ,
790+ "max_chunks_per_file" ,
791+ None ,
792+ ),
793+ "redetect_origin" : getattr (
794+ args ,
795+ "redetect_origin" ,
796+ False ,
797+ ),
798+ }
728799
729- if getattr (args , "daemon" , False ):
730- payload = {
731- "source" : args .dir ,
732- "mode" : mode ,
733- "wing" : args .wing ,
734- "agent" : args .agent ,
735- "limit" : args .limit ,
736- "dry_run" : args .dry_run ,
737- "extract" : args .extract ,
738- "no_gitignore" : args .no_gitignore ,
739- "include_ignored" : include_ignored ,
740- "max_chunks_per_file" : getattr (args , "max_chunks_per_file" , None ),
741- "redetect_origin" : getattr (args , "redetect_origin" , False ),
742- }
743- if source_adapter :
744- payload ["source_adapter" ] = source_adapter
745- _submit_daemon_cli_job ("mine" , payload , args , background = getattr (args , "background" , False ))
800+ if source_adapter :
801+ payload ["source_adapter" ] = source_adapter
802+
803+ routing = _resolve_cli_write_routing_or_exit (
804+ args ,
805+ "mine" ,
806+ )
807+ if routing .use_daemon :
808+ _submit_daemon_cli_job (
809+ "mine" ,
810+ payload ,
811+ args ,
812+ background = bool (getattr (args , "background" , False )),
813+ auto_start = routing .decision .auto_start_daemon ,
814+ )
746815 return
747816
748817 from .palace import MineAlreadyRunning , MineValidationError
@@ -1026,6 +1095,19 @@ def cmd_sweep(args):
10261095
10271096 palace_path = os .path .expanduser (args .palace ) if args .palace else MempalaceConfig ().palace_path
10281097 target = os .path .expanduser (args .target )
1098+ routing = _resolve_cli_write_routing_or_exit (
1099+ args ,
1100+ "sweep" ,
1101+ )
1102+ if routing .use_daemon :
1103+ _submit_daemon_cli_job (
1104+ "sweep" ,
1105+ {"target" : target },
1106+ args ,
1107+ background = bool (getattr (args , "background" , False )),
1108+ auto_start = routing .decision .auto_start_daemon ,
1109+ )
1110+ return
10291111
10301112 if os .path .isfile (target ):
10311113 result = sweep (target , palace_path )
@@ -1058,18 +1140,25 @@ def cmd_sync(args):
10581140 """Prune drawers whose source files are gitignored, deleted, or moved (#1252)."""
10591141 palace_path = os .path .expanduser (args .palace ) if args .palace else MempalaceConfig ().palace_path
10601142
1061- if getattr (args , "background" , False ) and not getattr (args , "daemon" , False ):
1062- print ("mempalace: --background requires --daemon" , file = sys .stderr )
1063- sys .exit (2 )
1143+ payload = {
1144+ "dir" : args .dir ,
1145+ "root" : list (args .root or []),
1146+ "wing" : args .wing ,
1147+ "dry_run" : args .dry_run ,
1148+ }
10641149
1065- if getattr (args , "daemon" , False ):
1066- payload = {
1067- "dir" : args .dir ,
1068- "root" : list (args .root or []),
1069- "wing" : args .wing ,
1070- "dry_run" : args .dry_run ,
1071- }
1072- _submit_daemon_cli_job ("sync" , payload , args , background = getattr (args , "background" , False ))
1150+ routing = _resolve_cli_write_routing_or_exit (
1151+ args ,
1152+ "sync" ,
1153+ )
1154+ if routing .use_daemon :
1155+ _submit_daemon_cli_job (
1156+ "sync" ,
1157+ payload ,
1158+ args ,
1159+ background = bool (getattr (args , "background" , False )),
1160+ auto_start = routing .decision .auto_start_daemon ,
1161+ )
10731162 return
10741163
10751164 from .palace import MineAlreadyRunning
@@ -1160,7 +1249,14 @@ def cmd_sync(args):
11601249 print (f"\n { '=' * 55 } \n " )
11611250
11621251
1163- def _submit_daemon_cli_job (kind : str , payload : dict , args , * , background : bool ) -> None :
1252+ def _submit_daemon_cli_job (
1253+ kind : str ,
1254+ payload : dict ,
1255+ args ,
1256+ * ,
1257+ background : bool ,
1258+ auto_start : bool = True ,
1259+ ) -> None :
11641260 palace_path = os .path .expanduser (args .palace ) if args .palace else MempalaceConfig ().palace_path
11651261 backend = _backend_arg (args )
11661262 from .daemon import DaemonError , submit_job
@@ -1172,7 +1268,7 @@ def _submit_daemon_cli_job(kind: str, payload: dict, args, *, background: bool)
11721268 palace_path = palace_path ,
11731269 backend = backend ,
11741270 wait = not background ,
1175- auto_start = True ,
1271+ auto_start = auto_start ,
11761272 # A job refused the palace lock is deferred, not failed (#2014), so
11771273 # it never becomes terminal while the holder lives. Waiting it out
11781274 # would strand this terminal behind a peer that can outlive the
@@ -2520,6 +2616,11 @@ def main():
25202616 ),
25212617 )
25222618
2619+ add_cli_write_routing_flags (
2620+ p_init ,
2621+ allow_background = False ,
2622+ )
2623+
25232624 # mine
25242625 p_mine = sub .add_parser ("mine" , help = "Mine files into the palace" )
25252626 p_mine .add_argument (
@@ -2582,16 +2683,7 @@ def main():
25822683 p_mine .add_argument (
25832684 "--dry-run" , action = "store_true" , help = "Show what would be filed without filing"
25842685 )
2585- p_mine .add_argument (
2586- "--daemon" ,
2587- action = "store_true" ,
2588- help = "Submit this mine to the opt-in local daemon queue" ,
2589- )
2590- p_mine .add_argument (
2591- "--background" ,
2592- action = "store_true" ,
2593- help = "With --daemon, return a job id immediately instead of waiting" ,
2594- )
2686+ add_cli_write_routing_flags (p_mine )
25952687 p_mine .add_argument (
25962688 "--extract" ,
25972689 choices = ["exchange" , "general" ],
@@ -2634,6 +2726,8 @@ def main():
26342726 help = "A .jsonl transcript file, or a directory to scan recursively" ,
26352727 )
26362728
2729+ add_cli_write_routing_flags (p_sweep )
2730+
26372731 # sync
26382732 p_sync = sub .add_parser (
26392733 "sync" ,
@@ -2665,16 +2759,7 @@ def main():
26652759 action = "store_false" ,
26662760 help = "Actually delete drawers (overrides --dry-run; requires --wing or a project root)" ,
26672761 )
2668- p_sync .add_argument (
2669- "--daemon" ,
2670- action = "store_true" ,
2671- help = "Submit this sync to the opt-in local daemon queue" ,
2672- )
2673- p_sync .add_argument (
2674- "--background" ,
2675- action = "store_true" ,
2676- help = "With --daemon, return a job id immediately instead of waiting" ,
2677- )
2762+ add_cli_write_routing_flags (p_sync )
26782763
26792764 # search
26802765 p_search = sub .add_parser ("search" , help = "Find anything, exact words" )
0 commit comments