Coverage for little_loops / cli / adapt.py: 15%

53 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2026-06-29 00:55 -0500

1"""ll-adapt: unified host-parameterized skill/command/agent adapter. 

2 

3Dispatches to host-specific emitters via the :func:`resolve_emitter` registry. 

4Dry-run by default; use ``--apply`` to write changes. 

5""" 

6 

7from __future__ import annotations 

8 

9import argparse 

10import sys 

11 

12from little_loops.adapters.core import AdapterError, process_agents, process_commands, process_skills, resolve_emitter 

13from little_loops.session_store import DEFAULT_DB_PATH, cli_event_context 

14 

15__all__ = ["main_adapt"] 

16 

17 

18def _find_plugin_root(): 

19 from little_loops.skill_expander import _find_plugin_root as _fpr 

20 

21 return _fpr() 

22 

23 

24def main_adapt() -> int: 

25 """Entry point for ``ll-adapt`` CLI.""" 

26 with cli_event_context(DEFAULT_DB_PATH, "ll-adapt", sys.argv[1:]): 

27 parser = argparse.ArgumentParser( 

28 prog="ll-adapt", 

29 description=( 

30 "Generate host-specific skill, command, and agent artefacts for a given host. " 

31 "Dry-run by default; use --apply to write changes." 

32 ), 

33 formatter_class=argparse.RawDescriptionHelpFormatter, 

34 epilog=""" 

35Examples: 

36 ll-adapt --host codex # Dry-run: preview proposed changes 

37 ll-adapt --host codex --apply # Write Codex artefacts 

38 ll-adapt --host codex --only codebase-analyzer --apply # Single agent 

39 ll-adapt --host codex --quiet # Suppress per-entry output 

40""", 

41 ) 

42 parser.add_argument( 

43 "--host", 

44 required=True, 

45 metavar="HOST", 

46 help="Target host (e.g. codex, omp)", 

47 ) 

48 parser.add_argument( 

49 "--apply", 

50 action="store_true", 

51 default=False, 

52 help="Write changes (default: dry-run)", 

53 ) 

54 parser.add_argument( 

55 "--dry-run", 

56 action="store_true", 

57 default=False, 

58 dest="dry_run", 

59 help="Preview changes without writing (default behaviour; explicit alias)", 

60 ) 

61 parser.add_argument( 

62 "--only", 

63 metavar="NAME", 

64 default=None, 

65 help="Restrict agent processing to a single agent by stem name", 

66 ) 

67 parser.add_argument( 

68 "--quiet", 

69 action="store_true", 

70 default=False, 

71 help="Suppress per-entry output; only print final summary", 

72 ) 

73 

74 args = parser.parse_args() 

75 apply = args.apply and not args.dry_run 

76 

77 try: 

78 emitter = resolve_emitter(args.host) 

79 except AdapterError as exc: 

80 print(f"ERROR: {exc}", file=sys.stderr) 

81 return 1 

82 

83 plugin_root = _find_plugin_root() 

84 skills_dir = plugin_root / "skills" 

85 commands_dir = plugin_root / "commands" 

86 agents_dir = plugin_root / "agents" 

87 

88 mode = "APPLY" if apply else "DRY-RUN" 

89 if not args.quiet: 

90 print(f"ll-adapt --host {args.host} [{mode}]") 

91 print(f"Plugin root: {plugin_root}") 

92 if args.only: 

93 print(f"Filter: {args.only}") 

94 print() 

95 

96 total_adapted = total_skipped = total_errors = 0 

97 

98 # Skills 

99 if skills_dir.exists(): 

100 s_adapted, s_skipped, s_errors = process_skills( 

101 emitter, skills_dir, apply, args.quiet 

102 ) 

103 total_adapted += s_adapted 

104 total_skipped += s_skipped 

105 total_errors += s_errors 

106 

107 # Commands 

108 c_adapted, c_skipped, c_errors = process_commands( 

109 emitter, commands_dir, skills_dir, apply, args.quiet 

110 ) 

111 total_adapted += c_adapted 

112 total_skipped += c_skipped 

113 total_errors += c_errors 

114 

115 # Agents 

116 if agents_dir.exists(): 

117 codex_dir = plugin_root / ".codex" / "agents" 

118 a_adapted, a_skipped, a_errors = process_agents( 

119 emitter, agents_dir, codex_dir, apply, args.quiet, args.only 

120 ) 

121 total_adapted += a_adapted 

122 total_skipped += a_skipped 

123 total_errors += a_errors 

124 

125 print(f"\nDone: {total_adapted} adapted, {total_skipped} skipped, {total_errors} errors") 

126 return 0 if total_errors == 0 else 1