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

2

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

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

5"""

6

7 from __future__ import annotations

8

9 import argparse

10 import sys

11

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

13 from little_loops.session_store import DEFAULT_DB_PATH, cli_event_context

14

15__all__ = ["main_adapt"]

16

17

18 def _find_plugin_root():

19 from little_loops.skill_expander import _find_plugin_root as _fpr

20

21 return _fpr()

22

23

24 def 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="""

35 Examples:

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