mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
find-nearby and the (new) maps optional skill both used OpenStreetMap's Overpass + Nominatim to answer the same question — 'what's near this location?' — so shipping both would be duplicate code for overlapping capability. Consolidate into one active-by-default skill at skills/productivity/maps/ that is a strict superset of find-nearby. Moves + deletions: - optional-skills/productivity/maps/ → skills/productivity/maps/ (active, no install step needed) - skills/leisure/find-nearby/ → DELETED (fully superseded) Upgrades to maps_client.py so it covers everything find-nearby did: - Overpass server failover — tries overpass-api.de then overpass.kumi.systems so a single-mirror outage doesn't break the skill (new overpass_query helper, used by both nearby and bbox) - nearby now accepts --near "<address>" as a shortcut that auto-geocodes, so one command replaces the old 'search → copy coords → nearby' chain - nearby now accepts --category (repeatable) for multi-type queries in one call (e.g. --category restaurant --category bar), results merged and deduped by (osm_type, osm_id), sorted by distance, capped at --limit - Each nearby result now includes maps_url (clickable Google Maps search link) and directions_url (Google Maps directions from the search point — only when a ref point is known) - Promoted commonly-useful OSM tags to top-level fields on each result: cuisine, hours (opening_hours), phone, website — instead of forcing callers to dig into the raw tags dict SKILL.md: - Version bumped 1.1.0 → 1.2.0, description rewritten to lead with capability surface - New 'Working With Telegram Location Pins' section replacing find-nearby's equivalent workflow - metadata.hermes.supersedes: [find-nearby] so tooling can flag any lingering references to the old skill External references updated: - optional-skills/productivity/telephony/SKILL.md — related_skills find-nearby → maps - website/docs/reference/skills-catalog.md — removed the (now-empty) 'leisure' section, added 'maps' row under productivity - website/docs/user-guide/features/cron.md — find-nearby example usages swapped to maps - tests/tools/test_cronjob_tools.py, tests/hermes_cli/test_cron.py, tests/cron/test_scheduler.py — fixture string values swapped - cli.py:5290 — /cron help-hint example swapped Not touched: - RELEASE_v0.2.0.md — historical record, left intact E2E-verified live (Nominatim + Overpass, one query each): - nearby --near "Times Square" --category restaurant --category bar → 3 results, sorted by distance, all with maps_url, directions_url, cuisine, phone, website where OSM had the tags All 111 targeted tests pass across tests/cron/, tests/tools/, tests/hermes_cli/.
107 lines
3.3 KiB
Python
107 lines
3.3 KiB
Python
"""Tests for hermes_cli.cron command handling."""
|
|
|
|
from argparse import Namespace
|
|
|
|
import pytest
|
|
|
|
from cron.jobs import create_job, get_job, list_jobs
|
|
from hermes_cli.cron import cron_command
|
|
|
|
|
|
@pytest.fixture()
|
|
def tmp_cron_dir(tmp_path, monkeypatch):
|
|
monkeypatch.setattr("cron.jobs.CRON_DIR", tmp_path / "cron")
|
|
monkeypatch.setattr("cron.jobs.JOBS_FILE", tmp_path / "cron" / "jobs.json")
|
|
monkeypatch.setattr("cron.jobs.OUTPUT_DIR", tmp_path / "cron" / "output")
|
|
return tmp_path
|
|
|
|
|
|
class TestCronCommandLifecycle:
|
|
def test_pause_resume_run(self, tmp_cron_dir, capsys):
|
|
job = create_job(prompt="Check server status", schedule="every 1h")
|
|
|
|
cron_command(Namespace(cron_command="pause", job_id=job["id"]))
|
|
paused = get_job(job["id"])
|
|
assert paused["state"] == "paused"
|
|
|
|
cron_command(Namespace(cron_command="resume", job_id=job["id"]))
|
|
resumed = get_job(job["id"])
|
|
assert resumed["state"] == "scheduled"
|
|
|
|
cron_command(Namespace(cron_command="run", job_id=job["id"]))
|
|
triggered = get_job(job["id"])
|
|
assert triggered["state"] == "scheduled"
|
|
|
|
out = capsys.readouterr().out
|
|
assert "Paused job" in out
|
|
assert "Resumed job" in out
|
|
assert "Triggered job" in out
|
|
|
|
def test_edit_can_replace_and_clear_skills(self, tmp_cron_dir, capsys):
|
|
job = create_job(
|
|
prompt="Combine skill outputs",
|
|
schedule="every 1h",
|
|
skill="blogwatcher",
|
|
)
|
|
|
|
cron_command(
|
|
Namespace(
|
|
cron_command="edit",
|
|
job_id=job["id"],
|
|
schedule="every 2h",
|
|
prompt="Revised prompt",
|
|
name="Edited Job",
|
|
deliver=None,
|
|
repeat=None,
|
|
skill=None,
|
|
skills=["maps", "blogwatcher"],
|
|
clear_skills=False,
|
|
)
|
|
)
|
|
updated = get_job(job["id"])
|
|
assert updated["skills"] == ["maps", "blogwatcher"]
|
|
assert updated["name"] == "Edited Job"
|
|
assert updated["prompt"] == "Revised prompt"
|
|
assert updated["schedule_display"] == "every 120m"
|
|
|
|
cron_command(
|
|
Namespace(
|
|
cron_command="edit",
|
|
job_id=job["id"],
|
|
schedule=None,
|
|
prompt=None,
|
|
name=None,
|
|
deliver=None,
|
|
repeat=None,
|
|
skill=None,
|
|
skills=None,
|
|
clear_skills=True,
|
|
)
|
|
)
|
|
cleared = get_job(job["id"])
|
|
assert cleared["skills"] == []
|
|
assert cleared["skill"] is None
|
|
|
|
out = capsys.readouterr().out
|
|
assert "Updated job" in out
|
|
|
|
def test_create_with_multiple_skills(self, tmp_cron_dir, capsys):
|
|
cron_command(
|
|
Namespace(
|
|
cron_command="create",
|
|
schedule="every 1h",
|
|
prompt="Use both skills",
|
|
name="Skill combo",
|
|
deliver=None,
|
|
repeat=None,
|
|
skill=None,
|
|
skills=["blogwatcher", "maps"],
|
|
)
|
|
)
|
|
out = capsys.readouterr().out
|
|
assert "Created job" in out
|
|
|
|
jobs = list_jobs()
|
|
assert len(jobs) == 1
|
|
assert jobs[0]["skills"] == ["blogwatcher", "maps"]
|
|
assert jobs[0]["name"] == "Skill combo"
|