1"""ll-adapt-agents-for-codex: thin alias for ``ll-adapt --host codex`` (agents).

2

3 The implementation now lives in :mod:`little_loops.adapters.codex` and

4:mod:`little_loops.adapters.core`. This module is kept for backward

5 compatibility; it re-exports the original helper names used by tests and

6 delegates processing to :class:`~little_loops.adapters.codex.CodexEmitter`.

7"""

8

9 from __future__ import annotations

10

11 import argparse

12 import sys

13 from pathlib import Path

14

15 from little_loops.adapters.codex import (

16 CodexEmitter,

17 _MARKER,

18 _extract_agent_short_desc as _extract_short_desc,

19 _format_agent_toml,

20)

21 from little_loops.adapters.core import _read_frontmatter, process_agents as _core_process_agents

22 from little_loops.session_store import DEFAULT_DB_PATH, cli_event_context

23

24__all__ = ["main_adapt_agents_for_codex"]

25

26_MAX_SHORT_DESC = 80

27

28

29 def _find_plugin_root() -> Path:

30 from little_loops.skill_expander import _find_plugin_root as _fpr

31

32 return _fpr()

33

34

35# ---------------------------------------------------------------------------

36# Backward-compat function wrappers (original signatures)

37# ---------------------------------------------------------------------------

38

39

40 def _extract_agent_frontmatter(text: str) -> dict | None:

41 """Backward-compat wrapper around core._read_frontmatter."""

42 return _read_frontmatter(text)

43

44

45 def _emit_agent_toml(name: str, description: str, model: str, body: str) -> str:

46 """Backward-compat wrapper (4-arg): delegates to _format_agent_toml with empty fm."""

47 return _format_agent_toml(name, description, model, body, {})

48

49

50 def _process_agents(

51 agents_dir: Path,

52 codex_dir: Path,

53 apply: bool,

54 quiet: bool,

55 only: str | None,

56) -> tuple[int, int, int]:

57 """Backward-compat wrapper: delegate to core traversal + CodexEmitter."""

58 return _core_process_agents(CodexEmitter(), agents_dir, codex_dir, apply, quiet, only)

59

60

61# ---------------------------------------------------------------------------

62# CLI entry point

63# ---------------------------------------------------------------------------

64

65

66 def main_adapt_agents_for_codex() -> int:

67 """Entry point for ll-adapt-agents-for-codex CLI."""

68 with cli_event_context(DEFAULT_DB_PATH, "ll-adapt-agents-for-codex", sys.argv[1:]):

69 parser = argparse.ArgumentParser(

70 prog="ll-adapt-agents-for-codex",

71 description=(

72 "Emit .codex/agents/<name>.toml files from agents/*.md for Codex subagent "

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

74 "(Thin alias for: ll-adapt --host codex)"

75 ),

76 formatter_class=argparse.RawDescriptionHelpFormatter,

77 epilog="""

78 Examples:

79 ll-adapt-agents-for-codex # Dry-run: preview proposed changes

80 ll-adapt-agents-for-codex --apply # Write .codex/agents/*.toml files

81 ll-adapt-agents-for-codex --only codebase-analyzer --apply # Single agent

82 ll-adapt-agents-for-codex --quiet # Suppress per-entry output

83""",

84 )

85 parser.add_argument(

86 "--apply",

87 action="store_true",

88 default=False,

89 help="Write .codex/agents/*.toml files (default: dry-run)",

90 )

91 parser.add_argument(

92 "--only",

93 metavar="NAME",

94 default=None,

95 help="Restrict to a single agent by stem name (e.g. codebase-analyzer)",

96 )

97 parser.add_argument(

98 "--quiet",

99 action="store_true",

100 default=False,

101 help="Suppress per-agent output; only print final summary",

102 )

103

104 args = parser.parse_args()

105

106 plugin_root = _find_plugin_root()

107 agents_dir = plugin_root / "agents"

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

109

110 if not agents_dir.exists():

111 print(f"ERROR: agents directory not found: {agents_dir}", file=sys.stderr)

112 return 1

113

114 mode = "APPLY" if args.apply else "DRY-RUN"

115 if not args.quiet:

116 print(f"ll-adapt-agents-for-codex [{mode}]")

117 print(f"Agents dir: {agents_dir}")

118 print(f"Output dir: {codex_dir}")

119 if args.only:

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

121 print()

122

123 adapted, skipped, errors = _process_agents(

124 agents_dir, codex_dir, args.apply, args.quiet, args.only

125 )

126

127 print(f"\nDone: {adapted} adapted, {skipped} skipped, {errors} errors")

128 return 0 if errors == 0 else 1