lithermes-ai 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import argparse
|
|
3
4
|
import functools
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Any
|
|
@@ -116,16 +117,40 @@ def _pre_llm_call(**kwargs: Any) -> dict[str, str] | None:
|
|
|
116
117
|
|
|
117
118
|
|
|
118
119
|
def _setup_lithermes_cli(parser) -> None:
|
|
120
|
+
try:
|
|
121
|
+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
|
|
122
|
+
except Exception:
|
|
123
|
+
pass
|
|
124
|
+
parser.description = "LitHermes — Hermes-native Litwork toolkit (litgoal runtime, skills, hooks)."
|
|
125
|
+
parser.epilog = (
|
|
126
|
+
"slash commands: /lit /lit-loop /lit-plan /litgoal /review-work /start-work /deep-interview\n"
|
|
127
|
+
"skills: 20 lithermes:* skills — `hermes lithermes status` lists them\n"
|
|
128
|
+
"hooks: pre_llm_call, subagent_stop, transform_llm_output\n"
|
|
129
|
+
"run `hermes lithermes status` for versions + full surface, or `doctor` for health checks"
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument("--version", action="store_true", help="print the LitHermes plugin version")
|
|
119
132
|
sub = parser.add_subparsers(dest="lh_cmd")
|
|
133
|
+
sub.add_parser("version", help="print the LitHermes plugin version")
|
|
134
|
+
sub.add_parser("status", help="show LitHermes + Hermes versions, hooks, commands, skills")
|
|
135
|
+
sub.add_parser("doctor", help="run LitHermes health checks")
|
|
120
136
|
goal_parser = sub.add_parser("goal", help="LitHermes litgoal durable runtime")
|
|
121
137
|
litgoal_cli.setup(goal_parser)
|
|
122
138
|
|
|
123
139
|
|
|
124
140
|
def _handle_lithermes_cli(args) -> int:
|
|
125
|
-
|
|
141
|
+
cmd = getattr(args, "lh_cmd", None)
|
|
142
|
+
if getattr(args, "version", False) or cmd == "version":
|
|
143
|
+
print(core.version_line())
|
|
144
|
+
return 0
|
|
145
|
+
if cmd == "doctor":
|
|
146
|
+
lines, code = core.doctor_report()
|
|
147
|
+
print("\n".join(lines))
|
|
148
|
+
return code
|
|
149
|
+
if cmd == "goal":
|
|
126
150
|
return litgoal_cli.handle(args)
|
|
127
|
-
|
|
128
|
-
|
|
151
|
+
# status, or bare `hermes lithermes` → the most useful "who am I" answer
|
|
152
|
+
print(core.status_report())
|
|
153
|
+
return 0
|
|
129
154
|
|
|
130
155
|
|
|
131
156
|
def _ignited(handler):
|
|
@@ -981,3 +981,107 @@ def command_start_work(raw_args: str) -> str | dict[str, str]:
|
|
|
981
981
|
"display": display,
|
|
982
982
|
"agent_message": build_run_agent_message(load_run_state(run_dir)),
|
|
983
983
|
}
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
# ---------------------------------------------------------------------------
|
|
987
|
+
# Management / diagnostics surface (hermes lithermes version|status|doctor).
|
|
988
|
+
# Hermes plugins otherwise can't answer "who am I and what version" — these
|
|
989
|
+
# helpers back the CLI subcommands wired in __init__.py.
|
|
990
|
+
# ---------------------------------------------------------------------------
|
|
991
|
+
|
|
992
|
+
PLUGIN_ROOT = Path(__file__).resolve().parent
|
|
993
|
+
# These mirror what register()/register_hook()/litgoal tools wire up in __init__.py.
|
|
994
|
+
# They are hardcoded (not introspected) so `status` works without a live ctx —
|
|
995
|
+
# keep them in sync when adding a hook / slash command / goal tool.
|
|
996
|
+
HOOKS = ("pre_llm_call", "subagent_stop", "transform_llm_output")
|
|
997
|
+
SLASH_COMMANDS = (
|
|
998
|
+
"lit", "lit-loop", "lit-plan", "litgoal", "review-work",
|
|
999
|
+
"start-work", "deep-interview", "litwork-loop", "litwork-plan",
|
|
1000
|
+
)
|
|
1001
|
+
GOAL_TOOLS = (
|
|
1002
|
+
"goal_set", "goal_status", "goal_add_criterion", "goal_evidence",
|
|
1003
|
+
"goal_criterion_status", "goal_steer", "goal_checkpoint", "goal_complete",
|
|
1004
|
+
)
|
|
1005
|
+
_VERSION_RE = re.compile(r"^version:\s*(.+)$", re.MULTILINE)
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def plugin_version() -> str:
|
|
1009
|
+
"""Read the LitHermes plugin version from the bundled plugin.yaml."""
|
|
1010
|
+
try:
|
|
1011
|
+
m = _VERSION_RE.search((PLUGIN_ROOT / "plugin.yaml").read_text(encoding="utf-8"))
|
|
1012
|
+
return m.group(1).strip().strip("\"'") if m else "unknown"
|
|
1013
|
+
except (OSError, ValueError):
|
|
1014
|
+
return "unknown"
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def hermes_host_version() -> str:
|
|
1018
|
+
"""Best-effort Hermes host runtime version (unknown outside Hermes)."""
|
|
1019
|
+
try:
|
|
1020
|
+
import hermes_cli
|
|
1021
|
+
return str(getattr(hermes_cli, "__version__", "") or "unknown")
|
|
1022
|
+
except Exception:
|
|
1023
|
+
return "unknown"
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def version_line() -> str:
|
|
1027
|
+
return f"lithermes {plugin_version()}"
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def _evidence_kinds() -> tuple[str, ...]:
|
|
1031
|
+
try:
|
|
1032
|
+
from .litgoal import model
|
|
1033
|
+
return tuple(model.EVIDENCE_KINDS)
|
|
1034
|
+
except Exception:
|
|
1035
|
+
return ("red", "green", "scenario", "cleanup", "note")
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
def _skill_names() -> list[str]:
|
|
1039
|
+
skills_dir = PLUGIN_ROOT / "skills"
|
|
1040
|
+
if not skills_dir.is_dir():
|
|
1041
|
+
return []
|
|
1042
|
+
return sorted(d.name for d in skills_dir.iterdir() if (d / "SKILL.md").is_file())
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def status_report() -> str:
|
|
1046
|
+
"""One-glance health/identity line for `hermes lithermes status`."""
|
|
1047
|
+
skills = _skill_names()
|
|
1048
|
+
return "\n".join([
|
|
1049
|
+
f"LitHermes plugin {plugin_version()} (Hermes host {hermes_host_version()})",
|
|
1050
|
+
f"plugin dir: {PLUGIN_ROOT}",
|
|
1051
|
+
f"hooks ({len(HOOKS)}): {', '.join(HOOKS)}",
|
|
1052
|
+
f"slash commands ({len(SLASH_COMMANDS)}): {', '.join('/' + c for c in SLASH_COMMANDS)}",
|
|
1053
|
+
f"skills ({len(skills)}): {', '.join(skills)}",
|
|
1054
|
+
f"goal tools ({len(GOAL_TOOLS)}): {', '.join(GOAL_TOOLS)}",
|
|
1055
|
+
"litgoal state: .hermes/lithermes/litgoal/ (drive via: hermes lithermes goal status)",
|
|
1056
|
+
f"litgoal evidence kinds: {', '.join(_evidence_kinds())}",
|
|
1057
|
+
])
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def doctor_report() -> tuple[list[str], int]:
|
|
1061
|
+
"""Health checks for `hermes lithermes doctor`. Returns (lines, exit_code)."""
|
|
1062
|
+
lines: list[str] = []
|
|
1063
|
+
ok = True
|
|
1064
|
+
ver = plugin_version()
|
|
1065
|
+
if ver != "unknown":
|
|
1066
|
+
lines.append(f"[OK] plugin.yaml readable (version {ver})")
|
|
1067
|
+
else:
|
|
1068
|
+
lines.append("[WARN] plugin.yaml unreadable or missing a version field")
|
|
1069
|
+
ok = False
|
|
1070
|
+
skills = _skill_names()
|
|
1071
|
+
if skills:
|
|
1072
|
+
lines.append(f"[OK] skills bundled: {len(skills)}")
|
|
1073
|
+
else:
|
|
1074
|
+
lines.append("[WARN] no skills found under skills/")
|
|
1075
|
+
ok = False
|
|
1076
|
+
try:
|
|
1077
|
+
from .litgoal import runtime as _rt # noqa: F401
|
|
1078
|
+
lines.append("[OK] litgoal durable runtime importable")
|
|
1079
|
+
except Exception as exc:
|
|
1080
|
+
lines.append(f"[WARN] litgoal runtime import failed: {exc}")
|
|
1081
|
+
ok = False
|
|
1082
|
+
hv = hermes_host_version()
|
|
1083
|
+
if hv != "unknown":
|
|
1084
|
+
lines.append(f"[OK] Hermes host detected (v{hv})")
|
|
1085
|
+
else:
|
|
1086
|
+
lines.append("[NOTE] Hermes host version unknown (running outside Hermes?)")
|
|
1087
|
+
return lines, (0 if ok else 1)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"syncedAt": "2026-06-15T16:
|
|
2
|
+
"syncedAt": "2026-06-15T16:35:55.443Z",
|
|
3
3
|
"source": "source-reference",
|
|
4
|
-
"sourceHash": "
|
|
4
|
+
"sourceHash": "c93c37881e1f6f5730a1adc6c0e62e4dfab1ca44146eed900b8468448438fe8a",
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"path": "NOTICE.md",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "__init__.py",
|
|
16
|
-
"sha256": "
|
|
16
|
+
"sha256": "96b816aee730ea9a5e1fedbc283829240baffddecc37872e4f60bb05f420366c"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "core.py",
|
|
20
|
-
"sha256": "
|
|
20
|
+
"sha256": "70ddccfb4cc2fe1a923a5a61244e5cb36ef8ac0f94d34a76703fa6d82dbabf3f"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"path": "litgoal/__init__.py",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "plugin.yaml",
|
|
52
|
-
"sha256": "
|
|
52
|
+
"sha256": "7761c417acfcd614e8d434b3ca11c498f48422a72f5b70f817a59326aca51b60"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"path": "skills/ai-slop-remover/SKILL.md",
|