@craftsamo/git-tools 0.1.0 → 0.2.1

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.
@@ -162,18 +162,13 @@ def resolve_target(
162
162
  """What ``target`` prints: ``["*"]`` for resources that cannot be told, no description for one absent or bad.
163
163
 
164
164
  ``language`` is the plugin's ``language`` setting and ``harness_language`` reads Hermes' display language
165
- per call; neither may fail the run, and ``target`` picks among them. ``option_flags`` are the flags of the
166
- plugin's tool options, which the call's description may read as ``run`` does.
165
+ per call; neither may fail the run, and ``target`` picks among them.
167
166
  """
168
167
  try:
169
168
  result = runtime.run(
170
169
  [
171
- "target",
172
- tool_id,
173
- "--cwd",
174
- agent_cwd(),
175
- *language_flags(language, _known(harness_language)),
176
- *(option_flags or []),
170
+ "target", tool_id, "--cwd", agent_cwd(),
171
+ *language_flags(language, _known(harness_language)), *(option_flags or []),
177
172
  ],
178
173
  stdin=json.dumps(run_arguments(args)),
179
174
  timeout=TARGET_TIMEOUT_SECONDS,
@@ -232,8 +227,7 @@ def make_hook(
232
227
 
233
228
  Whether a person is there to ask, and what happens when none is, is
234
229
  Hermes' own gate to decide. ``language`` is the plugin's ``language``
235
- setting; ``harness_language`` is read on every call; ``option_flags``
236
- are the flags of the plugin's tool options, passed to ``target``. When ``pins`` is
230
+ setting; ``harness_language`` is read on every call. When ``pins`` is
237
231
  given, the pin ``target`` answered is kept in it under the call's
238
232
  ``pin_key`` for the pin hook; the decision is the same either way.
239
233
  """
@@ -309,7 +303,6 @@ def register_approval(
309
303
  ) -> None:
310
304
  """Hold every non-read call of ``tools`` for approval, with the card in ``language`` (the plugin's setting).
311
305
 
312
- ``option_flags`` are the flags of the plugin's tool options, which ``target`` reads as ``run`` does.
313
306
  Two hooks, in this order, since Hermes runs them in registration order: the approval hook
314
307
  stashes the pin of what the card shows, then the pin hook gives it to the call.
315
308
  """
@@ -317,12 +310,7 @@ def register_approval(
317
310
  ctx.register_hook(
318
311
  HOOK_NAME,
319
312
  make_hook(
320
- runtime,
321
- tools,
322
- surface=lambda: session_surface(ctx),
323
- language=language,
324
- pins=pins,
325
- option_flags=option_flags,
313
+ runtime, tools, surface=lambda: session_surface(ctx), language=language, pins=pins, option_flags=option_flags
326
314
  ),
327
315
  )
328
316
  ctx.register_hook(HOOK_NAME, make_pin_hook(tools, pins))
@@ -9,7 +9,7 @@ from .context import PluginContext
9
9
  from .listing import list_tools
10
10
  from . import project
11
11
  from .runtime import Unavailable, locate_runtime
12
- from .settings import SettingsError, language_setting, option_flags, settings_flags
12
+ from .settings import SettingsError, emoji_setting, language_setting, option_flags, settings_flags
13
13
  from .skills import register_skills
14
14
  from .tools import register_tools
15
15
 
@@ -33,6 +33,6 @@ def register(ctx: PluginContext) -> None:
33
33
  logger.warning("%s is not available and registers nothing: %s", project.PLUGIN_NAME, error)
34
34
  return
35
35
 
36
- register_tools(ctx, runtime, tools, flags)
36
+ register_tools(ctx, runtime, tools, flags, emoji_setting(ctx))
37
37
  register_approval(ctx, runtime, tools, language_setting(ctx), option_flags(ctx))
38
38
  register_skills(ctx, runtime.skills_dir, {tool.toolset for tool in tools})
@@ -14,9 +14,10 @@ SKILL_PREFIX = "git-"
14
14
  #: The emoji each toolset is shown with; empty by default until a project adds its own.
15
15
  TOOLSET_EMOJI: dict[str, str] = {}
16
16
 
17
- #: The settings the tools themselves read, beside the ones every plugin takes: each holds text and is named in
18
- #: snake_case, as in ``src/project.ts``. A setting ``a_b`` becomes the flag ``--a-b`` of the command line.
19
- OPTIONS: tuple[str, ...] = ("published_text_rules", "worktree_root")
17
+ #: The settings the tools themselves read: each snake_case name and a type (``text``, ``boolean``, ``number``,
18
+ #: ``list``, ``path`` or ``paths``) or a dict with ``type`` and optional bounds (``integer``, ``min``, ``max``),
19
+ #: as in ``src/project.ts``. Write OPTIONS on one line with double quotes. A setting ``a_b`` becomes ``--a-b``.
20
+ OPTIONS: dict[str, str | dict[str, object]] = {"published_text_rules": "text", "worktree_root": "text"}
20
21
 
21
22
  __all__ = [
22
23
  "CLI_NAME",
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import math
6
+ from collections.abc import Mapping
5
7
  from typing import Any
6
8
 
7
9
  from . import project
@@ -31,30 +33,60 @@ def language_setting(ctx: PluginContext) -> str | None:
31
33
  return (value.strip() or None) if isinstance(value, str) else None
32
34
 
33
35
 
34
- def option_flags(ctx: PluginContext) -> list[str]:
35
- """Read the tool options (``project.OPTIONS``) and return the flags that say the same, each ``--a-b <text>``.
36
+ def emoji_setting(ctx: PluginContext, defaults: Mapping[str, str] | None = None) -> dict[str, str]:
37
+ """The emoji per toolset: ``defaults`` (``project.TOOLSET_EMOJI``) with string entries of ``emoji`` merged over it.
36
38
 
37
- An option that is absent or blank gives no flag, so the command line's own
38
- variable (``GIT_TOOLS_<OPTION>``) applies. These are the only flags
39
- ``target`` takes beside its language ones.
39
+ A setting that is not a ``dict``, and an entry whose key or value is not a
40
+ ``str``, are ignored so a typo never takes every tool away. It is no flag
41
+ of ``settings_flags``.
42
+ """
43
+ merged = dict(project.TOOLSET_EMOJI if defaults is None else defaults)
44
+ value = ctx.get_config("emoji")
45
+ if isinstance(value, dict):
46
+ merged.update({key: item for key, item in value.items() if isinstance(key, str) and isinstance(item, str)})
47
+ return merged
40
48
 
41
- Raises:
42
- SettingsError: an option is not text.
49
+
50
+ def _option_flag(name: str, declaration: str | dict[str, object], value: Any) -> list[str]:
51
+ kind = declaration["type"] if isinstance(declaration, dict) else declaration
52
+ flag = f"--{name.replace('_', '-')}"
53
+ if kind in ("text", "path"):
54
+ if not isinstance(value, str):
55
+ expected = "a path" if kind == "path" else "text"
56
+ raise SettingsError(f'setting "{name}" must be {expected}')
57
+ return [flag, text] if (text := value.strip()) else []
58
+ if kind == "boolean":
59
+ if not isinstance(value, bool):
60
+ raise SettingsError(f'setting "{name}" must be true or false')
61
+ return [flag] if value else []
62
+ if kind == "number":
63
+ if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
64
+ raise SettingsError(f'setting "{name}" must be a finite number')
65
+ return [flag, str(value)]
66
+ if kind in ("list", "paths"):
67
+ if not isinstance(value, list) or not all(isinstance(item, str) and "," not in item for item in value):
68
+ expected = "paths" if kind == "paths" else "strings"
69
+ raise SettingsError(f'setting "{name}" must be a list of {expected} without commas')
70
+ items = [item.strip() for item in value if item.strip()]
71
+ return [flag, ",".join(items)] if items else []
72
+ raise SettingsError(f'unknown option type "{kind}" for "{name}"')
73
+
74
+
75
+ def option_flags(ctx: PluginContext, options: Mapping[str, str | dict[str, object]] | None = None) -> list[str]:
76
+ """The package's tool options as flags, with absent settings left to the command line's environment.
77
+
78
+ Bounds and path absoluteness are checked by the command line, not here.
43
79
  """
44
80
  flags: list[str] = []
45
- for name in project.OPTIONS:
81
+ for name, declaration in (project.OPTIONS if options is None else options).items():
46
82
  value = ctx.get_config(name)
47
- if value is None:
48
- continue
49
- if not isinstance(value, str):
50
- raise SettingsError(f'setting "{name}" must be text')
51
- if text := value.strip():
52
- flags += [f"--{name.replace('_', '-')}", text]
83
+ if value is not None:
84
+ flags += _option_flag(name, declaration, value)
53
85
  return flags
54
86
 
55
87
 
56
88
  def settings_flags(ctx: PluginContext) -> list[str]:
57
- """Read ``toolsets``, ``read_only``, ``tools``, ``exclude_tools`` and the tool options and return the flags that say the same.
89
+ """Read ``toolsets``, ``read_only``, ``tools`` and ``exclude_tools`` and return the flags that say the same.
58
90
 
59
91
  A setting that is absent gives no flag, so the command line's own defaults
60
92
  (and ``GIT_TOOLS_*`` variables) apply. A wrong type is refused rather
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
- from collections.abc import Callable
6
+ from collections.abc import Callable, Mapping
7
7
  from typing import Any
8
8
 
9
9
  from .approval import PIN_ARGUMENT, parse_pin, run_arguments
@@ -76,14 +76,21 @@ def make_handler(runtime: Runtime, tool_id: str, flags: list[str]) -> Callable[.
76
76
  return handler
77
77
 
78
78
 
79
- def register_tools(ctx: PluginContext, runtime: Runtime, tools: list[ToolInfo], flags: list[str]) -> None:
79
+ def register_tools(
80
+ ctx: PluginContext,
81
+ runtime: Runtime,
82
+ tools: list[ToolInfo],
83
+ flags: list[str],
84
+ emoji: Mapping[str, str] | None = None,
85
+ ) -> None:
80
86
  """Register each listed tool under its own ID, grouped in one Hermes toolset per package toolset."""
87
+ toolset_emoji = TOOLSET_EMOJI if emoji is None else emoji
81
88
  for tool in tools:
82
89
  ctx.register_tool(
83
90
  name=tool.id,
84
91
  toolset=TOOLSET_PREFIX + tool.toolset.replace("_", "-"),
85
92
  schema={"name": tool.id, "description": tool.description, "parameters": tool.input_schema},
86
93
  handler=make_handler(runtime, tool.id, flags),
87
- emoji=TOOLSET_EMOJI.get(tool.toolset, ""),
94
+ emoji=toolset_emoji.get(tool.toolset, ""),
88
95
  description=tool.title,
89
96
  )